From 6895054d6da8f4b786bafae8ad922749438cabe7 Mon Sep 17 00:00:00 2001 From: UNV Date: Thu, 1 May 2025 21:52:17 +0300 Subject: [PATCH 1/2] Reformatting users of EP_NAME. --- .../UnusedDeclarationFixProvider.java | 7 +- .../codeInspection/JavaExtensionPoints.java | 10 +- .../daemon/impl/UnusedSymbolUtil.java | 575 +- .../daemon/impl/analysis/HighlightUtil.java | 7028 +++++++++-------- .../ChangeVariableTypeQuickFixProvider.java | 13 +- .../canBeFinal/CanBeFinalHandler.java | 16 +- .../impl/javaCompiler/BackendCompiler.java | 63 +- .../JavaCompilerConfiguration.java | 629 +- .../java/coverage/JavaCoverageEngine.java | 988 +-- .../coverage/JavaCoverageEngineExtension.java | 28 +- .../java/debugger/PositionManagerFactory.java | 6 +- .../java/debugger/engine/DebuggerUtils.java | 884 ++- .../engine/SimplePropertyGetterProvider.java | 5 +- .../engine/SourcePositionHighlighter.java | 27 +- .../DebuggerClassFilterProvider.java | 6 +- .../java/debugger/impl/DebuggerSession.java | 1707 ++-- .../impl/JvmSteppingCommandProvider.java | 48 +- .../impl/engine/DebugProcessImpl.java | 3785 ++++----- .../impl/engine/ExtraSteppingFilter.java | 12 +- .../engine/FrameExtraVariablesProvider.java | 6 +- .../engine/JavaBreakpointHandlerFactory.java | 7 +- .../debugger/impl/engine/JavaStackFrame.java | 144 +- .../debugger/impl/engine/RequestHint.java | 604 +- .../impl/engine/SourcePositionProvider.java | 55 +- .../impl/settings/NodeRendererSettings.java | 965 +-- .../impl/ui/tree/render/NodeRenderer.java | 10 +- .../java/execution/JUnitRecognizer.java | 20 +- .../java/execution/JavaTestPatcher.java | 4 +- .../filters/ExceptionFilterFactory.java | 6 +- .../execution/filters/ExceptionFilters.java | 21 +- .../runners/JavaPatchableProgramRunner.java | 21 +- .../execution/runners/JavaProgramPatcher.java | 4 +- .../impl/JavaTestFrameworkRunnableState.java | 676 +- .../impl/RunConfigurationExtension.java | 81 +- .../BaseJavaApplicationCommandLineState.java | 50 +- .../java/execution/impl/ui/JreProvider.java | 7 +- .../search/CustomPropertyScopeProvider.java | 4 +- .../AnnotationPackageSupport.java | 78 +- .../codeInsight/AnnotationTargetUtil.java | 591 +- .../language/codeInsight/ImportFilter.java | 22 +- .../InferredAnnotationProvider.java | 31 +- .../runner/JavaMainMethodProvider.java | 8 +- .../jvm/facade/JvmElementProvider.java | 6 +- .../language/psi/ClassTypePointerFactory.java | 6 +- .../psi/augment/PsiAugmentProvider.java | 498 +- .../psi/augment/TypeAnnotationModifier.java | 29 +- .../psi/targets/AliasingPsiTargetMapper.java | 6 +- .../impl/codeInsight/ExceptionUtil.java | 1677 ++-- .../impl/psi/impl/compiled/ClsClassImpl.java | 1128 +-- .../compiled/ClsCustomNavigationPolicy.java | 34 +- .../impl/psi/impl/compiled/ClsFieldImpl.java | 400 +- .../impl/psi/impl/compiled/ClsFileImpl.java | 1148 +-- .../impl/psi/impl/compiled/ClsMethodImpl.java | 625 +- .../SmartTypePointerManagerImpl.java | 377 +- .../impl/source/PsiClassReferenceType.java | 467 +- .../graphInference/InferenceVariable.java | 324 +- .../codeInsight/ExtraExceptionHandler.java | 4 +- .../properties/impl/i18n/I18nizeAction.java | 245 +- .../impl/i18n/I18nizeHandlerProvider.java | 7 +- .../InferredAnnotationsManagerImpl.java | 72 +- .../daemon/impl/quickfix/OrderEntryFix.java | 407 +- .../quickFix/ExternalLibraryResolver.java | 54 +- .../generation/GenerateMembersUtil.java | 1589 ++-- .../GetterSetterPrototypeProvider.java | 112 +- .../generation/OverrideImplementUtil.java | 1277 +-- .../OverrideImplementsAnnotationsHandler.java | 9 +- .../JavaExpressionSurroundDescriptor.java | 83 +- .../JavaExpressionSurrounder.java | 39 +- .../DocumentationDelegateProvider.java | 79 +- .../macro/VariableTypeCalculator.java | 32 +- .../UncheckedWarningLocalInspectionBase.java | 1017 +-- .../visibility/VisibilityExtension.java | 4 +- .../visibility/VisibilityInspection.java | 1273 ++- .../GenerateToStringActionHandlerImpl.java | 400 +- .../BaseSmartPointerPsiNode.java | 196 +- ...ClassInitializerMayBeStaticInspection.java | 99 +- .../MethodMayBeStaticInspection.java | 330 +- .../roots/JavaProjectModelModifier.java | 93 +- ...vaProjectModelModificationServiceImpl.java | 97 +- .../psi/NonClasspathResolveScopeEnlarger.java | 36 +- ...troduceParameterMethodUsagesProcessor.java | 19 +- .../IntroduceParameterProcessor.java | 1236 ++- .../IntroduceParameterUtil.java | 166 +- .../migration/MigrationMapSet.java | 708 +- .../PredefinedMigrationProvider.java | 22 +- .../MoveAllClassesInFileHandler.java | 13 +- .../MoveClassHandler.java | 28 +- .../MoveClassToInnerHandler.java | 21 +- .../MoveClassToInnerProcessor.java | 571 +- .../MoveClassesOrPackagesImpl.java | 661 +- .../MoveClassesOrPackagesProcessor.java | 1051 +-- .../MoveClassesOrPackagesUtil.java | 624 +- .../MoveDirectoryWithClassesHelper.java | 64 +- .../RenameAliasingPomTargetProcessor.java | 52 +- .../safeDelete/ImportSearcher.java | 30 +- 95 files changed, 19811 insertions(+), 18986 deletions(-) diff --git a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/UnusedDeclarationFixProvider.java b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/UnusedDeclarationFixProvider.java index b07cc9033f..0dcea5c65e 100644 --- a/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/UnusedDeclarationFixProvider.java +++ b/java-analysis-api/src/main/java/com/intellij/java/analysis/codeInspection/UnusedDeclarationFixProvider.java @@ -30,9 +30,8 @@ */ @ExtensionAPI(ComponentScope.APPLICATION) public interface UnusedDeclarationFixProvider { + ExtensionPointName EP_NAME = ExtensionPointName.create(UnusedDeclarationFixProvider.class); - ExtensionPointName EP_NAME = ExtensionPointName.create(UnusedDeclarationFixProvider.class); - - @Nonnull - IntentionAction[] getQuickFixes(PsiElement unusedElement); + @Nonnull + IntentionAction[] getQuickFixes(PsiElement unusedElement); } \ No newline at end of file diff --git a/java-analysis-api/src/main/java/consulo/java/analysis/codeInspection/JavaExtensionPoints.java b/java-analysis-api/src/main/java/consulo/java/analysis/codeInspection/JavaExtensionPoints.java index 23c94557c3..b7144462da 100644 --- a/java-analysis-api/src/main/java/consulo/java/analysis/codeInspection/JavaExtensionPoints.java +++ b/java-analysis-api/src/main/java/consulo/java/analysis/codeInspection/JavaExtensionPoints.java @@ -26,10 +26,10 @@ */ @Deprecated public interface JavaExtensionPoints { - @Nonnull - @Deprecated - ExtensionPointName DEAD_CODE_EP_NAME = ExtensionPointName.create(EntryPoint.class); + @Nonnull + @Deprecated + ExtensionPointName DEAD_CODE_EP_NAME = ExtensionPointName.create(EntryPoint.class); - @Nonnull - ExtensionPointName CANT_BE_STATIC_EP_NAME = ExtensionPointName.create(CantBeStaticCondition.class); + @Nonnull + ExtensionPointName CANT_BE_STATIC_EP_NAME = ExtensionPointName.create(CantBeStaticCondition.class); } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/UnusedSymbolUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/UnusedSymbolUtil.java index 176af67b11..aa71556274 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/UnusedSymbolUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/UnusedSymbolUtil.java @@ -51,323 +51,336 @@ import static consulo.language.psi.search.PsiSearchHelper.SearchCostResult.TOO_MANY_OCCURRENCES; public class UnusedSymbolUtil { - public static boolean isInjected(@Nonnull Project project, @Nonnull PsiModifierListOwner modifierListOwner) { - return EntryPointsManagerBase.getInstance(project).isEntryPoint(modifierListOwner); - } - - public static boolean isImplicitUsage( - @Nonnull Project project, - @Nonnull PsiModifierListOwner element, - @Nonnull ProgressIndicator progress - ) { - return isInjected(project, element) || project.getExtensionPoint(ImplicitUsageProvider.class).findFirstSafe(provider -> { - progress.checkCanceled(); - return provider.isImplicitUsage(element); - }) != null; - } - - public static boolean isImplicitRead(@Nonnull PsiVariable variable) { - return isImplicitRead(variable.getProject(), variable, null); - } - - public static boolean isImplicitRead(@Nonnull Project project, @Nonnull PsiVariable element, @Nullable ProgressIndicator progress) { - return project.getExtensionPoint(ImplicitUsageProvider.class).findFirstSafe(provider -> { - ProgressManager.checkCanceled(); - return provider.isImplicitRead(element); - }) != null || isInjected(project, element); - } - - public static boolean isImplicitWrite(@Nonnull PsiVariable variable) { - return isImplicitWrite(variable.getProject(), variable, null); - } - - public static boolean isImplicitWrite(@Nonnull Project project, @Nonnull PsiVariable element, @Nullable ProgressIndicator progress) { - return project.getExtensionPoint(ImplicitUsageProvider.class).findFirstSafe(provider -> { - ProgressManager.checkCanceled(); - return provider.isImplicitWrite(element); - }) != null || isInjected(project, element); - } - - @Nullable - public static HighlightInfo createUnusedSymbolInfo( - @Nonnull PsiElement element, - @Nonnull String message, - @Nonnull final HighlightInfoType highlightInfoType - ) { - HighlightInfo info = HighlightInfo.newHighlightInfo(highlightInfoType).range(element).descriptionAndTooltip - (message).create(); - if (info == null) { - return null; //filtered out + public static boolean isInjected(@Nonnull Project project, @Nonnull PsiModifierListOwner modifierListOwner) { + return EntryPointsManagerBase.getInstance(project).isEntryPoint(modifierListOwner); } - for (UnusedDeclarationFixProvider provider : UnusedDeclarationFixProvider.EP_NAME.getExtensionList()) { - IntentionAction[] fixes = provider.getQuickFixes(element); - for (IntentionAction fix : fixes) { - QuickFixAction.registerQuickFixAction(info, fix); - } + public static boolean isImplicitUsage( + @Nonnull Project project, + @Nonnull PsiModifierListOwner element, + @Nonnull ProgressIndicator progress + ) { + return isInjected(project, element) || project.getExtensionPoint(ImplicitUsageProvider.class).findFirstSafe(provider -> { + progress.checkCanceled(); + return provider.isImplicitUsage(element); + }) != null; } - return info; - } - public static boolean isFieldUnused( - @Nonnull Project project, - @Nonnull PsiFile containingFile, - @Nonnull PsiField field, - @Nonnull ProgressIndicator progress, - @Nonnull GlobalUsageHelper helper - ) { - if (helper.isLocallyUsed(field)) { - return false; - } - if (field instanceof PsiEnumConstant && isEnumValuesMethodUsed(project, containingFile, field, progress, helper)) { - return false; + public static boolean isImplicitRead(@Nonnull PsiVariable variable) { + return isImplicitRead(variable.getProject(), variable, null); } - return weAreSureThereAreNoUsages(project, containingFile, field, progress, helper); - } - public static boolean isMethodReferenced( - @Nonnull Project project, - @Nonnull PsiFile containingFile, - @Nonnull PsiMethod method, - @Nonnull ProgressIndicator progress, - @Nonnull GlobalUsageHelper helper - ) { - if (helper.isLocallyUsed(method)) { - return true; + public static boolean isImplicitRead(@Nonnull Project project, @Nonnull PsiVariable element, @Nullable ProgressIndicator progress) { + return project.getExtensionPoint(ImplicitUsageProvider.class).findFirstSafe(provider -> { + ProgressManager.checkCanceled(); + return provider.isImplicitRead(element); + }) != null || isInjected(project, element); } - boolean isPrivate = method.hasModifierProperty(PsiModifier.PRIVATE); - PsiClass containingClass = method.getContainingClass(); - if (JavaHighlightUtil.isSerializationRelatedMethod(method, containingClass)) { - return true; + public static boolean isImplicitWrite(@Nonnull PsiVariable variable) { + return isImplicitWrite(variable.getProject(), variable, null); } - if (isPrivate) { - if (isIntentionalPrivateConstructor(method, containingClass) || isImplicitUsage(project, method, progress)) { - return true; - } - if (!helper.isCurrentFileAlreadyChecked()) { - return !weAreSureThereAreNoUsages(project, containingFile, method, progress, helper); - } - } else { - //class maybe used in some weird way, e.g. from XML, therefore the only constructor is used too - boolean isConstructor = method.isConstructor(); - if (containingClass != null && isConstructor && containingClass.getConstructors().length == 1 && - isClassUsed(project, containingFile, containingClass, progress, helper)) { - return true; - } - if (isImplicitUsage(project, method, progress)) { - return true; - } - if (!isConstructor && FindSuperElementsHelper.findSuperElements(method).length != 0) { - return true; - } - if (!weAreSureThereAreNoUsages(project, containingFile, method, progress, helper)) { - return true; - } + public static boolean isImplicitWrite(@Nonnull Project project, @Nonnull PsiVariable element, @Nullable ProgressIndicator progress) { + return project.getExtensionPoint(ImplicitUsageProvider.class).findFirstSafe(provider -> { + ProgressManager.checkCanceled(); + return provider.isImplicitWrite(element); + }) != null || isInjected(project, element); } - return false; - } - private static boolean weAreSureThereAreNoUsages( - @Nonnull Project project, - @Nonnull PsiFile containingFile, - @Nonnull final PsiMember member, - @Nonnull ProgressIndicator progress, - @Nonnull GlobalUsageHelper helper - ) { - log("* " + member.getName() + ": call wearesure"); - if (!helper.shouldCheckUsages(member)) { - log("* " + member.getName() + ": should not check"); - return false; - } + @Nullable + public static HighlightInfo createUnusedSymbolInfo( + @Nonnull PsiElement element, + @Nonnull String message, + @Nonnull final HighlightInfoType highlightInfoType + ) { + HighlightInfo info = HighlightInfo.newHighlightInfo(highlightInfoType).range(element).descriptionAndTooltip + (message).create(); + if (info == null) { + return null; //filtered out + } - final PsiFile ignoreFile = helper.isCurrentFileAlreadyChecked() ? containingFile : null; + for (UnusedDeclarationFixProvider provider : UnusedDeclarationFixProvider.EP_NAME.getExtensionList()) { + IntentionAction[] fixes = provider.getQuickFixes(element); + for (IntentionAction fix : fixes) { + QuickFixAction.registerQuickFixAction(info, fix); + } + } + return info; + } - boolean sure = processUsages(project, containingFile, member, progress, ignoreFile, new Processor() { - @Override - public boolean process(UsageInfo info) { - PsiFile psiFile = info.getFile(); - if (psiFile == ignoreFile || psiFile == null) { - return true; // ignore usages in containingFile because isLocallyUsed() method would have caught - // that + public static boolean isFieldUnused( + @Nonnull Project project, + @Nonnull PsiFile containingFile, + @Nonnull PsiField field, + @Nonnull ProgressIndicator progress, + @Nonnull GlobalUsageHelper helper + ) { + if (helper.isLocallyUsed(field)) { + return false; } - int offset = info.getNavigationOffset(); - if (offset == -1) { - return true; + if (field instanceof PsiEnumConstant && isEnumValuesMethodUsed(project, containingFile, field, progress, helper)) { + return false; } - PsiElement element = psiFile.findElementAt(offset); - boolean inComment = element instanceof PsiComment; - log("* " + member.getName() + ": usage :" + element); - return inComment; // ignore comments - } - }); - log("* " + member.getName() + ": result:" + sure); - return sure; - } + return weAreSureThereAreNoUsages(project, containingFile, field, progress, helper); + } - private static void log(String s) { - //System.out.println(s); - } + public static boolean isMethodReferenced( + @Nonnull Project project, + @Nonnull PsiFile containingFile, + @Nonnull PsiMethod method, + @Nonnull ProgressIndicator progress, + @Nonnull GlobalUsageHelper helper + ) { + if (helper.isLocallyUsed(method)) { + return true; + } - // return false if can't process usages (weird member of too may usages) or processor returned false - @RequiredReadAction - public static boolean processUsages( - @Nonnull Project project, - @Nonnull PsiFile containingFile, - @Nonnull PsiMember member, - @Nonnull ProgressIndicator progress, - @Nullable PsiFile ignoreFile, - @Nonnull Processor usageInfoProcessor - ) { - String name = member.getName(); - if (name == null) { - log("* " + member.getName() + " no name; false"); - return false; - } - SearchScope useScope = member.getUseScope(); - PsiSearchHelper searchHelper = PsiSearchHelper.SERVICE.getInstance(project); - if (useScope instanceof GlobalSearchScope globalSearchScope) { - // some classes may have references from within XML outside dependent modules, e.g. our actions - if (member instanceof PsiClass) { - useScope = GlobalSearchScope.projectScope(project).uniteWith(globalSearchScope); - } + boolean isPrivate = method.hasModifierProperty(PsiModifier.PRIVATE); + PsiClass containingClass = method.getContainingClass(); + if (JavaHighlightUtil.isSerializationRelatedMethod(method, containingClass)) { + return true; + } + if (isPrivate) { + if (isIntentionalPrivateConstructor(method, containingClass) || isImplicitUsage(project, method, progress)) { + return true; + } + if (!helper.isCurrentFileAlreadyChecked()) { + return !weAreSureThereAreNoUsages(project, containingFile, method, progress, helper); + } + } + else { + //class maybe used in some weird way, e.g. from XML, therefore the only constructor is used too + boolean isConstructor = method.isConstructor(); + if (containingClass != null && isConstructor && containingClass.getConstructors().length == 1 && + isClassUsed(project, containingFile, containingClass, progress, helper)) { + return true; + } + if (isImplicitUsage(project, method, progress)) { + return true; + } - // if we've resolved all references, find usages will be fast - PsiSearchHelper.SearchCostResult cheapEnough = RefResolveService.ENABLED && RefResolveService.getInstance - (project).isUpToDate() ? PsiSearchHelper.SearchCostResult.FEW_OCCURRENCES : searchHelper - .isCheapEnoughToSearch(name, (GlobalSearchScope) useScope, ignoreFile, progress); - if (cheapEnough == TOO_MANY_OCCURRENCES) { - log("* " + member.getName() + " too many usages; false"); + if (!isConstructor && FindSuperElementsHelper.findSuperElements(method).length != 0) { + return true; + } + if (!weAreSureThereAreNoUsages(project, containingFile, method, progress, helper)) { + return true; + } + } return false; - } - - //search usages if it cheap - //if count is 0 there is no usages since we've called myRefCountHolder.isReferenced() before - if (cheapEnough == PsiSearchHelper.SearchCostResult.ZERO_OCCURRENCES && !canBeReferencedViaWeirdNames - (member, containingFile)) { - log("* " + member.getName() + " 0 usages; true"); - return true; - } + } - if (member instanceof PsiMethod) { - String propertyName = PropertyUtil.getPropertyName(member); - if (propertyName != null) { - SearchScope fileScope = containingFile.getUseScope(); - if (fileScope instanceof GlobalSearchScope - && searchHelper.isCheapEnoughToSearch(propertyName, (GlobalSearchScope) fileScope, ignoreFile, progress) == TOO_MANY_OCCURRENCES) { - log("* " + member.getName() + " too many prop usages; false"); + private static boolean weAreSureThereAreNoUsages( + @Nonnull Project project, + @Nonnull PsiFile containingFile, + @Nonnull final PsiMember member, + @Nonnull ProgressIndicator progress, + @Nonnull GlobalUsageHelper helper + ) { + log("* " + member.getName() + ": call wearesure"); + if (!helper.shouldCheckUsages(member)) { + log("* " + member.getName() + ": should not check"); return false; - } } - } - } - FindUsagesOptions options; - if (member instanceof PsiPackage) { - options = new JavaPackageFindUsagesOptions(project); - options.isSearchForTextOccurrences = true; - } else if (member instanceof PsiClass) { - options = new JavaClassFindUsagesOptions(project); - options.isSearchForTextOccurrences = true; - } else if (member instanceof PsiMethod method) { - options = new JavaMethodFindUsagesOptions(project); - options.isSearchForTextOccurrences = method.isConstructor(); - } else if (member instanceof PsiVariable) { - options = new JavaVariableFindUsagesOptions(project); - options.isSearchForTextOccurrences = false; - } else { - options = new FindUsagesOptions(project); - options.isSearchForTextOccurrences = true; + + final PsiFile ignoreFile = helper.isCurrentFileAlreadyChecked() ? containingFile : null; + + boolean sure = processUsages(project, containingFile, member, progress, ignoreFile, new Processor() { + @Override + public boolean process(UsageInfo info) { + PsiFile psiFile = info.getFile(); + if (psiFile == ignoreFile || psiFile == null) { + return true; // ignore usages in containingFile because isLocallyUsed() method would have caught + // that + } + int offset = info.getNavigationOffset(); + if (offset == -1) { + return true; + } + PsiElement element = psiFile.findElementAt(offset); + boolean inComment = element instanceof PsiComment; + log("* " + member.getName() + ": usage :" + element); + return inComment; // ignore comments + } + }); + log("* " + member.getName() + ": result:" + sure); + return sure; } - options.isUsages = true; - options.searchScope = useScope; - return JavaFindUsagesHelper.processElementUsages(member, options, usageInfoProcessor); - } - private static boolean isEnumValuesMethodUsed( - @Nonnull Project project, - @Nonnull PsiFile containingFile, - @Nonnull PsiMember member, - @Nonnull ProgressIndicator progress, - @Nonnull GlobalUsageHelper helper - ) { - final PsiClass containingClass = member.getContainingClass(); - if (!(containingClass instanceof PsiClassImpl)) { - return true; + private static void log(String s) { + //System.out.println(s); } - if (containingClass.isEnum()) { - PsiMethod[] methodsByName = containingClass.findMethodsByName(JavaEnumAugmentProvider.VALUES_METHOD_NAME, - false); + // return false if can't process usages (weird member of too may usages) or processor returned false + @RequiredReadAction + public static boolean processUsages( + @Nonnull Project project, + @Nonnull PsiFile containingFile, + @Nonnull PsiMember member, + @Nonnull ProgressIndicator progress, + @Nullable PsiFile ignoreFile, + @Nonnull Processor usageInfoProcessor + ) { + String name = member.getName(); + if (name == null) { + log("* " + member.getName() + " no name; false"); + return false; + } + SearchScope useScope = member.getUseScope(); + PsiSearchHelper searchHelper = PsiSearchHelper.SERVICE.getInstance(project); + if (useScope instanceof GlobalSearchScope globalSearchScope) { + // some classes may have references from within XML outside dependent modules, e.g. our actions + if (member instanceof PsiClass) { + useScope = GlobalSearchScope.projectScope(project).uniteWith(globalSearchScope); + } + + // if we've resolved all references, find usages will be fast + PsiSearchHelper.SearchCostResult cheapEnough = RefResolveService.ENABLED && RefResolveService.getInstance + (project).isUpToDate() ? PsiSearchHelper.SearchCostResult.FEW_OCCURRENCES : searchHelper + .isCheapEnoughToSearch(name, (GlobalSearchScope)useScope, ignoreFile, progress); + if (cheapEnough == TOO_MANY_OCCURRENCES) { + log("* " + member.getName() + " too many usages; false"); + return false; + } - PsiMethod valuesMethod = null; - for (PsiMethod psiMethod : methodsByName) { - if (psiMethod.getParameterList().getParametersCount() == 0 && psiMethod.hasModifierProperty - (PsiModifier.STATIC)) { - valuesMethod = psiMethod; - break; + //search usages if it cheap + //if count is 0 there is no usages since we've called myRefCountHolder.isReferenced() before + if (cheapEnough == PsiSearchHelper.SearchCostResult.ZERO_OCCURRENCES && !canBeReferencedViaWeirdNames + (member, containingFile)) { + log("* " + member.getName() + " 0 usages; true"); + return true; + } + + if (member instanceof PsiMethod) { + String propertyName = PropertyUtil.getPropertyName(member); + if (propertyName != null) { + SearchScope fileScope = containingFile.getUseScope(); + if (fileScope instanceof GlobalSearchScope + && searchHelper.isCheapEnoughToSearch( + propertyName, + (GlobalSearchScope)fileScope, + ignoreFile, + progress + ) == TOO_MANY_OCCURRENCES) { + log("* " + member.getName() + " too many prop usages; false"); + return false; + } + } + } + } + FindUsagesOptions options; + if (member instanceof PsiPackage) { + options = new JavaPackageFindUsagesOptions(project); + options.isSearchForTextOccurrences = true; + } + else if (member instanceof PsiClass) { + options = new JavaClassFindUsagesOptions(project); + options.isSearchForTextOccurrences = true; + } + else if (member instanceof PsiMethod method) { + options = new JavaMethodFindUsagesOptions(project); + options.isSearchForTextOccurrences = method.isConstructor(); } - } - return valuesMethod == null || isMethodReferenced(project, containingFile, valuesMethod, progress, helper); - } else { - return true; + else if (member instanceof PsiVariable) { + options = new JavaVariableFindUsagesOptions(project); + options.isSearchForTextOccurrences = false; + } + else { + options = new FindUsagesOptions(project); + options.isSearchForTextOccurrences = true; + } + options.isUsages = true; + options.searchScope = useScope; + return JavaFindUsagesHelper.processElementUsages(member, options, usageInfoProcessor); } - } - private static boolean canBeReferencedViaWeirdNames(@Nonnull PsiMember member, @Nonnull PsiFile containingFile) { - if (member instanceof PsiClass) { - return false; - } - if (!(containingFile instanceof PsiJavaFile)) { - return true; // Groovy field can be referenced from Java by getter - } - if (member instanceof PsiField) { - return false; //Java field cannot be referenced by anything but its name - } - if (member instanceof PsiMethod method) { - return PropertyUtil.isSimplePropertyAccessor(method); //Java accessors can be referenced by field name from Groovy + private static boolean isEnumValuesMethodUsed( + @Nonnull Project project, + @Nonnull PsiFile containingFile, + @Nonnull PsiMember member, + @Nonnull ProgressIndicator progress, + @Nonnull GlobalUsageHelper helper + ) { + final PsiClass containingClass = member.getContainingClass(); + if (!(containingClass instanceof PsiClassImpl)) { + return true; + } + + if (containingClass.isEnum()) { + PsiMethod[] methodsByName = containingClass.findMethodsByName( + JavaEnumAugmentProvider.VALUES_METHOD_NAME, + false + ); + + PsiMethod valuesMethod = null; + for (PsiMethod psiMethod : methodsByName) { + if (psiMethod.getParameterList().getParametersCount() == 0 && psiMethod.hasModifierProperty + (PsiModifier.STATIC)) { + valuesMethod = psiMethod; + break; + } + } + return valuesMethod == null || isMethodReferenced(project, containingFile, valuesMethod, progress, helper); + } + else { + return true; + } } - return false; - } - public static boolean isClassUsed( - @Nonnull Project project, - @Nonnull PsiFile containingFile, - @Nonnull PsiClass aClass, - @Nonnull ProgressIndicator progress, - @Nonnull GlobalUsageHelper helper - ) { - Boolean result = helper.unusedClassCache.get(aClass); - if (result == null) { - result = isReallyUsed(project, containingFile, aClass, progress, helper); - helper.unusedClassCache.put(aClass, result); + private static boolean canBeReferencedViaWeirdNames(@Nonnull PsiMember member, @Nonnull PsiFile containingFile) { + if (member instanceof PsiClass) { + return false; + } + if (!(containingFile instanceof PsiJavaFile)) { + return true; // Groovy field can be referenced from Java by getter + } + if (member instanceof PsiField) { + return false; //Java field cannot be referenced by anything but its name + } + if (member instanceof PsiMethod method) { + return PropertyUtil.isSimplePropertyAccessor(method); //Java accessors can be referenced by field name from Groovy + } + return false; } - return result; - } - private static boolean isReallyUsed( - @Nonnull Project project, - @Nonnull PsiFile containingFile, - @Nonnull PsiClass aClass, - @Nonnull ProgressIndicator progress, - @Nonnull GlobalUsageHelper helper - ) { - if (isImplicitUsage(project, aClass, progress) || helper.isLocallyUsed(aClass)) { - return true; + public static boolean isClassUsed( + @Nonnull Project project, + @Nonnull PsiFile containingFile, + @Nonnull PsiClass aClass, + @Nonnull ProgressIndicator progress, + @Nonnull GlobalUsageHelper helper + ) { + Boolean result = helper.unusedClassCache.get(aClass); + if (result == null) { + result = isReallyUsed(project, containingFile, aClass, progress, helper); + helper.unusedClassCache.put(aClass, result); + } + return result; } - if (helper.isCurrentFileAlreadyChecked()) { - if (aClass.getContainingClass() != null && aClass.hasModifierProperty(PsiModifier.PRIVATE) || - aClass.getParent() instanceof PsiDeclarationStatement || - aClass instanceof PsiTypeParameter) { - return false; - } + + private static boolean isReallyUsed( + @Nonnull Project project, + @Nonnull PsiFile containingFile, + @Nonnull PsiClass aClass, + @Nonnull ProgressIndicator progress, + @Nonnull GlobalUsageHelper helper + ) { + if (isImplicitUsage(project, aClass, progress) || helper.isLocallyUsed(aClass)) { + return true; + } + if (helper.isCurrentFileAlreadyChecked()) { + if (aClass.getContainingClass() != null && aClass.hasModifierProperty(PsiModifier.PRIVATE) || + aClass.getParent() instanceof PsiDeclarationStatement || + aClass instanceof PsiTypeParameter) { + return false; + } + } + return !weAreSureThereAreNoUsages(project, containingFile, aClass, progress, helper); } - return !weAreSureThereAreNoUsages(project, containingFile, aClass, progress, helper); - } - private static boolean isIntentionalPrivateConstructor(@Nonnull PsiMethod method, PsiClass containingClass) { - return method.isConstructor() && - containingClass != null && - containingClass.getConstructors().length == 1; - } + private static boolean isIntentionalPrivateConstructor(@Nonnull PsiMethod method, PsiClass containingClass) { + return method.isConstructor() && + containingClass != null && + containingClass.getConstructors().length == 1; + } } diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightUtil.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightUtil.java index 2671c60d04..aaa9600dd3 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightUtil.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/daemon/impl/analysis/HighlightUtil.java @@ -96,3486 +96,3812 @@ * @since Jul 30, 2002 */ public class HighlightUtil extends HighlightUtilBase { - private static final Logger LOG = Logger.getInstance(HighlightUtil.class); - - private static final Map> ourInterfaceIncompatibleModifiers = new HashMap<>(7); - private static final Map> ourMethodIncompatibleModifiers = new HashMap<>(11); - private static final Map> ourFieldIncompatibleModifiers = new HashMap<>(8); - private static final Map> ourClassIncompatibleModifiers = new HashMap<>(8); - private static final Map> ourClassInitializerIncompatibleModifiers = new HashMap<>(1); - private static final Map> ourModuleIncompatibleModifiers = new HashMap<>(1); - private static final Map> ourRequiresIncompatibleModifiers = new HashMap<>(2); - - private static final Set ourConstructorNotAllowedModifiers = - Set.of(PsiModifier.ABSTRACT, PsiModifier.STATIC, PsiModifier.NATIVE, PsiModifier.FINAL, PsiModifier - .STRICTFP, PsiModifier.SYNCHRONIZED); - - private static final String SERIAL_PERSISTENT_FIELDS_FIELD_NAME = "serialPersistentFields"; - - static { - ourClassIncompatibleModifiers.put(PsiModifier.ABSTRACT, Set.of(PsiModifier.FINAL)); - ourClassIncompatibleModifiers.put(PsiModifier.FINAL, Set.of(PsiModifier.ABSTRACT, PsiModifier.SEALED, PsiModifier.NON_SEALED)); - ourClassIncompatibleModifiers.put(PsiModifier.PACKAGE_LOCAL, Set.of(PsiModifier.PRIVATE, PsiModifier.PUBLIC, PsiModifier.PROTECTED)); - ourClassIncompatibleModifiers.put(PsiModifier.PRIVATE, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PROTECTED)); - ourClassIncompatibleModifiers.put(PsiModifier.PUBLIC, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE, PsiModifier.PROTECTED)); - ourClassIncompatibleModifiers.put(PsiModifier.PROTECTED, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PRIVATE)); - ourClassIncompatibleModifiers.put(PsiModifier.STRICTFP, Set.of()); - ourClassIncompatibleModifiers.put(PsiModifier.STATIC, Set.of()); - ourClassIncompatibleModifiers.put(PsiModifier.SEALED, Set.of(PsiModifier.FINAL, PsiModifier.NON_SEALED)); - ourClassIncompatibleModifiers.put(PsiModifier.NON_SEALED, Set.of(PsiModifier.FINAL, PsiModifier.SEALED)); - - ourInterfaceIncompatibleModifiers.put(PsiModifier.ABSTRACT, Set.of()); - ourInterfaceIncompatibleModifiers - .put(PsiModifier.PACKAGE_LOCAL, Set.of(PsiModifier.PRIVATE, PsiModifier.PUBLIC, PsiModifier.PROTECTED)); - ourInterfaceIncompatibleModifiers - .put(PsiModifier.PRIVATE, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PROTECTED)); - ourInterfaceIncompatibleModifiers - .put(PsiModifier.PUBLIC, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE, PsiModifier.PROTECTED)); - ourInterfaceIncompatibleModifiers - .put(PsiModifier.PROTECTED, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PRIVATE)); - ourInterfaceIncompatibleModifiers.put(PsiModifier.STRICTFP, Set.of()); - ourInterfaceIncompatibleModifiers.put(PsiModifier.STATIC, Set.of()); - ourInterfaceIncompatibleModifiers.put(PsiModifier.SEALED, Set.of(PsiModifier.NON_SEALED)); - ourInterfaceIncompatibleModifiers.put(PsiModifier.NON_SEALED, Set.of(PsiModifier.SEALED)); - - ourMethodIncompatibleModifiers.put(PsiModifier.ABSTRACT, Set.of( - PsiModifier.NATIVE, PsiModifier.STATIC, PsiModifier.FINAL, PsiModifier.PRIVATE, PsiModifier.STRICTFP, PsiModifier.SYNCHRONIZED, - PsiModifier.DEFAULT)); - ourMethodIncompatibleModifiers.put(PsiModifier.NATIVE, Set.of(PsiModifier.ABSTRACT, PsiModifier.STRICTFP)); - ourMethodIncompatibleModifiers.put(PsiModifier.PACKAGE_LOCAL, Set.of(PsiModifier.PRIVATE, PsiModifier.PUBLIC, PsiModifier.PROTECTED)); - ourMethodIncompatibleModifiers.put(PsiModifier.PRIVATE, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PROTECTED)); - ourMethodIncompatibleModifiers.put(PsiModifier.PUBLIC, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE, PsiModifier.PROTECTED)); - ourMethodIncompatibleModifiers.put(PsiModifier.PROTECTED, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PRIVATE)); - ourMethodIncompatibleModifiers.put(PsiModifier.STATIC, Set.of(PsiModifier.ABSTRACT, PsiModifier.DEFAULT)); - ourMethodIncompatibleModifiers - .put(PsiModifier.DEFAULT, Set.of(PsiModifier.ABSTRACT, PsiModifier.STATIC, PsiModifier.PRIVATE)); - ourMethodIncompatibleModifiers.put(PsiModifier.SYNCHRONIZED, Set.of(PsiModifier.ABSTRACT)); - ourMethodIncompatibleModifiers.put(PsiModifier.STRICTFP, Set.of(PsiModifier.ABSTRACT)); - ourMethodIncompatibleModifiers.put(PsiModifier.FINAL, Set.of(PsiModifier.ABSTRACT)); - - ourFieldIncompatibleModifiers.put(PsiModifier.FINAL, Set.of(PsiModifier.VOLATILE)); - ourFieldIncompatibleModifiers.put(PsiModifier.PACKAGE_LOCAL, Set.of(PsiModifier.PRIVATE, PsiModifier.PUBLIC, PsiModifier.PROTECTED)); - ourFieldIncompatibleModifiers.put(PsiModifier.PRIVATE, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PROTECTED)); - ourFieldIncompatibleModifiers.put(PsiModifier.PUBLIC, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE, PsiModifier.PROTECTED)); - ourFieldIncompatibleModifiers.put(PsiModifier.PROTECTED, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PRIVATE)); - ourFieldIncompatibleModifiers.put(PsiModifier.STATIC, Set.of()); - ourFieldIncompatibleModifiers.put(PsiModifier.TRANSIENT, Set.of()); - ourFieldIncompatibleModifiers.put(PsiModifier.VOLATILE, Set.of(PsiModifier.FINAL)); - - ourClassInitializerIncompatibleModifiers.put(PsiModifier.STATIC, Set.of()); - - ourModuleIncompatibleModifiers.put(PsiModifier.OPEN, Set.of()); - - ourRequiresIncompatibleModifiers.put(PsiModifier.STATIC, Set.of()); - ourRequiresIncompatibleModifiers.put(PsiModifier.TRANSITIVE, Set.of()); - } - - private HighlightUtil() { - } - - @Nullable - private static String getIncompatibleModifier(String modifier, - @Nullable PsiModifierList modifierList, - @Nonnull Map> incompatibleModifiersHash) { - if (modifierList == null) { - return null; - } - - // modifier is always incompatible with itself - PsiElement[] modifiers = modifierList.getChildren(); - int modifierCount = 0; - for (PsiElement otherModifier : modifiers) { - if (Comparing.equal(modifier, otherModifier.getText(), true)) { - modifierCount++; - } - } - if (modifierCount > 1) { - return modifier; - } - - Set incompatibles = incompatibleModifiersHash.get(modifier); - if (incompatibles == null) { - return null; - } - final PsiElement parent = modifierList.getParent(); - final boolean level8OrHigher = PsiUtil.isLanguageLevel8OrHigher(modifierList); - final boolean level9OrHigher = PsiUtil.isLanguageLevel9OrHigher(modifierList); - for (@PsiModifier.ModifierConstant String incompatible : incompatibles) { - if (level8OrHigher) { - if (modifier.equals(PsiModifier.STATIC) && incompatible.equals(PsiModifier.ABSTRACT)) { - continue; - } - } - if (parent instanceof PsiMethod) { - if (level9OrHigher && modifier.equals(PsiModifier.PRIVATE) && incompatible.equals(PsiModifier.PUBLIC)) { - continue; - } - - if (modifier.equals(PsiModifier.STATIC) && incompatible.equals(PsiModifier.FINAL)) { - final PsiClass containingClass = ((PsiMethod)parent).getContainingClass(); - if (containingClass == null || !containingClass.isInterface()) { - continue; - } - } - } - if (modifierList.hasModifierProperty(incompatible)) { - return incompatible; - } - if (PsiModifier.ABSTRACT.equals(incompatible) && modifierList.hasExplicitModifier(incompatible)) { - return incompatible; - } - } - - return null; - } - - /** - * make element protected/package-private/public suggestion - * for private method in the interface it should add default modifier as well - */ - public static void registerAccessQuickFixAction(@Nonnull PsiMember refElement, - @Nonnull PsiJavaCodeReferenceElement place, - @Nullable HighlightInfo errorResult, - final PsiElement fileResolveScope) { - if (errorResult == null) { - return; - } - PsiClass accessObjectClass = null; - PsiElement qualifier = place.getQualifier(); - if (qualifier instanceof PsiExpression) { - accessObjectClass = (PsiClass)PsiUtil.getAccessObjectClass((PsiExpression)qualifier).getElement(); - } - registerReplaceInaccessibleFieldWithGetterSetterFix(refElement, place, accessObjectClass, errorResult); - - if (refElement instanceof PsiCompiledElement) { - return; - } - PsiModifierList modifierList = refElement.getModifierList(); - if (modifierList == null) { - return; - } - - PsiClass packageLocalClassInTheMiddle = getPackageLocalClassInTheMiddle(place); - if (packageLocalClassInTheMiddle != null) { - IntentionAction fix = QuickFixFactory.getInstance().createModifierListFix(packageLocalClassInTheMiddle, PsiModifier.PUBLIC, true, true); - QuickFixAction.registerQuickFixAction(errorResult, fix); - return; - } - - try { - Project project = refElement.getProject(); - JavaPsiFacade facade = JavaPsiFacade.getInstance(project); - PsiModifierList modifierListCopy = facade.getElementFactory().createFieldFromText("int a;", null).getModifierList(); - modifierListCopy.setModifierProperty(PsiModifier.STATIC, modifierList.hasModifierProperty(PsiModifier.STATIC)); - String minModifier = PsiModifier.PACKAGE_LOCAL; - if (refElement.hasModifierProperty(PsiModifier.PACKAGE_LOCAL)) { - minModifier = PsiModifier.PROTECTED; - } - if (refElement.hasModifierProperty(PsiModifier.PROTECTED)) { - minModifier = PsiModifier.PUBLIC; - } - PsiClass containingClass = refElement.getContainingClass(); - if (containingClass != null && containingClass.isInterface()) { - minModifier = PsiModifier.PUBLIC; - } - String[] modifiers = { - PsiModifier.PACKAGE_LOCAL, - PsiModifier.PROTECTED, - PsiModifier.PUBLIC, - }; - for (int i = ArrayUtil.indexOf(modifiers, minModifier); i < modifiers.length; i++) { - @PsiModifier.ModifierConstant String modifier = modifiers[i]; - modifierListCopy.setModifierProperty(modifier, true); - if (facade.getResolveHelper().isAccessible(refElement, modifierListCopy, place, accessObjectClass, fileResolveScope)) { - IntentionAction fix = QuickFixFactory.getInstance().createModifierListFix(refElement, modifier, true, true); - TextRange fixRange = new TextRange(errorResult.getStartOffset(), errorResult.getEndOffset()); - PsiElement ref = place.getReferenceNameElement(); - if (ref != null) { - fixRange = fixRange.union(ref.getTextRange()); - } - QuickFixAction.registerQuickFixAction(errorResult, fixRange, fix); - } - } - } - catch (IncorrectOperationException e) { - LOG.error(e); - } - } - - @Nullable - private static PsiClass getPackageLocalClassInTheMiddle(@Nonnull PsiElement place) { - if (place instanceof PsiReferenceExpression) { - // check for package-private classes in the middle - PsiReferenceExpression expression = (PsiReferenceExpression)place; - while (true) { - PsiElement resolved = expression.resolve(); - if (resolved instanceof PsiField) { - PsiField field = (PsiField)resolved; - PsiClass aClass = field.getContainingClass(); - if (aClass != null && aClass.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) && !JavaPsiFacade.getInstance(aClass.getProject()) - .arePackagesTheSame(aClass, place)) { - - return aClass; - } - } - PsiExpression qualifier = expression.getQualifierExpression(); - if (!(qualifier instanceof PsiReferenceExpression)) { - break; - } - expression = (PsiReferenceExpression)qualifier; - } - } - return null; - } - - - @Nullable - public static HighlightInfo checkInstanceOfApplicable(@Nonnull PsiInstanceOfExpression expression) { - PsiExpression operand = expression.getOperand(); - PsiTypeElement typeElement = expression.getCheckType(); - if (typeElement == null) { - return null; - } - PsiType checkType = typeElement.getType(); - PsiType operandType = operand.getType(); - if (operandType == null) { - return null; - } - if (TypeConversionUtil.isPrimitiveAndNotNull(operandType) || TypeConversionUtil.isPrimitiveAndNotNull(checkType) || !TypeConversionUtil.areTypesConvertible( - operandType, - checkType)) { - String message = JavaErrorBundle.message("inconvertible.type.cast", - JavaHighlightUtil.formatType(operandType), - JavaHighlightUtil.formatType(checkType)); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - return null; - } - - - /** - * 15.16 Cast Expressions - * ( ReferenceType {AdditionalBound} ) expression, where AdditionalBound: & InterfaceType then all must be true - * - ReferenceType must denote a class or interface type. - * - The erasures of all the listed types must be pairwise different. - * - No two listed types may be subtypes of different parameterization of the same generic interface. - */ - @Nullable - public static HighlightInfo checkIntersectionInTypeCast(@Nonnull PsiTypeCastExpression expression, - @Nonnull LanguageLevel languageLevel, - @Nonnull PsiFile file) { - PsiTypeElement castTypeElement = expression.getCastType(); - if (castTypeElement != null && isIntersection(castTypeElement, castTypeElement.getType())) { - HighlightInfo info = checkFeature(expression, JavaFeature.INTERSECTION_CASTS, languageLevel, file); - if (info != null) { - return info; - } - - final PsiTypeElement[] conjuncts = PsiTreeUtil.getChildrenOfType(castTypeElement, PsiTypeElement.class); - if (conjuncts != null) { - final Set erasures = new HashSet<>(conjuncts.length); - erasures.add(TypeConversionUtil.erasure(conjuncts[0].getType())); - final List conjList = new ArrayList<>(Arrays.asList(conjuncts)); - for (int i = 1; i < conjuncts.length; i++) { - final PsiTypeElement conjunct = conjuncts[i]; - final PsiType conjType = conjunct.getType(); - if (conjType instanceof PsiClassType) { - final PsiClass aClass = ((PsiClassType)conjType).resolve(); - if (aClass != null && !aClass.isInterface()) { - final HighlightInfo errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) - .range(conjunct) - .descriptionAndTooltip(JavaErrorBundle.message("interface" + - ".expected")) - .create(); - QuickFixAction.registerQuickFixAction(errorResult, - new FlipIntersectionSidesFix(aClass.getName(), conjList, conjunct, castTypeElement), - null); - return errorResult; - } - } - else { - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + private static final Logger LOG = Logger.getInstance(HighlightUtil.class); + + private static final Map> ourInterfaceIncompatibleModifiers = new HashMap<>(7); + private static final Map> ourMethodIncompatibleModifiers = new HashMap<>(11); + private static final Map> ourFieldIncompatibleModifiers = new HashMap<>(8); + private static final Map> ourClassIncompatibleModifiers = new HashMap<>(8); + private static final Map> ourClassInitializerIncompatibleModifiers = new HashMap<>(1); + private static final Map> ourModuleIncompatibleModifiers = new HashMap<>(1); + private static final Map> ourRequiresIncompatibleModifiers = new HashMap<>(2); + + private static final Set ourConstructorNotAllowedModifiers = + Set.of(PsiModifier.ABSTRACT, PsiModifier.STATIC, PsiModifier.NATIVE, PsiModifier.FINAL, PsiModifier + .STRICTFP, PsiModifier.SYNCHRONIZED); + + private static final String SERIAL_PERSISTENT_FIELDS_FIELD_NAME = "serialPersistentFields"; + + static { + ourClassIncompatibleModifiers.put(PsiModifier.ABSTRACT, Set.of(PsiModifier.FINAL)); + ourClassIncompatibleModifiers.put(PsiModifier.FINAL, Set.of(PsiModifier.ABSTRACT, PsiModifier.SEALED, PsiModifier.NON_SEALED)); + ourClassIncompatibleModifiers.put( + PsiModifier.PACKAGE_LOCAL, + Set.of(PsiModifier.PRIVATE, PsiModifier.PUBLIC, PsiModifier.PROTECTED) + ); + ourClassIncompatibleModifiers.put( + PsiModifier.PRIVATE, + Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PROTECTED) + ); + ourClassIncompatibleModifiers.put( + PsiModifier.PUBLIC, + Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE, PsiModifier.PROTECTED) + ); + ourClassIncompatibleModifiers.put( + PsiModifier.PROTECTED, + Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PRIVATE) + ); + ourClassIncompatibleModifiers.put(PsiModifier.STRICTFP, Set.of()); + ourClassIncompatibleModifiers.put(PsiModifier.STATIC, Set.of()); + ourClassIncompatibleModifiers.put(PsiModifier.SEALED, Set.of(PsiModifier.FINAL, PsiModifier.NON_SEALED)); + ourClassIncompatibleModifiers.put(PsiModifier.NON_SEALED, Set.of(PsiModifier.FINAL, PsiModifier.SEALED)); + + ourInterfaceIncompatibleModifiers.put(PsiModifier.ABSTRACT, Set.of()); + ourInterfaceIncompatibleModifiers + .put(PsiModifier.PACKAGE_LOCAL, Set.of(PsiModifier.PRIVATE, PsiModifier.PUBLIC, PsiModifier.PROTECTED)); + ourInterfaceIncompatibleModifiers + .put(PsiModifier.PRIVATE, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PROTECTED)); + ourInterfaceIncompatibleModifiers + .put(PsiModifier.PUBLIC, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE, PsiModifier.PROTECTED)); + ourInterfaceIncompatibleModifiers + .put(PsiModifier.PROTECTED, Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PRIVATE)); + ourInterfaceIncompatibleModifiers.put(PsiModifier.STRICTFP, Set.of()); + ourInterfaceIncompatibleModifiers.put(PsiModifier.STATIC, Set.of()); + ourInterfaceIncompatibleModifiers.put(PsiModifier.SEALED, Set.of(PsiModifier.NON_SEALED)); + ourInterfaceIncompatibleModifiers.put(PsiModifier.NON_SEALED, Set.of(PsiModifier.SEALED)); + + ourMethodIncompatibleModifiers.put( + PsiModifier.ABSTRACT, + Set.of( + PsiModifier.NATIVE, + PsiModifier.STATIC, + PsiModifier.FINAL, + PsiModifier.PRIVATE, + PsiModifier.STRICTFP, + PsiModifier.SYNCHRONIZED, + PsiModifier.DEFAULT + ) + ); + ourMethodIncompatibleModifiers.put(PsiModifier.NATIVE, Set.of(PsiModifier.ABSTRACT, PsiModifier.STRICTFP)); + ourMethodIncompatibleModifiers.put( + PsiModifier.PACKAGE_LOCAL, + Set.of(PsiModifier.PRIVATE, PsiModifier.PUBLIC, PsiModifier.PROTECTED) + ); + ourMethodIncompatibleModifiers.put( + PsiModifier.PRIVATE, + Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PROTECTED) + ); + ourMethodIncompatibleModifiers.put( + PsiModifier.PUBLIC, + Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE, PsiModifier.PROTECTED) + ); + ourMethodIncompatibleModifiers.put( + PsiModifier.PROTECTED, + Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PRIVATE) + ); + ourMethodIncompatibleModifiers.put(PsiModifier.STATIC, Set.of(PsiModifier.ABSTRACT, PsiModifier.DEFAULT)); + ourMethodIncompatibleModifiers + .put(PsiModifier.DEFAULT, Set.of(PsiModifier.ABSTRACT, PsiModifier.STATIC, PsiModifier.PRIVATE)); + ourMethodIncompatibleModifiers.put(PsiModifier.SYNCHRONIZED, Set.of(PsiModifier.ABSTRACT)); + ourMethodIncompatibleModifiers.put(PsiModifier.STRICTFP, Set.of(PsiModifier.ABSTRACT)); + ourMethodIncompatibleModifiers.put(PsiModifier.FINAL, Set.of(PsiModifier.ABSTRACT)); + + ourFieldIncompatibleModifiers.put(PsiModifier.FINAL, Set.of(PsiModifier.VOLATILE)); + ourFieldIncompatibleModifiers.put( + PsiModifier.PACKAGE_LOCAL, + Set.of(PsiModifier.PRIVATE, PsiModifier.PUBLIC, PsiModifier.PROTECTED) + ); + ourFieldIncompatibleModifiers.put( + PsiModifier.PRIVATE, + Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PROTECTED) + ); + ourFieldIncompatibleModifiers.put( + PsiModifier.PUBLIC, + Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE, PsiModifier.PROTECTED) + ); + ourFieldIncompatibleModifiers.put( + PsiModifier.PROTECTED, + Set.of(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PRIVATE) + ); + ourFieldIncompatibleModifiers.put(PsiModifier.STATIC, Set.of()); + ourFieldIncompatibleModifiers.put(PsiModifier.TRANSIENT, Set.of()); + ourFieldIncompatibleModifiers.put(PsiModifier.VOLATILE, Set.of(PsiModifier.FINAL)); + + ourClassInitializerIncompatibleModifiers.put(PsiModifier.STATIC, Set.of()); + + ourModuleIncompatibleModifiers.put(PsiModifier.OPEN, Set.of()); + + ourRequiresIncompatibleModifiers.put(PsiModifier.STATIC, Set.of()); + ourRequiresIncompatibleModifiers.put(PsiModifier.TRANSITIVE, Set.of()); + } + + private HighlightUtil() { + } + + @Nullable + private static String getIncompatibleModifier( + String modifier, + @Nullable PsiModifierList modifierList, + @Nonnull Map> incompatibleModifiersHash + ) { + if (modifierList == null) { + return null; + } + + // modifier is always incompatible with itself + PsiElement[] modifiers = modifierList.getChildren(); + int modifierCount = 0; + for (PsiElement otherModifier : modifiers) { + if (Comparing.equal(modifier, otherModifier.getText(), true)) { + modifierCount++; + } + } + if (modifierCount > 1) { + return modifier; + } + + Set incompatibles = incompatibleModifiersHash.get(modifier); + if (incompatibles == null) { + return null; + } + final PsiElement parent = modifierList.getParent(); + final boolean level8OrHigher = PsiUtil.isLanguageLevel8OrHigher(modifierList); + final boolean level9OrHigher = PsiUtil.isLanguageLevel9OrHigher(modifierList); + for (@PsiModifier.ModifierConstant String incompatible : incompatibles) { + if (level8OrHigher) { + if (modifier.equals(PsiModifier.STATIC) && incompatible.equals(PsiModifier.ABSTRACT)) { + continue; + } + } + if (parent instanceof PsiMethod) { + if (level9OrHigher && modifier.equals(PsiModifier.PRIVATE) && incompatible.equals(PsiModifier.PUBLIC)) { + continue; + } + + if (modifier.equals(PsiModifier.STATIC) && incompatible.equals(PsiModifier.FINAL)) { + final PsiClass containingClass = ((PsiMethod)parent).getContainingClass(); + if (containingClass == null || !containingClass.isInterface()) { + continue; + } + } + } + if (modifierList.hasModifierProperty(incompatible)) { + return incompatible; + } + if (PsiModifier.ABSTRACT.equals(incompatible) && modifierList.hasExplicitModifier(incompatible)) { + return incompatible; + } + } + + return null; + } + + /** + * make element protected/package-private/public suggestion + * for private method in the interface it should add default modifier as well + */ + public static void registerAccessQuickFixAction( + @Nonnull PsiMember refElement, + @Nonnull PsiJavaCodeReferenceElement place, + @Nullable HighlightInfo errorResult, + final PsiElement fileResolveScope + ) { + if (errorResult == null) { + return; + } + PsiClass accessObjectClass = null; + PsiElement qualifier = place.getQualifier(); + if (qualifier instanceof PsiExpression) { + accessObjectClass = (PsiClass)PsiUtil.getAccessObjectClass((PsiExpression)qualifier).getElement(); + } + registerReplaceInaccessibleFieldWithGetterSetterFix(refElement, place, accessObjectClass, errorResult); + + if (refElement instanceof PsiCompiledElement) { + return; + } + PsiModifierList modifierList = refElement.getModifierList(); + if (modifierList == null) { + return; + } + + PsiClass packageLocalClassInTheMiddle = getPackageLocalClassInTheMiddle(place); + if (packageLocalClassInTheMiddle != null) { + IntentionAction fix = + QuickFixFactory.getInstance().createModifierListFix(packageLocalClassInTheMiddle, PsiModifier.PUBLIC, true, true); + QuickFixAction.registerQuickFixAction(errorResult, fix); + return; + } + + try { + Project project = refElement.getProject(); + JavaPsiFacade facade = JavaPsiFacade.getInstance(project); + PsiModifierList modifierListCopy = facade.getElementFactory().createFieldFromText("int a;", null).getModifierList(); + modifierListCopy.setModifierProperty(PsiModifier.STATIC, modifierList.hasModifierProperty(PsiModifier.STATIC)); + String minModifier = PsiModifier.PACKAGE_LOCAL; + if (refElement.hasModifierProperty(PsiModifier.PACKAGE_LOCAL)) { + minModifier = PsiModifier.PROTECTED; + } + if (refElement.hasModifierProperty(PsiModifier.PROTECTED)) { + minModifier = PsiModifier.PUBLIC; + } + PsiClass containingClass = refElement.getContainingClass(); + if (containingClass != null && containingClass.isInterface()) { + minModifier = PsiModifier.PUBLIC; + } + String[] modifiers = { + PsiModifier.PACKAGE_LOCAL, + PsiModifier.PROTECTED, + PsiModifier.PUBLIC, + }; + for (int i = ArrayUtil.indexOf(modifiers, minModifier); i < modifiers.length; i++) { + @PsiModifier.ModifierConstant String modifier = modifiers[i]; + modifierListCopy.setModifierProperty(modifier, true); + if (facade.getResolveHelper().isAccessible(refElement, modifierListCopy, place, accessObjectClass, fileResolveScope)) { + IntentionAction fix = QuickFixFactory.getInstance().createModifierListFix(refElement, modifier, true, true); + TextRange fixRange = new TextRange(errorResult.getStartOffset(), errorResult.getEndOffset()); + PsiElement ref = place.getReferenceNameElement(); + if (ref != null) { + fixRange = fixRange.union(ref.getTextRange()); + } + QuickFixAction.registerQuickFixAction(errorResult, fixRange, fix); + } + } + } + catch (IncorrectOperationException e) { + LOG.error(e); + } + } + + @Nullable + private static PsiClass getPackageLocalClassInTheMiddle(@Nonnull PsiElement place) { + if (place instanceof PsiReferenceExpression) { + // check for package-private classes in the middle + PsiReferenceExpression expression = (PsiReferenceExpression)place; + while (true) { + PsiElement resolved = expression.resolve(); + if (resolved instanceof PsiField) { + PsiField field = (PsiField)resolved; + PsiClass aClass = field.getContainingClass(); + if (aClass != null && aClass.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) && !JavaPsiFacade.getInstance(aClass.getProject()) + .arePackagesTheSame(aClass, place)) { + + return aClass; + } + } + PsiExpression qualifier = expression.getQualifierExpression(); + if (!(qualifier instanceof PsiReferenceExpression)) { + break; + } + expression = (PsiReferenceExpression)qualifier; + } + } + return null; + } + + + @Nullable + public static HighlightInfo checkInstanceOfApplicable(@Nonnull PsiInstanceOfExpression expression) { + PsiExpression operand = expression.getOperand(); + PsiTypeElement typeElement = expression.getCheckType(); + if (typeElement == null) { + return null; + } + PsiType checkType = typeElement.getType(); + PsiType operandType = operand.getType(); + if (operandType == null) { + return null; + } + if (TypeConversionUtil.isPrimitiveAndNotNull(operandType) || TypeConversionUtil.isPrimitiveAndNotNull(checkType) || !TypeConversionUtil.areTypesConvertible( + operandType, + checkType + )) { + String message = JavaErrorBundle.message( + "inconvertible.type.cast", + JavaHighlightUtil.formatType(operandType), + JavaHighlightUtil.formatType(checkType) + ); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); + } + return null; + } + + + /** + * 15.16 Cast Expressions + * ( ReferenceType {AdditionalBound} ) expression, where AdditionalBound: & InterfaceType then all must be true + * - ReferenceType must denote a class or interface type. + * - The erasures of all the listed types must be pairwise different. + * - No two listed types may be subtypes of different parameterization of the same generic interface. + */ + @Nullable + public static HighlightInfo checkIntersectionInTypeCast( + @Nonnull PsiTypeCastExpression expression, + @Nonnull LanguageLevel languageLevel, + @Nonnull PsiFile file + ) { + PsiTypeElement castTypeElement = expression.getCastType(); + if (castTypeElement != null && isIntersection(castTypeElement, castTypeElement.getType())) { + HighlightInfo info = checkFeature(expression, JavaFeature.INTERSECTION_CASTS, languageLevel, file); + if (info != null) { + return info; + } + + final PsiTypeElement[] conjuncts = PsiTreeUtil.getChildrenOfType(castTypeElement, PsiTypeElement.class); + if (conjuncts != null) { + final Set erasures = new HashSet<>(conjuncts.length); + erasures.add(TypeConversionUtil.erasure(conjuncts[0].getType())); + final List conjList = new ArrayList<>(Arrays.asList(conjuncts)); + for (int i = 1; i < conjuncts.length; i++) { + final PsiTypeElement conjunct = conjuncts[i]; + final PsiType conjType = conjunct.getType(); + if (conjType instanceof PsiClassType) { + final PsiClass aClass = ((PsiClassType)conjType).resolve(); + if (aClass != null && !aClass.isInterface()) { + final HighlightInfo errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) .range(conjunct) - .descriptionAndTooltip("Unexpected type: class is expected") + .descriptionAndTooltip(JavaErrorBundle.message("interface.expected")) .create(); - } - if (!erasures.add(TypeConversionUtil.erasure(conjType))) { - final HighlightInfo highlightInfo = - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(conjunct).descriptionAndTooltip("Repeated interface").create(); - QuickFixAction.registerQuickFixAction(highlightInfo, new DeleteRepeatedInterfaceFix(conjunct, conjList), null); + QuickFixAction.registerQuickFixAction( + errorResult, + new FlipIntersectionSidesFix(aClass.getName(), conjList, conjunct, castTypeElement), + null + ); + return errorResult; + } + } + else { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(conjunct) + .descriptionAndTooltip("Unexpected type: class is expected") + .create(); + } + if (!erasures.add(TypeConversionUtil.erasure(conjType))) { + final HighlightInfo highlightInfo = + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(conjunct) + .descriptionAndTooltip("Repeated interface") + .create(); + QuickFixAction.registerQuickFixAction(highlightInfo, new DeleteRepeatedInterfaceFix(conjunct, conjList), null); + return highlightInfo; + } + } + + final List typeList = ContainerUtil.map(conjList, PsiTypeElement::getType); + final Ref differentArgumentsMessage = new Ref<>(); + final PsiClass sameGenericParameterization = InferenceSession.findParameterizationOfTheSameGenericClass(typeList, pair -> + { + if (!TypesDistinctProver.provablyDistinct(pair.first, pair.second)) { + return true; + } + differentArgumentsMessage.set(pair.first.getPresentableText() + " and " + pair.second.getPresentableText()); + return false; + }); + if (sameGenericParameterization != null) { + final String message = + formatClass(sameGenericParameterization) + " cannot be inherited with different arguments: " + differentArgumentsMessage.get(); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(message) + .create(); + } + } + } + + return null; + } + + private static boolean isIntersection(PsiTypeElement castTypeElement, PsiType castType) { + if (castType instanceof PsiIntersectionType) { + return true; + } + return castType instanceof PsiClassType && PsiTreeUtil.getChildrenOfType(castTypeElement, PsiTypeElement.class) != null; + } + + @Nullable + public static HighlightInfo checkInconvertibleTypeCast(@Nonnull PsiTypeCastExpression expression) { + final PsiTypeElement castTypeElement = expression.getCastType(); + if (castTypeElement == null) { + return null; + } + PsiType castType = castTypeElement.getType(); + + PsiExpression operand = expression.getOperand(); + if (operand == null) { + return null; + } + PsiType operandType = operand.getType(); + + if (operandType != null + && !TypeConversionUtil.areTypesConvertible(operandType, castType, PsiUtil.getLanguageLevel(expression)) + && !RedundantCastUtil.isInPolymorphicCall(expression)) { + String message = JavaErrorBundle.message( + "inconvertible.type.cast", + JavaHighlightUtil.formatType(operandType), + JavaHighlightUtil.formatType(castType) + ); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); + } + + + return null; + } + + @Nullable + public static HighlightInfo checkVariableExpected(@Nonnull PsiExpression expression) { + PsiExpression lValue; + if (expression instanceof PsiAssignmentExpression) { + PsiAssignmentExpression assignment = (PsiAssignmentExpression)expression; + lValue = assignment.getLExpression(); + } + else if (PsiUtil.isIncrementDecrementOperation(expression)) { + lValue = + expression instanceof PsiPostfixExpression ? ((PsiPostfixExpression)expression).getOperand() : ((PsiPrefixExpression)expression).getOperand(); + } + else { + lValue = null; + } + HighlightInfo errorResult = null; + if (lValue != null && !TypeConversionUtil.isLValue(lValue)) { + String description = JavaErrorBundle.message("variable.expected"); + errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(lValue).descriptionAndTooltip(description).create(); + } + + return errorResult; + } + + + @Nullable + public static HighlightInfo checkAssignmentOperatorApplicable(@Nonnull PsiAssignmentExpression assignment) { + PsiJavaToken operationSign = assignment.getOperationSign(); + IElementType eqOpSign = operationSign.getTokenType(); + IElementType opSign = TypeConversionUtil.convertEQtoOperation(eqOpSign); + if (opSign == null) { + return null; + } + final PsiType lType = assignment.getLExpression().getType(); + final PsiExpression rExpression = assignment.getRExpression(); + if (rExpression == null) { + return null; + } + final PsiType rType = rExpression.getType(); + HighlightInfo errorResult = null; + if (!TypeConversionUtil.isBinaryOperatorApplicable(opSign, lType, rType, true)) { + String operatorText = operationSign.getText().substring(0, operationSign.getText().length() - 1); + String message = JavaErrorBundle.message( + "binary.operator.not.applicable", + operatorText, + JavaHighlightUtil.formatType(lType), + JavaHighlightUtil.formatType(rType) + ); + + errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(assignment).descriptionAndTooltip(message).create(); + } + return errorResult; + } + + + @Nullable + public static HighlightInfo checkAssignmentCompatibleTypes(@Nonnull PsiAssignmentExpression assignment) { + PsiExpression lExpr = assignment.getLExpression(); + PsiExpression rExpr = assignment.getRExpression(); + if (rExpr == null) { + return null; + } + PsiType lType = lExpr.getType(); + PsiType rType = rExpr.getType(); + if (rType == null) { + return null; + } + + final IElementType sign = assignment.getOperationTokenType(); + HighlightInfo highlightInfo; + if (JavaTokenType.EQ.equals(sign)) { + highlightInfo = checkAssignability(lType, rType, rExpr, assignment); + } + else { + // 15.26.2. Compound Assignment Operators + final IElementType opSign = TypeConversionUtil.convertEQtoOperation(sign); + final PsiType type = TypeConversionUtil.calcTypeForBinaryExpression(lType, rType, opSign, true); + if (type == null || lType == null || TypeConversionUtil.areTypesConvertible(type, lType)) { + return null; + } + highlightInfo = createIncompatibleTypeHighlightInfo(lType, type, assignment.getTextRange(), 0); + QuickFixAction.registerQuickFixAction( + highlightInfo, + QuickFixFactory.getInstance().createChangeToAppendFix(sign, lType, assignment) + ); + } + if (highlightInfo == null) { + return null; + } + registerChangeVariableTypeFixes(lExpr, rType, rExpr, highlightInfo); + if (lType != null) { + registerChangeVariableTypeFixes(rExpr, lType, lExpr, highlightInfo); + } + return highlightInfo; + } + + private static void registerChangeVariableTypeFixes( + @Nonnull PsiExpression expression, + @Nonnull PsiType type, + @Nullable final PsiExpression lExpr, + @Nullable HighlightInfo highlightInfo + ) { + if (highlightInfo == null || !(expression instanceof PsiReferenceExpression)) { + return; + } + + final PsiElement element = ((PsiReferenceExpression)expression).resolve(); + if (!(element instanceof PsiVariable)) { + return; + } + + registerChangeVariableTypeFixes((PsiVariable)element, type, lExpr, highlightInfo); + + if (lExpr instanceof PsiMethodCallExpression && lExpr.getParent() instanceof PsiAssignmentExpression) { + final PsiElement parent = lExpr.getParent(); + if (parent.getParent() instanceof PsiStatement) { + final PsiMethod method = ((PsiMethodCallExpression)lExpr).resolveMethod(); + if (method != null && PsiType.VOID.equals(method.getReturnType())) { + QuickFixAction.registerQuickFixAction( + highlightInfo, + new ReplaceAssignmentFromVoidWithStatementIntentionAction(parent, lExpr) + ); + } + } + } + } + + private static boolean isCastIntentionApplicable(@Nonnull PsiExpression expression, @Nullable PsiType toType) { + while (expression instanceof PsiTypeCastExpression || expression instanceof PsiParenthesizedExpression) { + if (expression instanceof PsiTypeCastExpression) { + expression = ((PsiTypeCastExpression)expression).getOperand(); + } + if (expression instanceof PsiParenthesizedExpression) { + expression = ((PsiParenthesizedExpression)expression).getExpression(); + } + } + if (expression == null) { + return false; + } + PsiType rType = expression.getType(); + return rType != null && toType != null && TypeConversionUtil.areTypesConvertible(rType, toType); + } + + + @Nullable + public static HighlightInfo checkVariableInitializerType(@Nonnull PsiVariable variable) { + PsiExpression initializer = variable.getInitializer(); + // array initializer checked in checkArrayInitializerApplicable + if (initializer == null || initializer instanceof PsiArrayInitializerExpression) { + return null; + } + PsiType lType = variable.getType(); + PsiType rType = initializer.getType(); + PsiTypeElement typeElement = variable.getTypeElement(); + int start = typeElement != null ? typeElement.getTextRange().getStartOffset() : variable.getTextRange().getStartOffset(); + int end = variable.getTextRange().getEndOffset(); + HighlightInfo highlightInfo = checkAssignability(lType, rType, initializer, new TextRange(start, end), 0); + if (highlightInfo != null) { + registerChangeVariableTypeFixes(variable, rType, variable.getInitializer(), highlightInfo); + registerChangeVariableTypeFixes(initializer, lType, null, highlightInfo); + } + return highlightInfo; + } + + @Nullable + public static HighlightInfo checkAssignability( + @Nullable PsiType lType, + @Nullable PsiType rType, + @Nullable PsiExpression expression, + @Nonnull PsiElement elementToHighlight + ) { + TextRange textRange = elementToHighlight.getTextRange(); + return checkAssignability(lType, rType, expression, textRange, 0); + } + + @Nullable + private static HighlightInfo checkAssignability( + @Nullable PsiType lType, + @Nullable PsiType rType, + @Nullable PsiExpression expression, + @Nonnull TextRange textRange, + int navigationShift + ) { + if (lType == rType) { + return null; + } + if (expression == null) { + if (rType == null || lType == null || TypeConversionUtil.isAssignable(lType, rType)) { + return null; + } + } + else if (TypeConversionUtil.areTypesAssignmentCompatible(lType, expression)) { + return null; + } + if (rType == null) { + rType = expression.getType(); + } + HighlightInfo highlightInfo = createIncompatibleTypeHighlightInfo(lType, rType, textRange, navigationShift); + if (rType != null && expression != null && isCastIntentionApplicable(expression, lType)) { + QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createAddTypeCastFix(lType, expression)); + } + if (expression != null) { + QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createWrapWithAdapterFix(lType, expression)); + QuickFixAction.registerQuickFixAction( + highlightInfo, + QuickFixFactory.getInstance().createWrapWithOptionalFix(lType, expression) + ); + QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createWrapExpressionFix(lType, expression)); + QuickFixAction.registerQuickFixAction( + highlightInfo, + QuickFixFactory.getInstance().createWrapStringWithFileFix(lType, expression) + ); + AddTypeArgumentsConditionalFix.register(highlightInfo, expression, lType); + registerCollectionToArrayFixAction(highlightInfo, rType, lType, expression); + } + ChangeNewOperatorTypeFix.register(highlightInfo, expression, lType); + return highlightInfo; + } + + + @Nullable + public static HighlightInfo checkReturnStatementType(@Nonnull PsiReturnStatement statement) { + PsiMethod method = null; + PsiLambdaExpression lambda = null; + PsiElement parent = statement.getParent(); + while (true) { + if (parent instanceof PsiFile) { + break; + } + if (parent instanceof PsiClassInitializer) { + break; + } + if (parent instanceof PsiLambdaExpression) { + lambda = (PsiLambdaExpression)parent; + break; + } + if (parent instanceof PsiMethod) { + method = (PsiMethod)parent; + break; + } + parent = parent.getParent(); + } + if (parent instanceof PsiCodeFragment) { + return null; + } + String description; + HighlightInfo errorResult = null; + if (method == null && lambda != null) { + //todo check return statements type inside lambda + } + else if (method == null && !(parent instanceof ServerPageFile)) { + description = JavaErrorBundle.message("return.outside.method"); + errorResult = + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); + } + else { + PsiType returnType = method != null ? method.getReturnType() : null/*JSP page returns void*/; + boolean isMethodVoid = returnType == null || PsiType.VOID.equals(returnType); + final PsiExpression returnValue = statement.getReturnValue(); + if (returnValue != null) { + PsiType valueType = RefactoringChangeUtil.getTypeByExpression(returnValue); + if (isMethodVoid) { + description = JavaErrorBundle.message("return.from.void.method"); + errorResult = + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(statement) + .descriptionAndTooltip(description) + .create(); + if (valueType != null) { + QuickFixAction.registerQuickFixAction( + errorResult, + QuickFixFactory.getInstance().createMethodReturnFix(method, valueType, true) + ); + } + } + else { + TextRange textRange = statement.getTextRange(); + errorResult = checkAssignability(returnType, valueType, returnValue, textRange, returnValue.getStartOffsetInParent()); + if (errorResult != null && valueType != null) { + if (!PsiType.VOID.equals(valueType)) { + QuickFixAction.registerQuickFixAction( + errorResult, + QuickFixFactory.getInstance().createMethodReturnFix(method, valueType, true) + ); + } + registerChangeParameterClassFix(returnType, valueType, errorResult); + if (returnType instanceof PsiArrayType) { + final PsiType erasedValueType = TypeConversionUtil.erasure(valueType); + if (erasedValueType != null + && TypeConversionUtil.isAssignable(((PsiArrayType)returnType).getComponentType(), erasedValueType)) { + QuickFixAction.registerQuickFixAction(errorResult, QuickFixFactory.getInstance() + .createSurroundWithArrayFix(null, returnValue)); + } + } + registerCollectionToArrayFixAction(errorResult, valueType, returnType, returnValue); + } + } + } + else { + if (!isMethodVoid) { + description = JavaErrorBundle.message("missing.return.value"); + errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(statement) + .descriptionAndTooltip(description) + .navigationShift(PsiKeyword.RETURN.length()) + .create(); + QuickFixAction.registerQuickFixAction( + errorResult, + QuickFixFactory.getInstance().createMethodReturnFix(method, PsiType.VOID, true) + ); + } + } + } + return errorResult; + } + + private static void registerCollectionToArrayFixAction( + @Nullable HighlightInfo info, + @Nullable PsiType fromType, + @Nullable PsiType toType, + @Nonnull PsiExpression expression + ) { + if (toType instanceof PsiArrayType) { + PsiType arrayComponentType = ((PsiArrayType)toType).getComponentType(); + if (!(arrayComponentType instanceof PsiPrimitiveType) + && !(PsiUtil.resolveClassInType(arrayComponentType) instanceof PsiTypeParameter) + && InheritanceUtil.isInheritor(fromType, JavaClassNames.JAVA_UTIL_COLLECTION)) { + PsiType collectionItemType = JavaGenericsUtil.getCollectionItemType(fromType, expression.getResolveScope()); + if (collectionItemType != null && arrayComponentType.isAssignableFrom(collectionItemType)) { + QuickFixAction.registerQuickFixAction( + info, + QuickFixFactory.getInstance().createCollectionToArrayFix(expression, (PsiArrayType)toType) + ); + } + } + } + } + + @Nonnull + public static String getUnhandledExceptionsDescriptor(@Nonnull final Collection unhandled) { + return getUnhandledExceptionsDescriptor(unhandled, null); + } + + @Nonnull + private static String getUnhandledExceptionsDescriptor( + @Nonnull final Collection unhandled, + @Nullable final String source + ) { + final String exceptions = formatTypes(unhandled); + return source != null + ? JavaErrorBundle.message("unhandled.close.exceptions", exceptions, unhandled.size(), source) + : JavaErrorBundle.message("unhandled.exceptions", exceptions, unhandled.size()); + } + + @Nonnull + private static String formatTypes(@Nonnull Collection unhandled) { + return StringUtil.join(unhandled, JavaHighlightUtil::formatType, ", "); + } + + @Nullable + @RequiredReadAction + public static HighlightInfo checkVariableAlreadyDefined(@Nonnull PsiVariable variable) { + PsiVariable oldVariable = JavaPsiVariableUtil.findPreviousVariableDeclaration(variable); + if (oldVariable != null) { + String description = JavaErrorLocalize.variableAlreadyDefined(variable.getName()).get(); + PsiIdentifier identifier = variable.getNameIdentifier(); + assert identifier != null : variable; + HighlightInfo highlightInfo = + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(identifier).descriptionAndTooltip(description).create(); + if (variable instanceof PsiLocalVariable) { + QuickFixAction.registerQuickFixAction( + highlightInfo, + QuickFixFactory.getInstance().createReuseVariableDeclarationFix((PsiLocalVariable)variable) + ); + } + return highlightInfo; + } + return null; + } + + @Nullable + public static HighlightInfo checkUnderscore(@Nonnull PsiIdentifier identifier, @Nonnull LanguageLevel languageLevel) { + if ("_".equals(identifier.getText())) { + if (languageLevel.isAtLeast(LanguageLevel.JDK_1_9)) { + String text = JavaErrorBundle.message("underscore.identifier.error"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(identifier).descriptionAndTooltip(text).create(); + } + else if (languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) { + PsiElement parent = identifier.getParent(); + if (parent instanceof PsiParameter && ((PsiParameter)parent).getDeclarationScope() instanceof PsiLambdaExpression) { + String text = JavaErrorBundle.message("underscore.lambda.identifier"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(identifier).descriptionAndTooltip(text).create(); + } + } + } + + return null; + } + + @Nonnull + public static String formatClass(@Nonnull PsiClass aClass) { + return formatClass(aClass, true); + } + + @Nonnull + public static String formatClass(@Nonnull PsiClass aClass, boolean fqn) { + return PsiFormatUtil.formatClass( + aClass, + PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_ANONYMOUS_CLASS_VERBOSE | (fqn ? PsiFormatUtilBase.SHOW_FQ_NAME : 0) + ); + } + + @Nonnull + private static String formatField(@Nonnull PsiField field) { + return PsiFormatUtil.formatVariable( + field, + PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME, + PsiSubstitutor.EMPTY + ); + } + + @Nullable + public static HighlightInfo checkUnhandledExceptions(@Nonnull final PsiElement element, @Nullable TextRange textRange) { + final List unhandledExceptions = ExceptionUtil.getUnhandledExceptions(element); + if (unhandledExceptions.isEmpty()) { + return null; + } + + final HighlightInfoType highlightType = getUnhandledExceptionHighlightType(element); + if (highlightType == null) { + return null; + } + + if (textRange == null) { + textRange = element.getTextRange(); + } + final String description = getUnhandledExceptionsDescriptor(unhandledExceptions); + HighlightInfo errorResult = + HighlightInfo.newHighlightInfo(highlightType).range(textRange).descriptionAndTooltip(description).create(); + registerUnhandledExceptionFixes(element, errorResult, unhandledExceptions); + return errorResult; + } + + @Nullable + public static HighlightInfo checkUnhandledCloserExceptions(@Nonnull PsiResourceListElement resource) { + List unhandled = ExceptionUtil.getUnhandledCloserExceptions(resource, null); + if (unhandled.isEmpty()) { + return null; + } + + HighlightInfoType highlightType = getUnhandledExceptionHighlightType(resource); + if (highlightType == null) { + return null; + } + + String description = getUnhandledExceptionsDescriptor(unhandled, "auto-closeable resource"); + HighlightInfo highlight = HighlightInfo.newHighlightInfo(highlightType).range(resource).descriptionAndTooltip(description).create(); + registerUnhandledExceptionFixes(resource, highlight, unhandled); + return highlight; + } + + private static void registerUnhandledExceptionFixes(PsiElement element, HighlightInfo errorResult, List unhandled) { + QuickFixAction.registerQuickFixAction(errorResult, QuickFixFactory.getInstance().createAddExceptionToCatchFix()); + QuickFixAction.registerQuickFixAction(errorResult, QuickFixFactory.getInstance().createAddExceptionToThrowsFix(element)); + QuickFixAction.registerQuickFixAction( + errorResult, + QuickFixFactory.getInstance().createAddExceptionFromFieldInitializerToConstructorThrowsFix(element) + ); + QuickFixAction.registerQuickFixAction(errorResult, QuickFixFactory.getInstance().createSurroundWithTryCatchFix(element)); + if (unhandled.size() == 1) { + QuickFixAction.registerQuickFixAction( + errorResult, + QuickFixFactory.getInstance().createGeneralizeCatchFix(element, unhandled.get(0)) + ); + } + } + + @Nullable + private static HighlightInfoType getUnhandledExceptionHighlightType(PsiElement element) { + // JSP top level errors are handled by UnhandledExceptionInJSP inspection + if (FileTypeUtils.isInServerPageFile(element)) { + PsiMethod targetMethod = PsiTreeUtil.getParentOfType(element, PsiMethod.class, true, PsiLambdaExpression.class); + if (targetMethod instanceof SyntheticElement) { + return null; + } + } + + return HighlightInfoType.UNHANDLED_EXCEPTION; + } + + @Nullable + public static HighlightInfo checkBreakOutsideLoop(@Nonnull PsiBreakStatement statement) { + if (statement.getLabelIdentifier() == null) { + if (new PsiMatcherImpl(statement).ancestor(EnclosingLoopOrSwitchMatcherExpression.INSTANCE).getElement() == null) { + String description = JavaErrorBundle.message("break.outside.switch.or.loop"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); + } + } + else { + // todo labeled + } + return null; + } + + + @Nullable + public static HighlightInfo checkContinueOutsideLoop(@Nonnull PsiContinueStatement statement) { + if (statement.getLabelIdentifier() == null) { + if (new PsiMatcherImpl(statement).ancestor(EnclosingLoopMatcherExpression.INSTANCE).getElement() == null) { + String description = JavaErrorBundle.message("continue.outside.loop"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); + } + } + else { + PsiStatement exitedStatement = statement.findContinuedStatement(); + if (exitedStatement == null) { + return null; + } + if (!(exitedStatement instanceof PsiForStatement) && !(exitedStatement instanceof PsiWhileStatement) && !(exitedStatement instanceof PsiDoWhileStatement) && !(exitedStatement instanceof + PsiForeachStatement)) { + String description = JavaErrorBundle.message("not.loop.label", statement.getLabelIdentifier().getText()); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); + } + } + return null; + } + + @Nullable + public static HighlightInfo checkIllegalModifierCombination(@Nonnull PsiKeyword keyword, @Nonnull PsiModifierList modifierList) { + @PsiModifier.ModifierConstant String modifier = keyword.getText(); + String incompatible = getIncompatibleModifier(modifier, modifierList); + if (incompatible != null) { + String message = JavaErrorBundle.message("incompatible.modifiers", modifier, incompatible); + HighlightInfo highlightInfo = + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(keyword).descriptionAndTooltip(message).create(); + QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance() + .createModifierListFix(modifierList, modifier, false, false)); return highlightInfo; - } } - final List typeList = ContainerUtil.map(conjList, PsiTypeElement::getType); - final Ref differentArgumentsMessage = new Ref<>(); - final PsiClass sameGenericParameterization = InferenceSession.findParameterizationOfTheSameGenericClass(typeList, pair -> - { - if (!TypesDistinctProver.provablyDistinct(pair.first, pair.second)) { + return null; + } + + @Contract("null -> null") + private static Map> getIncompatibleModifierMap(@Nullable PsiElement modifierListOwner) { + if (modifierListOwner == null || PsiUtilCore.hasErrorElementChild(modifierListOwner)) { + return null; + } + if (modifierListOwner instanceof PsiClass) { + return ((PsiClass)modifierListOwner).isInterface() ? ourInterfaceIncompatibleModifiers : ourClassIncompatibleModifiers; + } + if (modifierListOwner instanceof PsiMethod) { + return ourMethodIncompatibleModifiers; + } + if (modifierListOwner instanceof PsiVariable) { + return ourFieldIncompatibleModifiers; + } + if (modifierListOwner instanceof PsiClassInitializer) { + return ourClassInitializerIncompatibleModifiers; + } + if (modifierListOwner instanceof PsiJavaModule) { + return ourModuleIncompatibleModifiers; + } + if (modifierListOwner instanceof PsiRequiresStatement) { + return ourRequiresIncompatibleModifiers; + } + return null; + } + + @Nullable + public static String getIncompatibleModifier(String modifier, @Nonnull PsiModifierList modifierList) { + Map> incompatibleModifierMap = getIncompatibleModifierMap(modifierList.getParent()); + return incompatibleModifierMap != null ? getIncompatibleModifier(modifier, modifierList, incompatibleModifierMap) : null; + } + + @Nullable + public static HighlightInfo checkNotAllowedModifier(@Nonnull PsiKeyword keyword, @Nonnull PsiModifierList modifierList) { + PsiElement modifierOwner = modifierList.getParent(); + Map> incompatibleModifierMap = getIncompatibleModifierMap(modifierOwner); + if (incompatibleModifierMap == null) { + return null; + } + + @PsiModifier.ModifierConstant String modifier = keyword.getText(); + Set incompatibles = incompatibleModifierMap.get(modifier); + PsiElement modifierOwnerParent = + modifierOwner instanceof PsiMember ? ((PsiMember)modifierOwner).getContainingClass() : modifierOwner.getParent(); + if (modifierOwnerParent == null) { + modifierOwnerParent = modifierOwner.getParent(); + } + boolean isAllowed = true; + if (modifierOwner instanceof PsiClass) { + PsiClass aClass = (PsiClass)modifierOwner; + if (aClass.isInterface()) { + if (PsiModifier.STATIC.equals(modifier) || PsiModifier.PRIVATE.equals(modifier) || PsiModifier.PROTECTED.equals(modifier) || PsiModifier.PACKAGE_LOCAL + .equals(modifier)) { + isAllowed = modifierOwnerParent instanceof PsiClass; + } + } + else { + if (PsiModifier.PUBLIC.equals(modifier)) { + isAllowed = + modifierOwnerParent instanceof PsiJavaFile || modifierOwnerParent instanceof PsiClass && (modifierOwnerParent instanceof PsiSyntheticClass || ((PsiClass) + modifierOwnerParent).getQualifiedName() != null); + } + else if (PsiModifier.STATIC.equals(modifier) + || PsiModifier.PRIVATE.equals(modifier) + || PsiModifier.PROTECTED.equals(modifier) + || PsiModifier.PACKAGE_LOCAL.equals(modifier)) { + isAllowed = modifierOwnerParent instanceof PsiClass && ((PsiClass)modifierOwnerParent).getQualifiedName() != null + || FileTypeUtils.isInServerPageFile(modifierOwnerParent); + } + + if (aClass.isEnum()) { + isAllowed &= !(PsiModifier.FINAL.equals(modifier) || PsiModifier.ABSTRACT.equals(modifier)); + } + + if (aClass.getContainingClass() instanceof PsiAnonymousClass) { + isAllowed &= !(PsiModifier.PRIVATE.equals(modifier) || PsiModifier.PROTECTED.equals(modifier)); + } + } + } + else if (modifierOwner instanceof PsiMethod) { + PsiMethod method = (PsiMethod)modifierOwner; + isAllowed = !(method.isConstructor() && ourConstructorNotAllowedModifiers.contains(modifier)); + PsiClass containingClass = method.getContainingClass(); + if ((method.hasModifierProperty(PsiModifier.PUBLIC) || method.hasModifierProperty(PsiModifier.PROTECTED)) && method.isConstructor() && containingClass != null && containingClass + .isEnum()) { + isAllowed = false; + } + + if (PsiModifier.PRIVATE.equals(modifier)) { + isAllowed &= + modifierOwnerParent instanceof PsiClass && (!((PsiClass)modifierOwnerParent).isInterface() || PsiUtil.isLanguageLevel9OrHigher( + modifierOwner) && !((PsiClass) + modifierOwnerParent).isAnnotationType()); + } + else if (PsiModifier.STRICTFP.equals(modifier)) { + isAllowed &= + modifierOwnerParent instanceof PsiClass && (!((PsiClass)modifierOwnerParent).isInterface() || PsiUtil.isLanguageLevel8OrHigher( + modifierOwner)); + } + else if (PsiModifier.PROTECTED.equals(modifier) + || PsiModifier.TRANSIENT.equals(modifier) + || PsiModifier.SYNCHRONIZED.equals(modifier)) { + isAllowed &= modifierOwnerParent instanceof PsiClass && !((PsiClass)modifierOwnerParent).isInterface(); + } + + if (containingClass != null && containingClass.isInterface()) { + isAllowed &= !PsiModifier.NATIVE.equals(modifier); + } + + if (containingClass != null && containingClass.isAnnotationType()) { + isAllowed &= !PsiModifier.STATIC.equals(modifier); + isAllowed &= !PsiModifier.DEFAULT.equals(modifier); + } + } + else if (modifierOwner instanceof PsiField) { + if (PsiModifier.PRIVATE.equals(modifier) || PsiModifier.PROTECTED.equals(modifier) || PsiModifier.TRANSIENT.equals(modifier) || PsiModifier.STRICTFP + .equals(modifier) || PsiModifier + .SYNCHRONIZED.equals(modifier)) { + isAllowed = modifierOwnerParent instanceof PsiClass && !((PsiClass)modifierOwnerParent).isInterface(); + } + } + else if (modifierOwner instanceof PsiClassInitializer) { + isAllowed = PsiModifier.STATIC.equals(modifier); + } + else if (modifierOwner instanceof PsiLocalVariable || modifierOwner instanceof PsiParameter) { + isAllowed = PsiModifier.FINAL.equals(modifier); + } + else if (modifierOwner instanceof PsiReceiverParameter) { + isAllowed = false; + } + + isAllowed &= incompatibles != null; + if (!isAllowed) { + String message = JavaErrorBundle.message("modifier.not.allowed", modifier); + HighlightInfo highlightInfo = + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(keyword).descriptionAndTooltip(message).create(); + QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance() + .createModifierListFix(modifierList, modifier, false, false)); + return highlightInfo; + } + + return null; + } + + @Nullable + public static HighlightInfo checkLiteralExpressionParsingError( + @Nonnull PsiLiteralExpression expression, + LanguageLevel level, + PsiFile file + ) { + PsiElement literal = expression.getFirstChild(); + assert literal instanceof PsiJavaToken : literal; + IElementType type = ((PsiJavaToken)literal).getTokenType(); + if (type == JavaTokenType.TRUE_KEYWORD || type == JavaTokenType.FALSE_KEYWORD || type == JavaTokenType.NULL_KEYWORD) { + return null; + } + + boolean isInt = ElementType.INTEGER_LITERALS.contains(type); + boolean isFP = ElementType.REAL_LITERALS.contains(type); + String text = isInt || isFP ? StringUtil.toLowerCase(literal.getText()) : literal.getText(); + Object value = expression.getValue(); + + if (file != null) { + if (isFP) { + if (text.startsWith(PsiLiteralUtil.HEX_PREFIX)) { + HighlightInfo info = checkFeature(expression, JavaFeature.HEX_FP_LITERALS, level, file); + if (info != null) { + return info; + } + } + } + if (isInt) { + if (text.startsWith(PsiLiteralUtil.BIN_PREFIX)) { + HighlightInfo info = checkFeature(expression, JavaFeature.BIN_LITERALS, level, file); + if (info != null) { + return info; + } + } + } + if (isInt || isFP) { + if (text.contains("_")) { + HighlightInfo info = checkFeature(expression, JavaFeature.UNDERSCORES, level, file); + if (info != null) { + return info; + } + info = checkUnderscores(expression, text, isInt); + if (info != null) { + return info; + } + } + } + } + + PsiElement parent = expression.getParent(); + if (type == JavaTokenType.INTEGER_LITERAL) { + String cleanText = StringUtil.replace(text, "_", ""); + //literal 2147483648 may appear only as the operand of the unary negation operator -. + if (!(cleanText.equals(PsiLiteralUtil._2_IN_31) && + parent instanceof PsiPrefixExpression && + ((PsiPrefixExpression)parent).getOperationTokenType() == JavaTokenType.MINUS)) { + if (cleanText.equals(PsiLiteralUtil.HEX_PREFIX)) { + String message = JavaErrorBundle.message("hexadecimal.numbers.must.contain.at.least.one.hexadecimal.digit"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(message) + .create(); + } + if (cleanText.equals(PsiLiteralUtil.BIN_PREFIX)) { + String message = JavaErrorBundle.message("binary.numbers.must.contain.at.least.one.hexadecimal.digit"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(message) + .create(); + } + if (value == null || cleanText.equals(PsiLiteralUtil._2_IN_31)) { + String message = JavaErrorBundle.message("integer.number.too.large"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(message) + .create(); + } + } + } + else if (type == JavaTokenType.LONG_LITERAL) { + String cleanText = StringUtil.replace(StringUtil.trimEnd(text, 'l'), "_", ""); + //literal 9223372036854775808L may appear only as the operand of the unary negation operator -. + if (!(cleanText.equals(PsiLiteralUtil._2_IN_63) && + parent instanceof PsiPrefixExpression && + ((PsiPrefixExpression)parent).getOperationTokenType() == JavaTokenType.MINUS)) { + if (cleanText.equals(PsiLiteralUtil.HEX_PREFIX)) { + String message = JavaErrorBundle.message("hexadecimal.numbers.must.contain.at.least.one.hexadecimal.digit"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(message) + .create(); + } + if (cleanText.equals(PsiLiteralUtil.BIN_PREFIX)) { + String message = JavaErrorBundle.message("binary.numbers.must.contain.at.least.one.hexadecimal.digit"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(message) + .create(); + } + if (value == null || cleanText.equals(PsiLiteralUtil._2_IN_63)) { + String message = JavaErrorBundle.message("long.number.too.large"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(message) + .create(); + } + } + } + else if (isFP) { + if (value == null) { + String message = JavaErrorBundle.message("malformed.floating.point.literal"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); + } + } + else if (type == JavaTokenType.CHARACTER_LITERAL) { + if (value == null) { + if (!StringUtil.startsWithChar(text, '\'')) { + return null; + } + if (!StringUtil.endsWithChar(text, '\'') || text.length() == 1) { + String message = JavaErrorBundle.message("unclosed.char.literal"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(message) + .create(); + } + text = text.substring(1, text.length() - 1); + + CharSequence chars = CodeInsightUtilCore.parseStringCharacters(text, null); + if (chars == null) { + String message = JavaErrorBundle.message("illegal.escape.character.in.character.literal"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(message) + .create(); + } + int length = chars.length(); + if (length > 1) { + String message = JavaErrorBundle.message("too.many.characters.in.character.literal"); + HighlightInfo info = + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); + QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createConvertToStringLiteralAction()); + return info; + } + else if (length == 0) { + String message = JavaErrorBundle.message("empty.character.literal"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(message) + .create(); + } + } + } + else if (type == JavaTokenType.STRING_LITERAL || type == JavaTokenType.TEXT_BLOCK_LITERAL) { + if (type == JavaTokenType.STRING_LITERAL) { + if (value == null) { + for (PsiElement element = expression.getFirstChild(); element != null; element = element.getNextSibling()) { + if (element instanceof OuterLanguageElement) { + return null; + } + } + + if (!StringUtil.startsWithChar(text, '\"')) { + return null; + } + if (StringUtil.endsWithChar(text, '\"')) { + if (text.length() == 1) { + String message = JavaErrorBundle.message("illegal.line.end.in.string.literal"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(message) + .create(); + } + text = text.substring(1, text.length() - 1); + } + else { + String message = JavaErrorBundle.message("illegal.line.end.in.string.literal"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(message) + .create(); + } + + if (CodeInsightUtilCore.parseStringCharacters(text, null) == null) { + String message = JavaErrorBundle.message("illegal.escape.character.in.string.literal"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(message) + .create(); + } + } + } + else { + if (value == null) { + if (!text.endsWith("\"\"\"")) { + String message = JavaErrorBundle.message("text.block.unclosed"); + int p = expression.getTextRange().getEndOffset(); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(p, p) + .endOfLine() + .descriptionAndTooltip(message) + .create(); + } + else { + StringBuilder chars = new StringBuilder(text.length()); + int[] offsets = new int[text.length() + 1]; + boolean success = CodeInsightUtilCore.parseStringCharacters(text, chars, offsets); + if (!success) { + String message = JavaErrorBundle.message("illegal.escape.character.in.string.literal"); + TextRange textRange = chars.length() < text.length() - 1 + ? new TextRange(offsets[chars.length()], offsets[chars.length() + 1]) + : expression.getTextRange(); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression, textRange) + .descriptionAndTooltip(message).create(); + } + else { + String message = JavaErrorBundle.message("text.block.new.line"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expression) + .descriptionAndTooltip(message) + .create(); + } + } + } + else { + if (file != null && containsUnescaped(text, "\\\n")) { + HighlightInfo info = checkFeature(expression, JavaFeature.TEXT_BLOCK_ESCAPES, level, file); + if (info != null) { + return info; + } + } + } + } + if (file != null && containsUnescaped(text, "\\s")) { + HighlightInfo info = checkFeature(expression, JavaFeature.TEXT_BLOCK_ESCAPES, level, file); + if (info != null) { + return info; + } + } + } + + if (value instanceof Float) { + Float number = (Float)value; + if (number.isInfinite()) { + String message = JavaErrorBundle.message("floating.point.number.too.large"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); + } + if (number.floatValue() == 0 && !TypeConversionUtil.isFPZero(text)) { + String message = JavaErrorBundle.message("floating.point.number.too.small"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); + } + } + else if (value instanceof Double) { + Double number = (Double)value; + if (number.isInfinite()) { + String message = JavaErrorBundle.message("floating.point.number.too.large"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); + } + if (number.doubleValue() == 0 && !TypeConversionUtil.isFPZero(text)) { + String message = JavaErrorBundle.message("floating.point.number.too.small"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); + } + } + + return null; + } + + private static final Pattern FP_LITERAL_PARTS = + Pattern.compile("(?:" + "(?:0x([_\\p{XDigit}]*)\\.?([_\\p{XDigit}]*)p[+-]?([_\\d]*))" + "|" + "(?:([_\\d]*)\\.?([_\\d]*)e?[+-]?([_\\d]*))" + ")" + + "[fd]?"); + + private static boolean containsUnescaped(@Nonnull String text, @Nonnull String subText) { + int start = 0; + while ((start = StringUtil.indexOf(text, subText, start)) != -1) { + int nSlashes = 0; + for (int pos = start - 1; pos >= 0; pos--) { + if (text.charAt(pos) != '\\') { + break; + } + nSlashes++; + } + if (nSlashes % 2 == 0) { + return true; + } + start += subText.length(); + } + return false; + } + + private static HighlightInfo checkUnderscores(@Nonnull PsiElement expression, @Nonnull String text, boolean isInt) { + String[] parts = ArrayUtil.EMPTY_STRING_ARRAY; + + if (isInt) { + int start = 0; + if (text.startsWith(PsiLiteralUtil.HEX_PREFIX) || text.startsWith(PsiLiteralUtil.BIN_PREFIX)) { + start += 2; + } + int end = text.length(); + if (StringUtil.endsWithChar(text, 'l')) { + --end; + } + parts = new String[]{text.substring(start, end)}; + } + else { + Matcher matcher = FP_LITERAL_PARTS.matcher(text); + if (matcher.matches()) { + parts = new String[matcher.groupCount()]; + for (int i = 0; i < matcher.groupCount(); i++) { + parts[i] = matcher.group(i + 1); + } + } + } + + for (String part : parts) { + if (part != null && (StringUtil.startsWithChar(part, '_') || StringUtil.endsWithChar(part, '_'))) { + String message = JavaErrorBundle.message("illegal.underscore"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); + } + } + + return null; + } + + @Nullable + public static HighlightInfo checkMustBeBoolean(@Nonnull PsiExpression expr, PsiType type) { + PsiElement parent = expr.getParent(); + if (parent instanceof PsiIfStatement || parent instanceof PsiWhileStatement || parent instanceof PsiForStatement && expr.equals(((PsiForStatement)parent) + .getCondition()) || parent instanceof + PsiDoWhileStatement && expr.equals(((PsiDoWhileStatement)parent).getCondition())) { + if (expr.getNextSibling() instanceof PsiErrorElement) { + return null; + } + + if (!TypeConversionUtil.isBooleanType(type)) { + final HighlightInfo info = createIncompatibleTypeHighlightInfo(PsiType.BOOLEAN, type, expr.getTextRange(), 0); + if (expr instanceof PsiMethodCallExpression) { + final PsiMethodCallExpression methodCall = (PsiMethodCallExpression)expr; + final PsiMethod method = methodCall.resolveMethod(); + if (method != null && PsiType.VOID.equals(method.getReturnType())) { + QuickFixAction.registerQuickFixAction( + info, + QuickFixFactory.getInstance().createMethodReturnFix(method, PsiType.BOOLEAN, true) + ); + } + } + else if (expr instanceof PsiAssignmentExpression && ((PsiAssignmentExpression)expr).getOperationTokenType() == JavaTokenType.EQ) { + QuickFixAction.registerQuickFixAction( + info, + QuickFixFactory.getInstance().createAssignmentToComparisonFix((PsiAssignmentExpression)expr) + ); + } + return info; + } + } + return null; + } + + + @Nonnull + public static Set collectUnhandledExceptions(@Nonnull final PsiTryStatement statement) { + final Set thrownTypes = new HashSet<>(); + + final PsiCodeBlock tryBlock = statement.getTryBlock(); + if (tryBlock != null) { + thrownTypes.addAll(ExceptionUtil.collectUnhandledExceptions(tryBlock, tryBlock)); + } + + final PsiResourceList resources = statement.getResourceList(); + if (resources != null) { + thrownTypes.addAll(ExceptionUtil.collectUnhandledExceptions(resources, resources)); + } + + return thrownTypes; + } + + @Nonnull + public static List checkExceptionThrownInTry( + @Nonnull final PsiParameter parameter, + @Nonnull final Set thrownTypes + ) { + final PsiElement declarationScope = parameter.getDeclarationScope(); + if (!(declarationScope instanceof PsiCatchSection)) { + return Collections.emptyList(); + } + + final PsiType caughtType = parameter.getType(); + if (caughtType instanceof PsiClassType) { + HighlightInfo info = checkSimpleCatchParameter(parameter, thrownTypes, (PsiClassType)caughtType); + return info == null ? Collections.emptyList() : Collections.singletonList(info); + } + if (caughtType instanceof PsiDisjunctionType) { + return checkMultiCatchParameter(parameter, thrownTypes); + } + + return Collections.emptyList(); + } + + @Nullable + private static HighlightInfo checkSimpleCatchParameter( + @Nonnull final PsiParameter parameter, + @Nonnull final Collection thrownTypes, + @Nonnull final PsiClassType caughtType + ) { + if (ExceptionUtil.isUncheckedExceptionOrSuperclass(caughtType)) { + return null; + } + + for (PsiClassType exceptionType : thrownTypes) { + if (exceptionType.isAssignableFrom(caughtType) || caughtType.isAssignableFrom(exceptionType)) { + return null; + } + } + + final String description = JavaErrorBundle.message("exception.never.thrown.try", JavaHighlightUtil.formatType(caughtType)); + final HighlightInfo errorResult = + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(parameter).descriptionAndTooltip(description).create(); + QuickFixAction.registerQuickFixAction(errorResult, QuickFixFactory.getInstance().createDeleteCatchFix(parameter)); + return errorResult; + } + + @Nonnull + private static List checkMultiCatchParameter( + @Nonnull final PsiParameter parameter, + @Nonnull final Collection thrownTypes + ) { + final List typeElements = PsiUtil.getParameterTypeElements(parameter); + final List highlights = new ArrayList<>(typeElements.size()); + + for (final PsiTypeElement typeElement : typeElements) { + final PsiType catchType = typeElement.getType(); + if (catchType instanceof PsiClassType && ExceptionUtil.isUncheckedExceptionOrSuperclass((PsiClassType)catchType)) { + continue; + } + + boolean used = false; + for (PsiClassType exceptionType : thrownTypes) { + if (exceptionType.isAssignableFrom(catchType) || catchType.isAssignableFrom(exceptionType)) { + used = true; + break; + } + } + if (!used) { + final String description = JavaErrorBundle.message("exception.never.thrown.try", JavaHighlightUtil.formatType(catchType)); + final HighlightInfo highlight = + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(description).create(); + QuickFixAction.registerQuickFixAction(highlight, QuickFixFactory.getInstance().createDeleteMultiCatchFix(typeElement)); + highlights.add(highlight); + } + } + + return highlights; + } + + + @Nonnull + public static Collection checkWithImprovedCatchAnalysis( + @Nonnull PsiParameter parameter, + @Nonnull Collection thrownInTryStatement, + @Nonnull PsiFile containingFile + ) { + final PsiElement scope = parameter.getDeclarationScope(); + if (!(scope instanceof PsiCatchSection)) { + return Collections.emptyList(); + } + + final PsiCatchSection catchSection = (PsiCatchSection)scope; + final PsiCatchSection[] allCatchSections = catchSection.getTryStatement().getCatchSections(); + final int idx = ArrayUtil.find(allCatchSections, catchSection); + if (idx <= 0) { + return Collections.emptyList(); + } + + final Collection thrownTypes = new HashSet<>(thrownInTryStatement); + final PsiManager manager = containingFile.getManager(); + final GlobalSearchScope parameterResolveScope = parameter.getResolveScope(); + thrownTypes.add(PsiType.getJavaLangError(manager, parameterResolveScope)); + thrownTypes.add(PsiType.getJavaLangRuntimeException(manager, parameterResolveScope)); + final Collection result = ContainerUtil.newArrayList(); + + final List parameterTypeElements = PsiUtil.getParameterTypeElements(parameter); + final boolean isMultiCatch = parameterTypeElements.size() > 1; + for (PsiTypeElement catchTypeElement : parameterTypeElements) { + final PsiType catchType = catchTypeElement.getType(); + if (ExceptionUtil.isGeneralExceptionType(catchType)) { + continue; + } + + // collect exceptions which are caught by this type + Collection caught = ContainerUtil.findAll(thrownTypes, catchType::isAssignableFrom); + if (caught.isEmpty()) { + continue; + } + final Collection caughtCopy = new HashSet<>(caught); + + // exclude all which are caught by previous catch sections + for (int i = 0; i < idx; i++) { + final PsiParameter prevCatchParameter = allCatchSections[i].getParameter(); + if (prevCatchParameter == null) { + continue; + } + for (PsiTypeElement prevCatchTypeElement : PsiUtil.getParameterTypeElements(prevCatchParameter)) { + final PsiType prevCatchType = prevCatchTypeElement.getType(); + caught.removeIf(prevCatchType::isAssignableFrom); + if (caught.isEmpty()) { + break; + } + } + } + + // check & warn + if (caught.isEmpty()) { + final String message = JavaErrorBundle.message("exception.already.caught.warn", formatTypes(caughtCopy), caughtCopy.size()); + final HighlightInfo highlightInfo = + HighlightInfo.newHighlightInfo(HighlightInfoType.WARNING).range(catchSection).descriptionAndTooltip(message).create(); + if (isMultiCatch) { + QuickFixAction.registerQuickFixAction( + highlightInfo, + QuickFixFactory.getInstance().createDeleteMultiCatchFix(catchTypeElement) + ); + } + else { + QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createDeleteCatchFix(parameter)); + } + result.add(highlightInfo); + } + } + + return result; + } + + + @Nullable + public static HighlightInfo checkNotAStatement(@Nonnull PsiStatement statement) { + if (!PsiUtil.isStatement(statement) && !PsiUtilCore.hasErrorElementChild(statement)) { + boolean isDeclarationNotAllowed = false; + if (statement instanceof PsiDeclarationStatement) { + final PsiElement parent = statement.getParent(); + isDeclarationNotAllowed = parent instanceof PsiIfStatement || parent instanceof PsiLoopStatement; + } + + String description = JavaErrorBundle.message(isDeclarationNotAllowed ? "declaration.not.allowed" : "not.a.statement"); + HighlightInfo error = + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); + if (statement instanceof PsiExpressionStatement) { + QuickFixAction.registerQuickFixAction( + error, + QuickFixFactory.getInstance() + .createDeleteSideEffectAwareFix((PsiExpressionStatement)statement) + ); + } + return error; + } + return null; + } + + @Nullable + public static HighlightInfo checkSwitchBlockStatements( + @Nonnull PsiSwitchBlock switchBlock, + @Nonnull LanguageLevel languageLevel, + @Nonnull PsiFile file + ) { + PsiCodeBlock body = switchBlock.getBody(); + if (body != null) { + PsiElement first = PsiTreeUtil.skipWhitespacesAndCommentsForward(body.getLBrace()); + if (first != null && !(first instanceof PsiSwitchLabelStatementBase) && !PsiUtil.isJavaToken(first, JavaTokenType.RBRACE)) { + String description = JavaErrorBundle.message("statement.must.be.prepended.with.case.label"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(first).descriptionAndTooltip(description).create(); + } + + PsiElement element = first; + PsiStatement alien = null; + boolean classicLabels = false; + boolean enhancedLabels = false; + boolean levelChecked = false; + while (element != null && !PsiUtil.isJavaToken(element, JavaTokenType.RBRACE)) { + if (element instanceof PsiSwitchLabeledRuleStatement) { + if (!levelChecked) { + HighlightInfo info = checkFeature(element, JavaFeature.ENHANCED_SWITCH, languageLevel, file); + if (info != null) { + return info; + } + levelChecked = true; + } + if (classicLabels) { + alien = (PsiStatement)element; + break; + } + enhancedLabels = true; + } + else if (element instanceof PsiStatement) { + if (enhancedLabels) { + alien = (PsiStatement)element; + break; + } + classicLabels = true; + } + + if (!levelChecked && element instanceof PsiSwitchLabelStatementBase) { + PsiExpressionList values = ((PsiSwitchLabelStatementBase)element).getCaseValues(); + if (values != null && values.getExpressionCount() > 1) { + HighlightInfo info = checkFeature(values, JavaFeature.ENHANCED_SWITCH, languageLevel, file); + if (info != null) { + return info; + } + levelChecked = true; + } + } + + element = PsiTreeUtil.skipWhitespacesAndCommentsForward(element); + } + if (alien != null) { + if (enhancedLabels && !(alien instanceof PsiSwitchLabelStatementBase)) { + PsiSwitchLabeledRuleStatement previousRule = + PsiTreeUtil.getPrevSiblingOfType(alien, PsiSwitchLabeledRuleStatement.class); + String description = JavaErrorBundle.message("statement.must.be.prepended.with.case.label"); + HighlightInfo info = + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(alien).descriptionAndTooltip(description).create(); + if (previousRule != null) { + QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance() + .createWrapSwitchRuleStatementsIntoBlockFix(previousRule)); + } + return info; + } + String description = JavaErrorBundle.message("different.case.kinds.in.switch"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(alien).descriptionAndTooltip(description).create(); + } + } + + return null; + } + + @Nullable + public static HighlightInfo checkSwitchSelectorType(@Nonnull PsiSwitchBlock switchBlock, @Nonnull LanguageLevel level) { + PsiExpression expression = switchBlock.getExpression(); + if (expression == null) { + return null; + } + PsiType type = expression.getType(); + if (type == null) { + return null; + } + + SelectorKind kind = getSwitchSelectorKind(type); + if (kind == SelectorKind.INT) { + return null; + } + + LanguageLevel requiredLevel = null; + if (kind == SelectorKind.ENUM) { + requiredLevel = LanguageLevel.JDK_1_5; + } + if (kind == SelectorKind.STRING) { + requiredLevel = LanguageLevel.JDK_1_7; + } + + if (kind == null || requiredLevel != null && !level.isAtLeast(requiredLevel)) { + boolean is7 = level.isAtLeast(LanguageLevel.JDK_1_7); + String expected = JavaErrorBundle.message(is7 ? "valid.switch.1_7.selector.types" : "valid.switch.selector.types"); + String message = JavaErrorBundle.message("incompatible.types", expected, JavaHighlightUtil.formatType(type)); + HighlightInfo info = + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); + if (switchBlock instanceof PsiSwitchStatement) { + QuickFixAction.registerQuickFixAction( + info, + QuickFixFactory.getInstance().createConvertSwitchToIfIntention((PsiSwitchStatement)switchBlock) + ); + } + if (PsiType.LONG.equals(type) || PsiType.FLOAT.equals(type) || PsiType.DOUBLE.equals(type)) { + QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createAddTypeCastFix(PsiType.INT, expression)); + QuickFixAction.registerQuickFixAction( + info, + QuickFixFactory.getInstance().createWrapWithAdapterFix(PsiType.INT, expression) + ); + } + if (requiredLevel != null) { + QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createIncreaseLanguageLevelFix(requiredLevel)); + } + return info; + } + + PsiClass member = PsiUtil.resolveClassInClassTypeOnly(type); + if (member != null && !PsiUtil.isAccessible(member.getProject(), member, expression, null)) { + String className = PsiFormatUtil.formatClass(member, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_FQ_NAME); + String message = JavaErrorBundle.message("inaccessible.type", className); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); + } + + return null; + } + + private enum SelectorKind { + INT, + ENUM, + STRING + } + + private static SelectorKind getSwitchSelectorKind(@Nonnull PsiType type) { + if (TypeConversionUtil.getTypeRank(type) <= TypeConversionUtil.INT_RANK) { + return SelectorKind.INT; + } + + PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(type); + if (psiClass != null) { + if (psiClass.isEnum()) { + return SelectorKind.ENUM; + } + if (Comparing.strEqual(psiClass.getQualifiedName(), JavaClassNames.JAVA_LANG_STRING)) { + return SelectorKind.STRING; + } + } + + return null; + } + + @Nullable + public static HighlightInfo checkPolyadicOperatorApplicable(@Nonnull PsiPolyadicExpression expression) { + PsiExpression[] operands = expression.getOperands(); + + PsiType lType = operands[0].getType(); + IElementType operationSign = expression.getOperationTokenType(); + for (int i = 1; i < operands.length; i++) { + PsiExpression operand = operands[i]; + PsiType rType = operand.getType(); + if (!TypeConversionUtil.isBinaryOperatorApplicable(operationSign, lType, rType, false)) { + PsiJavaToken token = expression.getTokenBeforeOperand(operand); + String message = JavaErrorBundle.message( + "binary.operator.not.applicable", + token.getText(), + JavaHighlightUtil.formatType(lType), + JavaHighlightUtil.formatType(rType) + ); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); + } + lType = TypeConversionUtil.calcTypeForBinaryExpression(lType, rType, operationSign, true); + } + + return null; + } + + + @Nullable + public static HighlightInfo checkUnaryOperatorApplicable(@Nullable PsiJavaToken token, @Nullable PsiExpression expression) { + if (token != null && expression != null && !TypeConversionUtil.isUnaryOperatorApplicable(token, expression)) { + PsiType type = expression.getType(); + if (type == null) { + return null; + } + String message = JavaErrorBundle.message("unary.operator.not.applicable", token.getText(), JavaHighlightUtil.formatType(type)); + + PsiElement parentExpr = token.getParent(); + HighlightInfo highlightInfo = + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(parentExpr).descriptionAndTooltip(message).create(); + if (parentExpr instanceof PsiPrefixExpression && token.getTokenType() == JavaTokenType.EXCL) { + QuickFixAction.registerQuickFixAction( + highlightInfo, + QuickFixFactory.getInstance().createNegationBroadScopeFix((PsiPrefixExpression)parentExpr) + ); + } + return highlightInfo; + } + return null; + } + + @Nullable + public static HighlightInfo checkThisOrSuperExpressionInIllegalContext( + @Nonnull PsiExpression expr, + @Nullable PsiJavaCodeReferenceElement qualifier, + @Nonnull LanguageLevel languageLevel + ) { + if (expr instanceof PsiSuperExpression) { + final PsiElement parent = expr.getParent(); + if (!(parent instanceof PsiReferenceExpression)) { + // like in 'Object o = super;' + final int o = expr.getTextRange().getEndOffset(); + String description = JavaErrorBundle.message("dot.expected.after.super.or.this"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(o, o + 1).descriptionAndTooltip(description).create(); + } + } + + PsiClass aClass; + if (qualifier != null) { + PsiElement resolved = qualifier.advancedResolve(true).getElement(); + if (resolved != null && !(resolved instanceof PsiClass)) { + String description = JavaErrorBundle.message("class.expected"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(qualifier).descriptionAndTooltip(description).create(); + } + aClass = (PsiClass)resolved; + } + else { + aClass = PsiTreeUtil.getParentOfType(expr, PsiClass.class); + if (aClass instanceof PsiAnonymousClass && PsiTreeUtil.isAncestor(((PsiAnonymousClass)aClass).getArgumentList(), expr, false)) { + aClass = PsiTreeUtil.getParentOfType(aClass, PsiClass.class, true); + } + } + if (aClass == null) { + return null; + } + + if (!InheritanceUtil.hasEnclosingInstanceInScope(aClass, expr, false, false)) { + if (!resolvesToImmediateSuperInterface(expr, qualifier, aClass, languageLevel)) { + return HighlightClassUtil.reportIllegalEnclosingUsage(expr, null, aClass, expr); + } + + if (expr instanceof PsiSuperExpression) { + final PsiElement resolved = ((PsiReferenceExpression)expr.getParent()).resolve(); + //15.11.2 + //The form T.super.Identifier refers to the field named Identifier of the lexically enclosing instance corresponding to T, + //but with that instance viewed as an instance of the superclass of T. + if (resolved instanceof PsiField) { + String description = JavaErrorBundle.message("is.not.an.enclosing.class", formatClass(aClass)); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expr).descriptionAndTooltip(description).create(); + } + } + } + + if (qualifier != null && aClass.isInterface() && expr instanceof PsiSuperExpression && languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) { + //15.12.1 for method invocation expressions; 15.13 for method references + //If TypeName denotes an interface, I, then let T be the type declaration immediately enclosing the method reference expression. + //It is a compile-time error if I is not a direct superinterface of T, + //or if there exists some other direct superclass or direct superinterface of T, J, such that J is a subtype of I. + final PsiClass classT = PsiTreeUtil.getParentOfType(expr, PsiClass.class); + if (classT != null) { + final PsiElement parent = expr.getParent(); + final PsiElement resolved = parent instanceof PsiReferenceExpression ? ((PsiReferenceExpression)parent).resolve() : null; + + PsiClass containingClass = + ObjectUtil.notNull(resolved instanceof PsiMethod ? ((PsiMethod)resolved).getContainingClass() : null, aClass); + for (PsiClass superClass : classT.getSupers()) { + if (superClass.isInheritor(containingClass, true)) { + String cause = null; + if (superClass.isInheritor(aClass, true) && superClass.isInterface()) { + cause = "redundant interface " + format(containingClass) + " is extended by "; + } + else if (resolved instanceof PsiMethod + && MethodSignatureUtil.findMethodBySuperMethod(superClass, (PsiMethod)resolved, true) != resolved) { + cause = "method " + ((PsiMethod)resolved).getName() + " is overridden in "; + } + + if (cause != null) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(qualifier) + .descriptionAndTooltip(JavaErrorBundle.message( + "bad.qualifier.in.super.method.reference", + cause + formatClass(superClass) + )) + .create(); + } + } + } + + if (!classT.isInheritor(aClass, false)) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(qualifier) + .descriptionAndTooltip(JavaErrorBundle.message("no.enclosing.instance.in.scope", format(aClass))) + .create(); + } + } + } + + if (expr instanceof PsiThisExpression) { + final PsiMethod psiMethod = PsiTreeUtil.getParentOfType(expr, PsiMethod.class); + if (psiMethod == null || psiMethod.getContainingClass() != aClass && !isInsideDefaultMethod(psiMethod, aClass)) { + if (aClass.isInterface()) { + return thisNotFoundInInterfaceInfo(expr); + } + + if (aClass instanceof PsiAnonymousClass && PsiTreeUtil.isAncestor( + ((PsiAnonymousClass)aClass).getArgumentList(), + expr, + true + )) { + final PsiClass parentClass = PsiTreeUtil.getParentOfType(aClass, PsiClass.class, true); + if (parentClass != null && parentClass.isInterface()) { + return thisNotFoundInInterfaceInfo(expr); + } + } + } + } + return null; + } + + public static HighlightInfo checkUnqualifiedSuperInDefaultMethod( + @Nonnull LanguageLevel languageLevel, + @Nonnull PsiReferenceExpression expr, + PsiExpression qualifier + ) { + if (languageLevel.isAtLeast(LanguageLevel.JDK_1_8) && qualifier instanceof PsiSuperExpression) { + final PsiMethod method = PsiTreeUtil.getParentOfType(expr, PsiMethod.class); + if (method != null && method.hasModifierProperty(PsiModifier.DEFAULT) && ((PsiSuperExpression)qualifier).getQualifier() == null) { + String description = JavaErrorBundle.message("unqualified.super.disallowed"); + HighlightInfo info = + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expr).descriptionAndTooltip(description).create(); + QualifySuperArgumentFix.registerQuickFixAction((PsiSuperExpression)qualifier, info); + return info; + } + } + return null; + } + + private static boolean isInsideDefaultMethod(PsiMethod method, PsiClass aClass) { + while (method != null && method.getContainingClass() != aClass) { + method = PsiTreeUtil.getParentOfType(method, PsiMethod.class, true); + } + return method != null && method.hasModifierProperty(PsiModifier.DEFAULT); + } + + private static HighlightInfo thisNotFoundInInterfaceInfo(@Nonnull PsiExpression expr) { + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expr) + .descriptionAndTooltip("Cannot find symbol variable this") + .create(); + } + + private static boolean resolvesToImmediateSuperInterface( + @Nonnull PsiExpression expr, + @Nullable PsiJavaCodeReferenceElement qualifier, + @Nonnull PsiClass aClass, + @Nonnull LanguageLevel languageLevel + ) { + if (!(expr instanceof PsiSuperExpression) || qualifier == null || !languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) { + return false; + } + final PsiType superType = expr.getType(); + if (!(superType instanceof PsiClassType)) { + return false; + } + final PsiClass superClass = ((PsiClassType)superType).resolve(); + return superClass != null && aClass.equals(superClass) + && PsiUtil.getEnclosingStaticElement(expr, PsiTreeUtil.getParentOfType(expr, PsiClass.class)) == null; + } + + @Nonnull + public static String buildProblemWithStaticDescription(@Nonnull PsiElement refElement) { + String type = FindUsagesProvider.forLanguage(JavaLanguage.INSTANCE).getType(refElement); + String name = HighlightMessageUtil.getSymbolName(refElement, PsiSubstitutor.EMPTY); + return JavaErrorBundle.message("non.static.symbol.referenced.from.static.context", type, name); + } + + public static void registerStaticProblemQuickFixAction( + @Nonnull PsiElement refElement, + HighlightInfo errorResult, + @Nonnull PsiJavaCodeReferenceElement place + ) { + if (refElement instanceof PsiModifierListOwner) { + QuickFixAction.registerQuickFixAction( + errorResult, + QuickFixFactory.getInstance().createModifierListFix( + (PsiModifierListOwner)refElement, + PsiModifier.STATIC, + true, + false + ) + ); + } + // make context non static + PsiModifierListOwner staticParent = PsiUtil.getEnclosingStaticElement(place, null); + if (staticParent != null && isInstanceReference(place)) { + QuickFixAction.registerQuickFixAction( + errorResult, + QuickFixFactory.getInstance() + .createModifierListFix(staticParent, PsiModifier.STATIC, false, false) + ); + } + if (place instanceof PsiReferenceExpression && refElement instanceof PsiField) { + QuickFixAction.registerQuickFixAction( + errorResult, + QuickFixFactory.getInstance().createCreateFieldFromUsageFix((PsiReferenceExpression)place) + ); + } + } + + private static boolean isInstanceReference(@Nonnull PsiJavaCodeReferenceElement place) { + PsiElement qualifier = place.getQualifier(); + if (qualifier == null) { + return true; + } + if (!(qualifier instanceof PsiJavaCodeReferenceElement)) { + return false; + } + PsiElement q = ((PsiReference)qualifier).resolve(); + if (q instanceof PsiClass) { + return false; + } + if (q != null) { + return true; + } + String qname = ((PsiJavaCodeReferenceElement)qualifier).getQualifiedName(); + return qname == null || !Character.isLowerCase(qname.charAt(0)); + } + + @Nonnull + public static String buildProblemWithAccessDescription(@Nonnull final PsiElement reference, @Nonnull final JavaResolveResult result) { + return buildProblemWithAccessDescription(reference, result, ObjectUtil.notNull(result.getElement())); + } + + @Nonnull + private static String buildProblemWithAccessDescription( + @Nonnull final PsiElement reference, + @Nonnull final JavaResolveResult result, + @Nonnull final PsiElement resolved + ) { + assert resolved instanceof PsiModifierListOwner : resolved; + PsiModifierListOwner refElement = (PsiModifierListOwner)resolved; + String symbolName = HighlightMessageUtil.getSymbolName(refElement, result.getSubstitutor()); + + if (refElement.hasModifierProperty(PsiModifier.PRIVATE)) { + String containerName = getContainerName(refElement, result.getSubstitutor()); + return JavaErrorBundle.message("private.symbol", symbolName, containerName); + } + else if (refElement.hasModifierProperty(PsiModifier.PROTECTED)) { + String containerName = getContainerName(refElement, result.getSubstitutor()); + return JavaErrorBundle.message("protected.symbol", symbolName, containerName); + } + else { + PsiClass packageLocalClass = getPackageLocalClassInTheMiddle(reference); + if (packageLocalClass != null) { + refElement = packageLocalClass; + symbolName = HighlightMessageUtil.getSymbolName(refElement, result.getSubstitutor()); + } + if (refElement.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) || packageLocalClass != null) { + String containerName = getContainerName(refElement, result.getSubstitutor()); + return JavaErrorBundle.message("package.local.symbol", symbolName, containerName); + } + else { + String containerName = getContainerName(refElement, result.getSubstitutor()); + return JavaErrorBundle.message("visibility.access.problem", symbolName, containerName); + } + } + } + + private static PsiElement getContainer(PsiModifierListOwner refElement) { + for (ContainerProvider provider : ContainerProvider.EP_NAME.getExtensions()) { + final PsiElement container = provider.getContainer(refElement); + if (container != null) { + return container; + } + } + return refElement.getParent(); + } + + private static String getContainerName(PsiModifierListOwner refElement, final PsiSubstitutor substitutor) { + final PsiElement container = getContainer(refElement); + return container == null ? "?" : HighlightMessageUtil.getSymbolName(container, substitutor); + } + + @Nullable + public static HighlightInfo checkValidArrayAccessExpression(@Nonnull PsiArrayAccessExpression arrayAccessExpression) { + final PsiExpression arrayExpression = arrayAccessExpression.getArrayExpression(); + final PsiType arrayExpressionType = arrayExpression.getType(); + + if (arrayExpressionType != null && !(arrayExpressionType instanceof PsiArrayType)) { + final String description = JavaErrorBundle.message("array.type.expected", JavaHighlightUtil.formatType(arrayExpressionType)); + final HighlightInfo info = + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(arrayExpression).descriptionAndTooltip(description).create(); + QuickFixAction.registerQuickFixAction( + info, + QuickFixFactory.getInstance().createReplaceWithListAccessFix(arrayAccessExpression) + ); + return info; + } + + final PsiExpression indexExpression = arrayAccessExpression.getIndexExpression(); + return indexExpression != null + ? checkAssignability(PsiType.INT, indexExpression.getType(), indexExpression, indexExpression) + : null; + } + + + @Nullable + public static HighlightInfo checkCatchParameterIsThrowable(@Nonnull final PsiParameter parameter) { + if (parameter.getDeclarationScope() instanceof PsiCatchSection) { + final PsiType type = parameter.getType(); + return checkMustBeThrowable(type, parameter, true); + } + return null; + } + + @Nullable + public static HighlightInfo checkTryResourceIsAutoCloseable(@Nonnull PsiResourceListElement resource) { + PsiType type = resource.getType(); + if (type == null) { + return null; + } + + PsiElementFactory factory = JavaPsiFacade.getInstance(resource.getProject()).getElementFactory(); + PsiClassType autoCloseable = factory.createTypeByFQClassName(JavaClassNames.JAVA_LANG_AUTO_CLOSEABLE, resource.getResolveScope()); + if (TypeConversionUtil.isAssignable(autoCloseable, type)) { + return null; + } + + return createIncompatibleTypeHighlightInfo(autoCloseable, type, resource.getTextRange(), 0); + } + + @Nullable + public static HighlightInfo checkResourceVariableIsFinal(@Nonnull PsiResourceExpression resource) { + PsiExpression expression = resource.getExpression(); + + if (expression instanceof PsiThisExpression) { + return null; + } + + if (expression instanceof PsiReferenceExpression) { + PsiElement target = ((PsiReferenceExpression)expression).resolve(); + if (target == null) { + return null; + } + + if (target instanceof PsiVariable) { + PsiVariable variable = (PsiVariable)target; + + PsiModifierList modifierList = variable.getModifierList(); + if (modifierList != null && modifierList.hasModifierProperty(PsiModifier.FINAL)) { + return null; + } + + if (!(variable instanceof PsiField) + && HighlightControlFlowUtil.isEffectivelyFinal(variable, resource, (PsiJavaCodeReferenceElement)expression)) { + return null; + } + } + + String text = JavaErrorBundle.message("resource.variable.must.be.final"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(text).create(); + } + + String text = JavaErrorBundle.message("declaration.or.variable.expected"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(text).create(); + } + + @Nonnull + public static Collection checkArrayInitializer(final PsiExpression initializer, PsiType type) { + if (!(initializer instanceof PsiArrayInitializerExpression)) { + return Collections.emptyList(); + } + if (!(type instanceof PsiArrayType)) { + return Collections.emptyList(); + } + + final PsiType componentType = ((PsiArrayType)type).getComponentType(); + final PsiArrayInitializerExpression arrayInitializer = (PsiArrayInitializerExpression)initializer; + + boolean arrayTypeFixChecked = false; + VariableArrayTypeFix fix = null; + + final Collection result = ContainerUtil.newArrayList(); + final PsiExpression[] initializers = arrayInitializer.getInitializers(); + for (PsiExpression expression : initializers) { + final HighlightInfo info = checkArrayInitializerCompatibleTypes(expression, componentType); + if (info != null) { + result.add(info); + + if (!arrayTypeFixChecked) { + final PsiType checkResult = JavaHighlightUtil.sameType(initializers); + fix = checkResult != null ? VariableArrayTypeFix.createFix(arrayInitializer, checkResult) : null; + arrayTypeFixChecked = true; + } + if (fix != null) { + QuickFixAction.registerQuickFixAction(info, new LocalQuickFixOnPsiElementAsIntentionAdapter(fix)); + } + } + } + return result; + } + + @Nullable + private static HighlightInfo checkArrayInitializerCompatibleTypes(@Nonnull PsiExpression initializer, final PsiType componentType) { + PsiType initializerType = initializer.getType(); + if (initializerType == null) { + String description = JavaErrorBundle.message("illegal.initializer", JavaHighlightUtil.formatType(componentType)); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(initializer).descriptionAndTooltip(description).create(); + } + PsiExpression expression = initializer instanceof PsiArrayInitializerExpression ? null : initializer; + return checkAssignability(componentType, initializerType, expression, initializer); + } + + @Nullable + public static HighlightInfo checkExpressionRequired( + @Nonnull PsiReferenceExpression expression, + @Nonnull JavaResolveResult resultForIncompleteCode + ) { + if (expression.getNextSibling() instanceof PsiErrorElement) { + return null; + } + + PsiElement resolved = resultForIncompleteCode.getElement(); + if (resolved == null || resolved instanceof PsiVariable) { + return null; + } + + PsiElement parent = expression.getParent(); + // String.class or String() are both correct + if (parent instanceof PsiReferenceExpression || parent instanceof PsiMethodCallExpression) { + return null; + } + + String description = JavaErrorBundle.message("expression.expected"); + HighlightInfo info = + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create(); + if (info != null) { + UnresolvedReferenceQuickFixProvider.registerReferenceFixes(expression, QuickFixActionRegistrar.create(info)); + } + return info; + } + + @Nullable + public static HighlightInfo checkArrayInitializerApplicable(@Nonnull PsiArrayInitializerExpression expression) { + /* + JLS 10.6 Array Initializers + An array initializer may be specified in a declaration, or as part of an array creation expression + */ + PsiElement parent = expression.getParent(); + if (parent instanceof PsiVariable) { + PsiVariable variable = (PsiVariable)parent; + if (variable.getType() instanceof PsiArrayType) { + return null; + } + } + else if (parent instanceof PsiNewExpression || parent instanceof PsiArrayInitializerExpression) { + return null; + } + + String description = JavaErrorBundle.message("array.initializer.not.allowed"); + HighlightInfo info = + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create(); + QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createAddNewArrayExpressionFix(expression)); + return info; + } + + + @Nullable + public static HighlightInfo checkCaseStatement(@Nonnull PsiSwitchLabelStatementBase statement) { + PsiSwitchBlock switchBlock = statement.getEnclosingSwitchBlock(); + if (switchBlock == null) { + String description = JavaErrorBundle.message("case.statement.outside.switch"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); + } + + return null; + } + + @Nonnull + public static Collection checkSwitchLabelValues(@Nonnull PsiSwitchBlock switchBlock) { + PsiCodeBlock body = switchBlock.getBody(); + if (body == null) { + return Collections.emptyList(); + } + + PsiExpression selectorExpression = switchBlock.getExpression(); + PsiType selectorType = selectorExpression == null ? PsiType.INT : selectorExpression.getType(); + MultiMap values = new MultiMap<>(); + Object defaultValue = new Object(); + Collection results = new ArrayList<>(); + boolean hasDefaultCase = false; + + for (PsiStatement st : body.getStatements()) { + if (!(st instanceof PsiSwitchLabelStatementBase)) { + continue; + } + PsiSwitchLabelStatementBase labelStatement = (PsiSwitchLabelStatementBase)st; + boolean defaultCase = labelStatement.isDefaultCase(); + + if (defaultCase) { + values.putValue(defaultValue, ObjectUtil.notNull(labelStatement.getFirstChild(), labelStatement)); + hasDefaultCase = true; + } + else { + PsiExpressionList expressionList = labelStatement.getCaseValues(); + if (expressionList != null) { + for (PsiExpression expr : expressionList.getExpressions()) { + if (selectorExpression != null) { + HighlightInfo result = checkAssignability(selectorType, expr.getType(), expr, expr); + if (result != null) { + results.add(result); + continue; + } + } + + Object value = null; + if (expr instanceof PsiReferenceExpression) { + PsiElement element = ((PsiReferenceExpression)expr).resolve(); + if (element instanceof PsiEnumConstant) { + value = ((PsiEnumConstant)element).getName(); + if (((PsiReferenceExpression)expr).getQualifier() != null) { + String message = JavaErrorBundle.message("qualified.enum.constant.in.switch"); + results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expr) + .descriptionAndTooltip(message) + .create()); + continue; + } + } + } + if (value == null) { + value = ConstantExpressionUtil.computeCastTo(expr, selectorType); + } + if (value == null) { + String description = JavaErrorBundle.message("constant.expression.required"); + results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(expr) + .descriptionAndTooltip(description) + .create()); + continue; + } + + values.putValue(value, expr); + } + } + } + } + + for (Map.Entry> entry : values.entrySet()) { + if (entry.getValue().size() > 1) { + Object value = entry.getKey(); + String description = + value == defaultValue + ? JavaErrorBundle.message("duplicate.default.switch.label") + : JavaErrorBundle.message("duplicate.switch.label", value); + for (PsiElement element : entry.getValue()) { + results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(element) + .descriptionAndTooltip(description) + .create()); + } + } + } + + if (results.isEmpty() && switchBlock instanceof PsiSwitchExpression) { + Set missingConstants = new HashSet<>(); + boolean exhaustive = hasDefaultCase; + if (!exhaustive) { + if (!values.isEmpty() && selectorType instanceof PsiClassType) { + PsiClass type = ((PsiClassType)selectorType).resolve(); + if (type != null && type.isEnum()) { + for (PsiField field : type.getFields()) { + if (field instanceof PsiEnumConstant && !values.containsKey(field.getName())) { + missingConstants.add(field.getName()); + } + } + exhaustive = missingConstants.isEmpty(); + } + } + } + if (!exhaustive) { + PsiElement range = ObjectUtil.notNull(selectorExpression, switchBlock); + String message = JavaErrorBundle.message(values.isEmpty() ? "switch.expr.empty" : "switch.expr.incomplete"); + HighlightInfo info = + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip(message).create(); + if (!missingConstants.isEmpty()) { + QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance() + .createAddMissingEnumBranchesFix(switchBlock, missingConstants)); + } + QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createAddSwitchDefaultFix(switchBlock, null)); + results.add(info); + } + } + + return results; + } + + + /** + * see JLS 8.3.2.3 + */ + @Nullable + public static HighlightInfo checkIllegalForwardReferenceToField( + @Nonnull PsiReferenceExpression expression, + @Nonnull PsiField referencedField + ) { + Boolean isIllegalForwardReference = isIllegalForwardReferenceToField(expression, referencedField, false); + if (isIllegalForwardReference == null) { + return null; + } + String description = JavaErrorBundle.message(isIllegalForwardReference ? "illegal.forward.reference" : "illegal.self.reference"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create(); + } + + public static Boolean isIllegalForwardReferenceToField( + @Nonnull PsiReferenceExpression expression, + @Nonnull PsiField referencedField, + boolean acceptQualified + ) { + PsiClass containingClass = referencedField.getContainingClass(); + if (containingClass == null) { + return null; + } + if (expression.getContainingFile() != referencedField.getContainingFile()) { + return null; + } + if (expression.getTextRange().getStartOffset() >= referencedField.getTextRange().getEndOffset()) { + return null; + } + // only simple reference can be illegal + if (!acceptQualified && expression.getQualifierExpression() != null) { + return null; + } + PsiField initField = findEnclosingFieldInitializer(expression); + PsiClassInitializer classInitializer = findParentClassInitializer(expression); + if (initField == null && classInitializer == null) { + return null; + } + // instance initializers may access static fields + boolean isStaticClassInitializer = classInitializer != null && classInitializer.hasModifierProperty(PsiModifier.STATIC); + boolean isStaticInitField = initField != null && initField.hasModifierProperty(PsiModifier.STATIC); + boolean inStaticContext = isStaticInitField || isStaticClassInitializer; + if (!inStaticContext && referencedField.hasModifierProperty(PsiModifier.STATIC)) { + return null; + } + if (PsiUtil.isOnAssignmentLeftHand(expression) && !PsiUtil.isAccessedForReading(expression)) { + return null; + } + if (!containingClass.getManager().areElementsEquivalent(containingClass, PsiTreeUtil.getParentOfType(expression, PsiClass.class))) { + return null; + } + return initField != referencedField; + } + + /** + * @return field that has initializer with this element as subexpression or null if not found + */ + @Nullable + public static PsiField findEnclosingFieldInitializer(@Nullable PsiElement element) { + while (element != null) { + PsiElement parent = element.getParent(); + if (parent instanceof PsiField) { + PsiField field = (PsiField)parent; + if (element == field.getInitializer()) { + return field; + } + if (field instanceof PsiEnumConstant && element == ((PsiEnumConstant)field).getArgumentList()) { + return field; + } + } + if (element instanceof PsiClass || element instanceof PsiMethod) { + return null; + } + element = parent; + } + return null; + } + + @Nullable + private static PsiClassInitializer findParentClassInitializer(@Nullable PsiElement element) { + while (element != null) { + if (element instanceof PsiClassInitializer) { + return (PsiClassInitializer)element; + } + if (element instanceof PsiClass || element instanceof PsiMethod) { + return null; + } + element = element.getParent(); + } + return null; + } + + + @Nullable + public static HighlightInfo checkIllegalType(@Nullable PsiTypeElement typeElement) { + if (typeElement == null || typeElement.getParent() instanceof PsiTypeElement) { + return null; + } + + if (PsiUtil.isInsideJavadocComment(typeElement)) { + return null; + } + + PsiType type = typeElement.getType(); + PsiType componentType = type.getDeepComponentType(); + if (componentType instanceof PsiClassType) { + PsiClass aClass = PsiUtil.resolveClassInType(componentType); + if (aClass == null) { + String canonicalText = type.getCanonicalText(); + String description = JavaErrorBundle.message("unknown.class", canonicalText); + HighlightInfo info = + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(description).create(); + + PsiJavaCodeReferenceElement referenceElement = typeElement.getInnermostComponentReferenceElement(); + if (referenceElement != null && info != null) { + UnresolvedReferenceQuickFixProvider.registerReferenceFixes(referenceElement, QuickFixActionRegistrar.create(info)); + } + return info; + } + } + + return null; + } + + @Nullable + public static HighlightInfo checkIllegalVoidType(@Nonnull PsiKeyword type) { + if (!PsiKeyword.VOID.equals(type.getText())) { + return null; + } + + PsiElement parent = type.getParent(); + if (parent instanceof PsiTypeElement) { + PsiElement typeOwner = parent.getParent(); + if (typeOwner != null) { + // do not highlight incomplete declarations + if (PsiUtilCore.hasErrorElementChild(typeOwner)) { + return null; + } + } + + if (typeOwner instanceof PsiMethod) { + PsiMethod method = (PsiMethod)typeOwner; + if (method.getReturnTypeElement() == parent && PsiType.VOID.equals(method.getReturnType())) { + return null; + } + } + else if (typeOwner instanceof PsiClassObjectAccessExpression) { + if (TypeConversionUtil.isVoidType(((PsiClassObjectAccessExpression)typeOwner).getOperand().getType())) { + return null; + } + } + else if (typeOwner instanceof JavaCodeFragment) { + if (typeOwner.getUserData(PsiUtil.VALID_VOID_TYPE_IN_CODE_FRAGMENT) != null) { + return null; + } + } + } + + String description = JavaErrorBundle.message("illegal.type.void"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(type).descriptionAndTooltip(description).create(); + } + + @Nullable + public static HighlightInfo checkMemberReferencedBeforeConstructorCalled( + @Nonnull PsiElement expression, + PsiElement resolved, + @Nonnull PsiFile containingFile + ) { + PsiClass referencedClass; + @NonNls String resolvedName; + PsiType type; + if (expression instanceof PsiJavaCodeReferenceElement) { + // redirected ctr + if (PsiKeyword.THIS.equals(((PsiJavaCodeReferenceElement)expression).getReferenceName()) && resolved instanceof PsiMethod && ((PsiMethod)resolved) + .isConstructor()) { + return null; + } + PsiElement qualifier = ((PsiJavaCodeReferenceElement)expression).getQualifier(); + type = qualifier instanceof PsiExpression ? ((PsiExpression)qualifier).getType() : null; + referencedClass = PsiUtil.resolveClassInType(type); + + boolean isSuperCall = RefactoringChangeUtil.isSuperMethodCall(expression.getParent()); + if (resolved == null && isSuperCall) { + if (qualifier instanceof PsiReferenceExpression) { + resolved = ((PsiReferenceExpression)qualifier).resolve(); + expression = qualifier; + type = ((PsiReferenceExpression)qualifier).getType(); + referencedClass = PsiUtil.resolveClassInType(type); + } + else if (qualifier == null) { + resolved = PsiTreeUtil.getParentOfType(expression, PsiMethod.class, true, PsiMember.class); + if (resolved != null) { + referencedClass = ((PsiMethod)resolved).getContainingClass(); + } + } + else if (qualifier instanceof PsiThisExpression) { + referencedClass = PsiUtil.resolveClassInType(((PsiThisExpression)qualifier).getType()); + } + } + if (resolved instanceof PsiField) { + PsiField referencedField = (PsiField)resolved; + if (referencedField.hasModifierProperty(PsiModifier.STATIC)) { + return null; + } + resolvedName = PsiFormatUtil.formatVariable( + referencedField, + PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME, + PsiSubstitutor.EMPTY + ); + referencedClass = referencedField.getContainingClass(); + } + else if (resolved instanceof PsiMethod) { + PsiMethod method = (PsiMethod)resolved; + if (method.hasModifierProperty(PsiModifier.STATIC)) { + return null; + } + PsiElement nameElement = + expression instanceof PsiThisExpression ? expression : ((PsiJavaCodeReferenceElement)expression).getReferenceNameElement(); + String name = nameElement == null ? null : nameElement.getText(); + if (isSuperCall) { + if (referencedClass == null) { + return null; + } + if (qualifier == null) { + PsiClass superClass = referencedClass.getSuperClass(); + if (superClass != null && PsiUtil.isInnerClass(superClass) && InheritanceUtil.isInheritorOrSelf( + referencedClass, + superClass.getContainingClass(), + true + )) { + // by default super() is considered this. - qualified + resolvedName = PsiKeyword.THIS; + } + else { + return null; + } + } + else { + resolvedName = qualifier.getText(); + } + } + else if (PsiKeyword.THIS.equals(name)) { + resolvedName = PsiKeyword.THIS; + } + else { + resolvedName = PsiFormatUtil.formatMethod( + method, + PsiSubstitutor.EMPTY, + PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME, + 0 + ); + if (referencedClass == null) { + referencedClass = method.getContainingClass(); + } + } + } + else if (resolved instanceof PsiClass) { + PsiClass aClass = (PsiClass)resolved; + if (aClass.hasModifierProperty(PsiModifier.STATIC)) { + return null; + } + referencedClass = aClass.getContainingClass(); + if (referencedClass == null) { + return null; + } + resolvedName = PsiFormatUtil.formatClass(aClass, PsiFormatUtilBase.SHOW_NAME); + } + else { + return null; + } + } + else if (expression instanceof PsiThisExpression) { + PsiThisExpression thisExpression = (PsiThisExpression)expression; + type = thisExpression.getType(); + referencedClass = PsiUtil.resolveClassInType(type); + if (thisExpression.getQualifier() != null) { + resolvedName = + referencedClass == null ? null : PsiFormatUtil.formatClass(referencedClass, PsiFormatUtilBase.SHOW_NAME) + ".this"; + } + else { + resolvedName = "this"; + } + } + else { + return null; + } + if (referencedClass == null) { + return null; + } + return checkReferenceToOurInstanceInsideThisOrSuper(expression, referencedClass, resolvedName, containingFile); + } + + @Nullable + private static HighlightInfo checkReferenceToOurInstanceInsideThisOrSuper( + @Nonnull final PsiElement expression, + @Nonnull PsiClass referencedClass, + final String resolvedName, + @Nonnull PsiFile containingFile + ) { + if (PsiTreeUtil.getParentOfType(expression, PsiReferenceParameterList.class) != null) { + return null; + } + PsiElement element = expression.getParent(); + while (element != null) { + // check if expression inside super()/this() call + if (RefactoringChangeUtil.isSuperOrThisMethodCall(element)) { + PsiElement parentClass = new PsiMatcherImpl(element).parent(PsiMatchers.hasClass(PsiExpressionStatement.class)) + .parent(PsiMatchers.hasClass(PsiCodeBlock.class)) + .parent(PsiMatchers + .hasClass(PsiMethod.class)) + .dot(JavaMatchers.isConstructor(true)) + .parent(PsiMatchers.hasClass(PsiClass.class)) + .getElement(); + if (parentClass == null) { + return null; + } + + // only this class/superclasses instance methods are not allowed to call + PsiClass aClass = (PsiClass)parentClass; + if (PsiUtil.isInnerClass(aClass) && referencedClass == aClass.getContainingClass()) { + return null; + } + // field or method should be declared in this class or super + if (!InheritanceUtil.isInheritorOrSelf(aClass, referencedClass, true)) { + return null; + } + // and point to our instance + if (expression instanceof PsiReferenceExpression + && !thisOrSuperReference(((PsiReferenceExpression)expression).getQualifierExpression(), aClass)) { + return null; + } + + if (expression instanceof PsiJavaCodeReferenceElement + && !aClass.equals(PsiTreeUtil.getParentOfType(expression, PsiClass.class)) + && PsiTreeUtil.getParentOfType(expression, PsiTypeElement.class) != null) { + return null; + } + + if (expression instanceof PsiJavaCodeReferenceElement + && PsiTreeUtil.getParentOfType(expression, PsiClassObjectAccessExpression.class) != null) { + return null; + } + + final HighlightInfo highlightInfo = createMemberReferencedError(resolvedName, expression.getTextRange()); + if (expression instanceof PsiReferenceExpression && PsiUtil.isInnerClass(aClass)) { + final String referenceName = ((PsiReferenceExpression)expression).getReferenceName(); + final PsiClass containingClass = aClass.getContainingClass(); + LOG.assertTrue(containingClass != null); + final PsiField fieldInContainingClass = containingClass.findFieldByName(referenceName, true); + if (fieldInContainingClass != null && ((PsiReferenceExpression)expression).getQualifierExpression() == null) { + QuickFixAction.registerQuickFixAction(highlightInfo, new QualifyWithThisFix(containingClass, expression)); + } + } + + return highlightInfo; + } + + if (element instanceof PsiReferenceExpression) { + final PsiElement resolve; + if (element instanceof PsiReferenceExpressionImpl) { + PsiReferenceExpressionImpl referenceExpression = (PsiReferenceExpressionImpl)element; + JavaResolveResult[] results = JavaResolveUtil.resolveWithContainingFile( + referenceExpression, + PsiReferenceExpressionImpl.OurGenericsResolver.INSTANCE, + true, + false, + containingFile + ); + resolve = results.length == 1 ? results[0].getElement() : null; + } + else { + resolve = ((PsiReferenceExpression)element).resolve(); + } + if (resolve instanceof PsiField && ((PsiField)resolve).hasModifierProperty(PsiModifier.STATIC)) { + return null; + } + } + + element = element.getParent(); + if (element instanceof PsiClass && InheritanceUtil.isInheritorOrSelf((PsiClass)element, referencedClass, true)) { + return null; + } + } + return null; + } + + private static HighlightInfo createMemberReferencedError(@NonNls final String resolvedName, @Nonnull TextRange textRange) { + String description = JavaErrorBundle.message("member.referenced.before.constructor.called", resolvedName); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).descriptionAndTooltip(description).create(); + } + + @Nullable + public static HighlightInfo checkImplicitThisReferenceBeforeSuper(@Nonnull PsiClass aClass, @Nonnull JavaSdkVersion javaSdkVersion) { + if (javaSdkVersion.isAtLeast(JavaSdkVersion.JDK_1_7)) { + return null; + } + if (aClass instanceof PsiAnonymousClass || aClass instanceof PsiTypeParameter) { + return null; + } + PsiClass superClass = aClass.getSuperClass(); + if (superClass == null || !PsiUtil.isInnerClass(superClass)) { + return null; + } + PsiClass outerClass = superClass.getContainingClass(); + if (!InheritanceUtil.isInheritorOrSelf(aClass, outerClass, true)) { + return null; + } + // 'this' can be used as an (implicit) super() qualifier + PsiMethod[] constructors = aClass.getConstructors(); + if (constructors.length == 0) { + TextRange range = HighlightNamesUtil.getClassDeclarationTextRange(aClass); + return createMemberReferencedError(aClass.getName() + ".this", range); + } + for (PsiMethod constructor : constructors) { + if (!isSuperCalledInConstructor(constructor)) { + return createMemberReferencedError( + aClass.getName() + ".this", + HighlightNamesUtil.getMethodDeclarationTextRange(constructor) + ); + } + } + return null; + } + + private static boolean isSuperCalledInConstructor(@Nonnull final PsiMethod constructor) { + final PsiCodeBlock body = constructor.getBody(); + if (body == null) { + return false; + } + final PsiStatement[] statements = body.getStatements(); + if (statements.length == 0) { + return false; + } + final PsiStatement statement = statements[0]; + final PsiElement element = new PsiMatcherImpl(statement).dot(PsiMatchers.hasClass(PsiExpressionStatement.class)) + .firstChild(PsiMatchers.hasClass(PsiMethodCallExpression.class)) + .firstChild + (PsiMatchers.hasClass(PsiReferenceExpression.class)) + .firstChild(PsiMatchers.hasClass(PsiKeyword.class)) + .dot(PsiMatchers.hasText(PsiKeyword.SUPER)) + .getElement(); + return element != null; + } + + private static boolean thisOrSuperReference(@Nullable PsiExpression qualifierExpression, PsiClass aClass) { + if (qualifierExpression == null) { + return true; + } + PsiJavaCodeReferenceElement qualifier; + if (qualifierExpression instanceof PsiThisExpression) { + qualifier = ((PsiThisExpression)qualifierExpression).getQualifier(); + } + else if (qualifierExpression instanceof PsiSuperExpression) { + qualifier = ((PsiSuperExpression)qualifierExpression).getQualifier(); + } + else { + return false; + } + if (qualifier == null) { return true; - } - differentArgumentsMessage.set(pair.first.getPresentableText() + " and " + pair.second.getPresentableText()); - return false; - }); - if (sameGenericParameterization != null) { - final String message = - formatClass(sameGenericParameterization) + " cannot be inherited with different arguments: " + differentArgumentsMessage.get(); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); } - } + PsiElement resolved = qualifier.resolve(); + return resolved instanceof PsiClass && InheritanceUtil.isInheritorOrSelf(aClass, (PsiClass)resolved, true); } - return null; - } - private static boolean isIntersection(PsiTypeElement castTypeElement, PsiType castType) { - if (castType instanceof PsiIntersectionType) { - return true; + @Nullable + public static HighlightInfo checkLabelWithoutStatement(@Nonnull PsiLabeledStatement statement) { + if (statement.getStatement() == null) { + String description = JavaErrorBundle.message("label.without.statement"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); + } + return null; } - return castType instanceof PsiClassType && PsiTreeUtil.getChildrenOfType(castTypeElement, PsiTypeElement.class) != null; - } - @Nullable - public static HighlightInfo checkInconvertibleTypeCast(@Nonnull PsiTypeCastExpression expression) { - final PsiTypeElement castTypeElement = expression.getCastType(); - if (castTypeElement == null) { - return null; - } - PsiType castType = castTypeElement.getType(); - PsiExpression operand = expression.getOperand(); - if (operand == null) { - return null; + @Nullable + public static HighlightInfo checkLabelAlreadyInUse(@Nonnull PsiLabeledStatement statement) { + PsiIdentifier identifier = statement.getLabelIdentifier(); + String text = identifier.getText(); + PsiElement element = statement; + while (element != null) { + if (element instanceof PsiMethod || element instanceof PsiClass) { + break; + } + if (element instanceof PsiLabeledStatement && element != statement + && Comparing.equal(((PsiLabeledStatement)element).getLabelIdentifier().getText(), text)) { + String description = JavaErrorBundle.message("duplicate.label", text); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(identifier) + .descriptionAndTooltip(description) + .create(); + } + element = element.getParent(); + } + return null; } - PsiType operandType = operand.getType(); - if (operandType != null && !TypeConversionUtil.areTypesConvertible(operandType, - castType, - PsiUtil.getLanguageLevel(expression)) && !RedundantCastUtil.isInPolymorphicCall( - expression)) { - String message = JavaErrorBundle.message("inconvertible.type.cast", - JavaHighlightUtil.formatType(operandType), - JavaHighlightUtil.formatType(castType)); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); + + @Nullable + public static HighlightInfo checkUnclosedComment(@Nonnull PsiComment comment) { + if (!(comment instanceof PsiDocComment) && comment.getTokenType() != JavaTokenType.C_STYLE_COMMENT) { + return null; + } + if (!comment.getText().endsWith("*/")) { + int start = comment.getTextRange().getEndOffset() - 1; + int end = start + 1; + String description = JavaErrorBundle.message("unclosed.comment"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(start, end).descriptionAndTooltip(description).create(); + } + return null; } - return null; - } + @Nonnull + public static Collection checkCatchTypeIsDisjoint(@Nonnull final PsiParameter parameter) { + if (!(parameter.getType() instanceof PsiDisjunctionType)) { + return Collections.emptyList(); + } - @Nullable - public static HighlightInfo checkVariableExpected(@Nonnull PsiExpression expression) { - PsiExpression lValue; - if (expression instanceof PsiAssignmentExpression) { - PsiAssignmentExpression assignment = (PsiAssignmentExpression)expression; - lValue = assignment.getLExpression(); - } - else if (PsiUtil.isIncrementDecrementOperation(expression)) { - lValue = - expression instanceof PsiPostfixExpression ? ((PsiPostfixExpression)expression).getOperand() : ((PsiPrefixExpression)expression).getOperand(); - } - else { - lValue = null; - } - HighlightInfo errorResult = null; - if (lValue != null && !TypeConversionUtil.isLValue(lValue)) { - String description = JavaErrorBundle.message("variable.expected"); - errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(lValue).descriptionAndTooltip(description).create(); + final Collection result = ContainerUtil.newArrayList(); + final List typeElements = PsiUtil.getParameterTypeElements(parameter); + for (int i = 0, size = typeElements.size(); i < size; i++) { + final PsiClass class1 = PsiUtil.resolveClassInClassTypeOnly(typeElements.get(i).getType()); + if (class1 == null) { + continue; + } + for (int j = i + 1; j < size; j++) { + final PsiClass class2 = PsiUtil.resolveClassInClassTypeOnly(typeElements.get(j).getType()); + if (class2 == null) { + continue; + } + final boolean sub = InheritanceUtil.isInheritorOrSelf(class1, class2, true); + final boolean sup = InheritanceUtil.isInheritorOrSelf(class2, class1, true); + if (sub || sup) { + final String name1 = PsiFormatUtil.formatClass(class1, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_FQ_NAME); + final String name2 = PsiFormatUtil.formatClass(class2, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_FQ_NAME); + final String message = JavaErrorBundle.message("exception.must.be.disjoint", sub ? name1 : name2, sub ? name2 : name1); + final PsiTypeElement element = typeElements.get(sub ? i : j); + final HighlightInfo highlight = + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(message).create(); + QuickFixAction.registerQuickFixAction(highlight, QuickFixFactory.getInstance().createDeleteMultiCatchFix(element)); + result.add(highlight); + break; + } + } + } + + return result; } - return errorResult; - } + @Nonnull + public static Collection checkExceptionAlreadyCaught(@Nonnull final PsiParameter parameter) { + final PsiElement scope = parameter.getDeclarationScope(); + if (!(scope instanceof PsiCatchSection)) { + return Collections.emptyList(); + } - @Nullable - public static HighlightInfo checkAssignmentOperatorApplicable(@Nonnull PsiAssignmentExpression assignment) { - PsiJavaToken operationSign = assignment.getOperationSign(); - IElementType eqOpSign = operationSign.getTokenType(); - IElementType opSign = TypeConversionUtil.convertEQtoOperation(eqOpSign); - if (opSign == null) { - return null; - } - final PsiType lType = assignment.getLExpression().getType(); - final PsiExpression rExpression = assignment.getRExpression(); - if (rExpression == null) { - return null; - } - final PsiType rType = rExpression.getType(); - HighlightInfo errorResult = null; - if (!TypeConversionUtil.isBinaryOperatorApplicable(opSign, lType, rType, true)) { - String operatorText = operationSign.getText().substring(0, operationSign.getText().length() - 1); - String message = JavaErrorBundle.message("binary.operator.not.applicable", - operatorText, - JavaHighlightUtil.formatType(lType), - JavaHighlightUtil.formatType(rType)); + final PsiCatchSection catchSection = (PsiCatchSection)scope; + final PsiCatchSection[] allCatchSections = catchSection.getTryStatement().getCatchSections(); + final int startFrom = ArrayUtil.find(allCatchSections, catchSection) - 1; + if (startFrom < 0) { + return Collections.emptyList(); + } - errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(assignment).descriptionAndTooltip(message).create(); - } - return errorResult; - } + final List typeElements = PsiUtil.getParameterTypeElements(parameter); + final boolean isInMultiCatch = typeElements.size() > 1; + final Collection result = ContainerUtil.newArrayList(); + + for (PsiTypeElement typeElement : typeElements) { + final PsiClass catchClass = PsiUtil.resolveClassInClassTypeOnly(typeElement.getType()); + if (catchClass == null) { + continue; + } + for (int i = startFrom; i >= 0; i--) { + final PsiCatchSection upperCatchSection = allCatchSections[i]; + final PsiType upperCatchType = upperCatchSection.getCatchType(); + + final boolean highlight = upperCatchType instanceof PsiDisjunctionType + ? checkMultipleTypes(catchClass, ((PsiDisjunctionType)upperCatchType).getDisjunctions()) + : checkSingleType(catchClass, upperCatchType); + if (highlight) { + final String className = + PsiFormatUtil.formatClass(catchClass, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_FQ_NAME); + final String description = JavaErrorBundle.message("exception.already.caught", className); + final HighlightInfo highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(typeElement) + .descriptionAndTooltip(description) + .create(); + result.add(highlightInfo); + + QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance() + .createMoveCatchUpFix(catchSection, upperCatchSection)); + if (isInMultiCatch) { + QuickFixAction.registerQuickFixAction( + highlightInfo, + QuickFixFactory.getInstance().createDeleteMultiCatchFix(typeElement) + ); + } + else { + QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createDeleteCatchFix(parameter)); + } + } + } + } - @Nullable - public static HighlightInfo checkAssignmentCompatibleTypes(@Nonnull PsiAssignmentExpression assignment) { - PsiExpression lExpr = assignment.getLExpression(); - PsiExpression rExpr = assignment.getRExpression(); - if (rExpr == null) { - return null; + return result; } - PsiType lType = lExpr.getType(); - PsiType rType = rExpr.getType(); - if (rType == null) { - return null; + + private static boolean checkMultipleTypes(final PsiClass catchClass, @Nonnull final List upperCatchTypes) { + for (int i = upperCatchTypes.size() - 1; i >= 0; i--) { + if (checkSingleType(catchClass, upperCatchTypes.get(i))) { + return true; + } + } + return false; } - final IElementType sign = assignment.getOperationTokenType(); - HighlightInfo highlightInfo; - if (JavaTokenType.EQ.equals(sign)) { - highlightInfo = checkAssignability(lType, rType, rExpr, assignment); + private static boolean checkSingleType(final PsiClass catchClass, final PsiType upperCatchType) { + final PsiClass upperCatchClass = PsiUtil.resolveClassInType(upperCatchType); + return upperCatchClass != null && InheritanceUtil.isInheritorOrSelf(catchClass, upperCatchClass, true); } - else { - // 15.26.2. Compound Assignment Operators - final IElementType opSign = TypeConversionUtil.convertEQtoOperation(sign); - final PsiType type = TypeConversionUtil.calcTypeForBinaryExpression(lType, rType, opSign, true); - if (type == null || lType == null || TypeConversionUtil.areTypesConvertible(type, lType)) { - return null; - } - highlightInfo = createIncompatibleTypeHighlightInfo(lType, type, assignment.getTextRange(), 0); - QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createChangeToAppendFix(sign, lType, assignment)); - } - if (highlightInfo == null) { - return null; - } - registerChangeVariableTypeFixes(lExpr, rType, rExpr, highlightInfo); - if (lType != null) { - registerChangeVariableTypeFixes(rExpr, lType, lExpr, highlightInfo); - } - return highlightInfo; - } - - private static void registerChangeVariableTypeFixes(@Nonnull PsiExpression expression, - @Nonnull PsiType type, - @Nullable final PsiExpression lExpr, - @Nullable HighlightInfo highlightInfo) { - if (highlightInfo == null || !(expression instanceof PsiReferenceExpression)) { - return; - } - - final PsiElement element = ((PsiReferenceExpression)expression).resolve(); - if (!(element instanceof PsiVariable)) { - return; - } - - registerChangeVariableTypeFixes((PsiVariable)element, type, lExpr, highlightInfo); - - if (lExpr instanceof PsiMethodCallExpression && lExpr.getParent() instanceof PsiAssignmentExpression) { - final PsiElement parent = lExpr.getParent(); - if (parent.getParent() instanceof PsiStatement) { - final PsiMethod method = ((PsiMethodCallExpression)lExpr).resolveMethod(); - if (method != null && PsiType.VOID.equals(method.getReturnType())) { - QuickFixAction.registerQuickFixAction(highlightInfo, new ReplaceAssignmentFromVoidWithStatementIntentionAction(parent, lExpr)); - } - } - } - } - - private static boolean isCastIntentionApplicable(@Nonnull PsiExpression expression, @Nullable PsiType toType) { - while (expression instanceof PsiTypeCastExpression || expression instanceof PsiParenthesizedExpression) { - if (expression instanceof PsiTypeCastExpression) { - expression = ((PsiTypeCastExpression)expression).getOperand(); - } - if (expression instanceof PsiParenthesizedExpression) { - expression = ((PsiParenthesizedExpression)expression).getExpression(); - } - } - if (expression == null) { - return false; - } - PsiType rType = expression.getType(); - return rType != null && toType != null && TypeConversionUtil.areTypesConvertible(rType, toType); - } - - - @Nullable - public static HighlightInfo checkVariableInitializerType(@Nonnull PsiVariable variable) { - PsiExpression initializer = variable.getInitializer(); - // array initializer checked in checkArrayInitializerApplicable - if (initializer == null || initializer instanceof PsiArrayInitializerExpression) { - return null; - } - PsiType lType = variable.getType(); - PsiType rType = initializer.getType(); - PsiTypeElement typeElement = variable.getTypeElement(); - int start = typeElement != null ? typeElement.getTextRange().getStartOffset() : variable.getTextRange().getStartOffset(); - int end = variable.getTextRange().getEndOffset(); - HighlightInfo highlightInfo = checkAssignability(lType, rType, initializer, new TextRange(start, end), 0); - if (highlightInfo != null) { - registerChangeVariableTypeFixes(variable, rType, variable.getInitializer(), highlightInfo); - registerChangeVariableTypeFixes(initializer, lType, null, highlightInfo); - } - return highlightInfo; - } - - @Nullable - public static HighlightInfo checkAssignability(@Nullable PsiType lType, - @Nullable PsiType rType, - @Nullable PsiExpression expression, - @Nonnull PsiElement elementToHighlight) { - TextRange textRange = elementToHighlight.getTextRange(); - return checkAssignability(lType, rType, expression, textRange, 0); - } - - @Nullable - private static HighlightInfo checkAssignability(@Nullable PsiType lType, - @Nullable PsiType rType, - @Nullable PsiExpression expression, - @Nonnull TextRange textRange, - int navigationShift) { - if (lType == rType) { - return null; - } - if (expression == null) { - if (rType == null || lType == null || TypeConversionUtil.isAssignable(lType, rType)) { - return null; - } - } - else if (TypeConversionUtil.areTypesAssignmentCompatible(lType, expression)) { - return null; - } - if (rType == null) { - rType = expression.getType(); - } - HighlightInfo highlightInfo = createIncompatibleTypeHighlightInfo(lType, rType, textRange, navigationShift); - if (rType != null && expression != null && isCastIntentionApplicable(expression, lType)) { - QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createAddTypeCastFix(lType, expression)); - } - if (expression != null) { - QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createWrapWithAdapterFix(lType, expression)); - QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createWrapWithOptionalFix(lType, expression)); - QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createWrapExpressionFix(lType, expression)); - QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createWrapStringWithFileFix(lType, expression)); - AddTypeArgumentsConditionalFix.register(highlightInfo, expression, lType); - registerCollectionToArrayFixAction(highlightInfo, rType, lType, expression); - } - ChangeNewOperatorTypeFix.register(highlightInfo, expression, lType); - return highlightInfo; - } - - - @Nullable - public static HighlightInfo checkReturnStatementType(@Nonnull PsiReturnStatement statement) { - PsiMethod method = null; - PsiLambdaExpression lambda = null; - PsiElement parent = statement.getParent(); - while (true) { - if (parent instanceof PsiFile) { - break; - } - if (parent instanceof PsiClassInitializer) { - break; - } - if (parent instanceof PsiLambdaExpression) { - lambda = (PsiLambdaExpression)parent; - break; - } - if (parent instanceof PsiMethod) { - method = (PsiMethod)parent; - break; - } - parent = parent.getParent(); - } - if (parent instanceof PsiCodeFragment) { - return null; - } - String description; - HighlightInfo errorResult = null; - if (method == null && lambda != null) { - //todo check return statements type inside lambda - } - else if (method == null && !(parent instanceof ServerPageFile)) { - description = JavaErrorBundle.message("return.outside.method"); - errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); - } - else { - PsiType returnType = method != null ? method.getReturnType() : null/*JSP page returns void*/; - boolean isMethodVoid = returnType == null || PsiType.VOID.equals(returnType); - final PsiExpression returnValue = statement.getReturnValue(); - if (returnValue != null) { - PsiType valueType = RefactoringChangeUtil.getTypeByExpression(returnValue); - if (isMethodVoid) { - description = JavaErrorBundle.message("return.from.void.method"); - errorResult = - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); - if (valueType != null) { - QuickFixAction.registerQuickFixAction(errorResult, QuickFixFactory.getInstance().createMethodReturnFix(method, valueType, true)); - } + + + @Nullable + public static HighlightInfo checkTernaryOperatorConditionIsBoolean(@Nonnull PsiExpression expression, PsiType type) { + if (expression.getParent() instanceof PsiConditionalExpression && ((PsiConditionalExpression)expression.getParent()).getCondition() == expression && !TypeConversionUtil + .isBooleanType(type)) { + return createIncompatibleTypeHighlightInfo(PsiType.BOOLEAN, type, expression.getTextRange(), 0); } - else { - TextRange textRange = statement.getTextRange(); - errorResult = checkAssignability(returnType, valueType, returnValue, textRange, returnValue.getStartOffsetInParent()); - if (errorResult != null && valueType != null) { - if (!PsiType.VOID.equals(valueType)) { - QuickFixAction.registerQuickFixAction(errorResult, QuickFixFactory.getInstance().createMethodReturnFix(method, valueType, true)); - } - registerChangeParameterClassFix(returnType, valueType, errorResult); - if (returnType instanceof PsiArrayType) { - final PsiType erasedValueType = TypeConversionUtil.erasure(valueType); - if (erasedValueType != null && TypeConversionUtil.isAssignable(((PsiArrayType)returnType).getComponentType(), - erasedValueType)) { - QuickFixAction.registerQuickFixAction(errorResult, QuickFixFactory.getInstance() - .createSurroundWithArrayFix(null, returnValue)); - } - } - registerCollectionToArrayFixAction(errorResult, valueType, returnType, returnValue); - } - } - } - else { - if (!isMethodVoid) { - description = JavaErrorBundle.message("missing.return.value"); - errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) - .range(statement) - .descriptionAndTooltip(description) - .navigationShift(PsiKeyword.RETURN.length()) - .create(); - QuickFixAction.registerQuickFixAction(errorResult, QuickFixFactory.getInstance().createMethodReturnFix(method, PsiType.VOID, true)); - } - } - } - return errorResult; - } - - private static void registerCollectionToArrayFixAction(@Nullable HighlightInfo info, - @Nullable PsiType fromType, - @Nullable PsiType toType, - @Nonnull PsiExpression expression) { - if (toType instanceof PsiArrayType) { - PsiType arrayComponentType = ((PsiArrayType)toType).getComponentType(); - if (!(arrayComponentType instanceof PsiPrimitiveType) && !(PsiUtil.resolveClassInType(arrayComponentType) instanceof PsiTypeParameter) && InheritanceUtil - .isInheritor(fromType, - JavaClassNames.JAVA_UTIL_COLLECTION)) { - PsiType collectionItemType = JavaGenericsUtil.getCollectionItemType(fromType, expression.getResolveScope()); - if (collectionItemType != null && arrayComponentType.isAssignableFrom(collectionItemType)) { - QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createCollectionToArrayFix(expression, (PsiArrayType)toType)); - } - } - } - } - - @Nonnull - public static String getUnhandledExceptionsDescriptor(@Nonnull final Collection unhandled) { - return getUnhandledExceptionsDescriptor(unhandled, null); - } - - @Nonnull - private static String getUnhandledExceptionsDescriptor(@Nonnull final Collection unhandled, @Nullable final String source) { - final String exceptions = formatTypes(unhandled); - return source != null ? JavaErrorBundle.message("unhandled.close.exceptions", - exceptions, - unhandled.size(), - source) : JavaErrorBundle.message("unhandled.exceptions", exceptions, - unhandled.size()); - } - - @Nonnull - private static String formatTypes(@Nonnull Collection unhandled) { - return StringUtil.join(unhandled, JavaHighlightUtil::formatType, ", "); - } - - @Nullable - @RequiredReadAction - public static HighlightInfo checkVariableAlreadyDefined(@Nonnull PsiVariable variable) { - PsiVariable oldVariable = JavaPsiVariableUtil.findPreviousVariableDeclaration(variable); - if (oldVariable != null) { - String description = JavaErrorLocalize.variableAlreadyDefined(variable.getName()).get(); - PsiIdentifier identifier = variable.getNameIdentifier(); - assert identifier != null : variable; - HighlightInfo highlightInfo = - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(identifier).descriptionAndTooltip(description).create(); - if (variable instanceof PsiLocalVariable) { - QuickFixAction.registerQuickFixAction(highlightInfo, - QuickFixFactory.getInstance().createReuseVariableDeclarationFix((PsiLocalVariable)variable)); - } - return highlightInfo; - } - return null; - } - - @Nullable - public static HighlightInfo checkUnderscore(@Nonnull PsiIdentifier identifier, @Nonnull LanguageLevel languageLevel) { - if ("_".equals(identifier.getText())) { - if (languageLevel.isAtLeast(LanguageLevel.JDK_1_9)) { - String text = JavaErrorBundle.message("underscore.identifier.error"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(identifier).descriptionAndTooltip(text).create(); - } - else if (languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) { - PsiElement parent = identifier.getParent(); - if (parent instanceof PsiParameter && ((PsiParameter)parent).getDeclarationScope() instanceof PsiLambdaExpression) { - String text = JavaErrorBundle.message("underscore.lambda.identifier"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(identifier).descriptionAndTooltip(text).create(); - } - } - } - - return null; - } - - @Nonnull - public static String formatClass(@Nonnull PsiClass aClass) { - return formatClass(aClass, true); - } - - @Nonnull - public static String formatClass(@Nonnull PsiClass aClass, boolean fqn) { - return PsiFormatUtil.formatClass(aClass, - PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_ANONYMOUS_CLASS_VERBOSE | (fqn ? PsiFormatUtilBase.SHOW_FQ_NAME : 0)); - } - - @Nonnull - private static String formatField(@Nonnull PsiField field) { - return PsiFormatUtil.formatVariable(field, PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME, PsiSubstitutor.EMPTY); - } - - @Nullable - public static HighlightInfo checkUnhandledExceptions(@Nonnull final PsiElement element, @Nullable TextRange textRange) { - final List unhandledExceptions = ExceptionUtil.getUnhandledExceptions(element); - if (unhandledExceptions.isEmpty()) { - return null; - } - - final HighlightInfoType highlightType = getUnhandledExceptionHighlightType(element); - if (highlightType == null) { - return null; - } - - if (textRange == null) { - textRange = element.getTextRange(); - } - final String description = getUnhandledExceptionsDescriptor(unhandledExceptions); - HighlightInfo errorResult = HighlightInfo.newHighlightInfo(highlightType).range(textRange).descriptionAndTooltip(description).create(); - registerUnhandledExceptionFixes(element, errorResult, unhandledExceptions); - return errorResult; - } - - @Nullable - public static HighlightInfo checkUnhandledCloserExceptions(@Nonnull PsiResourceListElement resource) { - List unhandled = ExceptionUtil.getUnhandledCloserExceptions(resource, null); - if (unhandled.isEmpty()) { - return null; - } - - HighlightInfoType highlightType = getUnhandledExceptionHighlightType(resource); - if (highlightType == null) { - return null; - } - - String description = getUnhandledExceptionsDescriptor(unhandled, "auto-closeable resource"); - HighlightInfo highlight = HighlightInfo.newHighlightInfo(highlightType).range(resource).descriptionAndTooltip(description).create(); - registerUnhandledExceptionFixes(resource, highlight, unhandled); - return highlight; - } - - private static void registerUnhandledExceptionFixes(PsiElement element, HighlightInfo errorResult, List unhandled) { - QuickFixAction.registerQuickFixAction(errorResult, QuickFixFactory.getInstance().createAddExceptionToCatchFix()); - QuickFixAction.registerQuickFixAction(errorResult, QuickFixFactory.getInstance().createAddExceptionToThrowsFix(element)); - QuickFixAction.registerQuickFixAction(errorResult, - QuickFixFactory.getInstance().createAddExceptionFromFieldInitializerToConstructorThrowsFix(element)); - QuickFixAction.registerQuickFixAction(errorResult, QuickFixFactory.getInstance().createSurroundWithTryCatchFix(element)); - if (unhandled.size() == 1) { - QuickFixAction.registerQuickFixAction(errorResult, QuickFixFactory.getInstance().createGeneralizeCatchFix(element, unhandled.get(0))); - } - } - - @Nullable - private static HighlightInfoType getUnhandledExceptionHighlightType(PsiElement element) { - // JSP top level errors are handled by UnhandledExceptionInJSP inspection - if (FileTypeUtils.isInServerPageFile(element)) { - PsiMethod targetMethod = PsiTreeUtil.getParentOfType(element, PsiMethod.class, true, PsiLambdaExpression.class); - if (targetMethod instanceof SyntheticElement) { return null; - } } - return HighlightInfoType.UNHANDLED_EXCEPTION; - } - @Nullable - public static HighlightInfo checkBreakOutsideLoop(@Nonnull PsiBreakStatement statement) { - if (statement.getLabelIdentifier() == null) { - if (new PsiMatcherImpl(statement).ancestor(EnclosingLoopOrSwitchMatcherExpression.INSTANCE).getElement() == null) { - String description = JavaErrorBundle.message("break.outside.switch.or.loop"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); - } - } - else { - // todo labeled + @Nullable + public static HighlightInfo checkStatementPrependedWithCaseInsideSwitch(@Nonnull PsiSwitchStatement statement) { + PsiCodeBlock body = statement.getBody(); + if (body != null) { + PsiElement first = PsiTreeUtil.skipSiblingsForward(body.getLBrace(), PsiWhiteSpace.class, PsiComment.class); + if (first != null && !(first instanceof PsiSwitchLabelStatement) && !PsiUtil.isJavaToken(first, JavaTokenType.RBRACE)) { + String description = JavaErrorBundle.message("statement.must.be.prepended.with.case.label"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(first).descriptionAndTooltip(description).create(); + } + } + + return null; } - return null; - } - @Nullable - public static HighlightInfo checkContinueOutsideLoop(@Nonnull PsiContinueStatement statement) { - if (statement.getLabelIdentifier() == null) { - if (new PsiMatcherImpl(statement).ancestor(EnclosingLoopMatcherExpression.INSTANCE).getElement() == null) { - String description = JavaErrorBundle.message("continue.outside.loop"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); - } - } - else { - PsiStatement exitedStatement = statement.findContinuedStatement(); - if (exitedStatement == null) { - return null; - } - if (!(exitedStatement instanceof PsiForStatement) && !(exitedStatement instanceof PsiWhileStatement) && !(exitedStatement instanceof PsiDoWhileStatement) && !(exitedStatement instanceof - PsiForeachStatement)) { - String description = JavaErrorBundle.message("not.loop.label", statement.getLabelIdentifier().getText()); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); - } - } - return null; - } - - @Nullable - public static HighlightInfo checkIllegalModifierCombination(@Nonnull PsiKeyword keyword, @Nonnull PsiModifierList modifierList) { - @PsiModifier.ModifierConstant String modifier = keyword.getText(); - String incompatible = getIncompatibleModifier(modifier, modifierList); - if (incompatible != null) { - String message = JavaErrorBundle.message("incompatible.modifiers", modifier, incompatible); - HighlightInfo highlightInfo = - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(keyword).descriptionAndTooltip(message).create(); - QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance() - .createModifierListFix(modifierList, modifier, false, false)); - return highlightInfo; - } - - return null; - } - - @Contract("null -> null") - private static Map> getIncompatibleModifierMap(@Nullable PsiElement modifierListOwner) { - if (modifierListOwner == null || PsiUtilCore.hasErrorElementChild(modifierListOwner)) { - return null; - } - if (modifierListOwner instanceof PsiClass) { - return ((PsiClass)modifierListOwner).isInterface() ? ourInterfaceIncompatibleModifiers : ourClassIncompatibleModifiers; - } - if (modifierListOwner instanceof PsiMethod) { - return ourMethodIncompatibleModifiers; - } - if (modifierListOwner instanceof PsiVariable) { - return ourFieldIncompatibleModifiers; - } - if (modifierListOwner instanceof PsiClassInitializer) { - return ourClassInitializerIncompatibleModifiers; - } - if (modifierListOwner instanceof PsiJavaModule) { - return ourModuleIncompatibleModifiers; - } - if (modifierListOwner instanceof PsiRequiresStatement) { - return ourRequiresIncompatibleModifiers; - } - return null; - } - - @Nullable - public static String getIncompatibleModifier(String modifier, @Nonnull PsiModifierList modifierList) { - Map> incompatibleModifierMap = getIncompatibleModifierMap(modifierList.getParent()); - return incompatibleModifierMap != null ? getIncompatibleModifier(modifier, modifierList, incompatibleModifierMap) : null; - } - - @Nullable - public static HighlightInfo checkNotAllowedModifier(@Nonnull PsiKeyword keyword, @Nonnull PsiModifierList modifierList) { - PsiElement modifierOwner = modifierList.getParent(); - Map> incompatibleModifierMap = getIncompatibleModifierMap(modifierOwner); - if (incompatibleModifierMap == null) { - return null; - } - - @PsiModifier.ModifierConstant String modifier = keyword.getText(); - Set incompatibles = incompatibleModifierMap.get(modifier); - PsiElement modifierOwnerParent = - modifierOwner instanceof PsiMember ? ((PsiMember)modifierOwner).getContainingClass() : modifierOwner.getParent(); - if (modifierOwnerParent == null) { - modifierOwnerParent = modifierOwner.getParent(); - } - boolean isAllowed = true; - if (modifierOwner instanceof PsiClass) { - PsiClass aClass = (PsiClass)modifierOwner; - if (aClass.isInterface()) { - if (PsiModifier.STATIC.equals(modifier) || PsiModifier.PRIVATE.equals(modifier) || PsiModifier.PROTECTED.equals(modifier) || PsiModifier.PACKAGE_LOCAL - .equals(modifier)) { - isAllowed = modifierOwnerParent instanceof PsiClass; - } - } - else { - if (PsiModifier.PUBLIC.equals(modifier)) { - isAllowed = - modifierOwnerParent instanceof PsiJavaFile || modifierOwnerParent instanceof PsiClass && (modifierOwnerParent instanceof PsiSyntheticClass || ((PsiClass) - modifierOwnerParent).getQualifiedName() != null); - } - else if (PsiModifier.STATIC.equals(modifier) || PsiModifier.PRIVATE.equals(modifier) || PsiModifier.PROTECTED.equals(modifier) || PsiModifier.PACKAGE_LOCAL - .equals(modifier)) { - isAllowed = - modifierOwnerParent instanceof PsiClass && ((PsiClass)modifierOwnerParent).getQualifiedName() != null || FileTypeUtils.isInServerPageFile( - modifierOwnerParent); - } - - if (aClass.isEnum()) { - isAllowed &= !(PsiModifier.FINAL.equals(modifier) || PsiModifier.ABSTRACT.equals(modifier)); - } - - if (aClass.getContainingClass() instanceof PsiAnonymousClass) { - isAllowed &= !(PsiModifier.PRIVATE.equals(modifier) || PsiModifier.PROTECTED.equals(modifier)); - } - } - } - else if (modifierOwner instanceof PsiMethod) { - PsiMethod method = (PsiMethod)modifierOwner; - isAllowed = !(method.isConstructor() && ourConstructorNotAllowedModifiers.contains(modifier)); - PsiClass containingClass = method.getContainingClass(); - if ((method.hasModifierProperty(PsiModifier.PUBLIC) || method.hasModifierProperty(PsiModifier.PROTECTED)) && method.isConstructor() && containingClass != null && containingClass - .isEnum()) { - isAllowed = false; - } - - if (PsiModifier.PRIVATE.equals(modifier)) { - isAllowed &= - modifierOwnerParent instanceof PsiClass && (!((PsiClass)modifierOwnerParent).isInterface() || PsiUtil.isLanguageLevel9OrHigher( - modifierOwner) && !((PsiClass) - modifierOwnerParent).isAnnotationType()); - } - else if (PsiModifier.STRICTFP.equals(modifier)) { - isAllowed &= - modifierOwnerParent instanceof PsiClass && (!((PsiClass)modifierOwnerParent).isInterface() || PsiUtil.isLanguageLevel8OrHigher( - modifierOwner)); - } - else if (PsiModifier.PROTECTED.equals(modifier) || PsiModifier.TRANSIENT.equals(modifier) || PsiModifier.SYNCHRONIZED.equals(modifier)) { - isAllowed &= modifierOwnerParent instanceof PsiClass && !((PsiClass)modifierOwnerParent).isInterface(); - } - - if (containingClass != null && containingClass.isInterface()) { - isAllowed &= !PsiModifier.NATIVE.equals(modifier); - } - - if (containingClass != null && containingClass.isAnnotationType()) { - isAllowed &= !PsiModifier.STATIC.equals(modifier); - isAllowed &= !PsiModifier.DEFAULT.equals(modifier); - } - } - else if (modifierOwner instanceof PsiField) { - if (PsiModifier.PRIVATE.equals(modifier) || PsiModifier.PROTECTED.equals(modifier) || PsiModifier.TRANSIENT.equals(modifier) || PsiModifier.STRICTFP - .equals(modifier) || PsiModifier - .SYNCHRONIZED.equals(modifier)) { - isAllowed = modifierOwnerParent instanceof PsiClass && !((PsiClass)modifierOwnerParent).isInterface(); - } - } - else if (modifierOwner instanceof PsiClassInitializer) { - isAllowed = PsiModifier.STATIC.equals(modifier); - } - else if (modifierOwner instanceof PsiLocalVariable || modifierOwner instanceof PsiParameter) { - isAllowed = PsiModifier.FINAL.equals(modifier); - } - else if (modifierOwner instanceof PsiReceiverParameter) { - isAllowed = false; - } - - isAllowed &= incompatibles != null; - if (!isAllowed) { - String message = JavaErrorBundle.message("modifier.not.allowed", modifier); - HighlightInfo highlightInfo = - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(keyword).descriptionAndTooltip(message).create(); - QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance() - .createModifierListFix(modifierList, modifier, false, false)); - return highlightInfo; - } - - return null; - } - - @Nullable - public static HighlightInfo checkLiteralExpressionParsingError(@Nonnull PsiLiteralExpression expression, - LanguageLevel level, - PsiFile file) { - PsiElement literal = expression.getFirstChild(); - assert literal instanceof PsiJavaToken : literal; - IElementType type = ((PsiJavaToken)literal).getTokenType(); - if (type == JavaTokenType.TRUE_KEYWORD || type == JavaTokenType.FALSE_KEYWORD || type == JavaTokenType.NULL_KEYWORD) { - return null; - } - - boolean isInt = ElementType.INTEGER_LITERALS.contains(type); - boolean isFP = ElementType.REAL_LITERALS.contains(type); - String text = isInt || isFP ? StringUtil.toLowerCase(literal.getText()) : literal.getText(); - Object value = expression.getValue(); - - if (file != null) { - if (isFP) { - if (text.startsWith(PsiLiteralUtil.HEX_PREFIX)) { - HighlightInfo info = checkFeature(expression, JavaFeature.HEX_FP_LITERALS, level, file); - if (info != null) { - return info; - } + @Nullable + public static HighlightInfo checkAssertOperatorTypes(@Nonnull PsiExpression expression, @Nullable PsiType type) { + if (type == null) { + return null; } - } - if (isInt) { - if (text.startsWith(PsiLiteralUtil.BIN_PREFIX)) { - HighlightInfo info = checkFeature(expression, JavaFeature.BIN_LITERALS, level, file); - if (info != null) { - return info; - } + if (!(expression.getParent() instanceof PsiAssertStatement)) { + return null; } - } - if (isInt || isFP) { - if (text.contains("_")) { - HighlightInfo info = checkFeature(expression, JavaFeature.UNDERSCORES, level, file); - if (info != null) { - return info; - } - info = checkUnderscores(expression, text, isInt); - if (info != null) { - return info; - } - } - } - } - - PsiElement parent = expression.getParent(); - if (type == JavaTokenType.INTEGER_LITERAL) { - String cleanText = StringUtil.replace(text, "_", ""); - //literal 2147483648 may appear only as the operand of the unary negation operator -. - if (!(cleanText.equals(PsiLiteralUtil._2_IN_31) && - parent instanceof PsiPrefixExpression && - ((PsiPrefixExpression)parent).getOperationTokenType() == JavaTokenType.MINUS)) { - if (cleanText.equals(PsiLiteralUtil.HEX_PREFIX)) { - String message = JavaErrorBundle.message("hexadecimal.numbers.must.contain.at.least.one.hexadecimal.digit"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - if (cleanText.equals(PsiLiteralUtil.BIN_PREFIX)) { - String message = JavaErrorBundle.message("binary.numbers.must.contain.at.least.one.hexadecimal.digit"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - if (value == null || cleanText.equals(PsiLiteralUtil._2_IN_31)) { - String message = JavaErrorBundle.message("integer.number.too.large"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - } - } - else if (type == JavaTokenType.LONG_LITERAL) { - String cleanText = StringUtil.replace(StringUtil.trimEnd(text, 'l'), "_", ""); - //literal 9223372036854775808L may appear only as the operand of the unary negation operator -. - if (!(cleanText.equals(PsiLiteralUtil._2_IN_63) && - parent instanceof PsiPrefixExpression && - ((PsiPrefixExpression)parent).getOperationTokenType() == JavaTokenType.MINUS)) { - if (cleanText.equals(PsiLiteralUtil.HEX_PREFIX)) { - String message = JavaErrorBundle.message("hexadecimal.numbers.must.contain.at.least.one.hexadecimal.digit"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - if (cleanText.equals(PsiLiteralUtil.BIN_PREFIX)) { - String message = JavaErrorBundle.message("binary.numbers.must.contain.at.least.one.hexadecimal.digit"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - if (value == null || cleanText.equals(PsiLiteralUtil._2_IN_63)) { - String message = JavaErrorBundle.message("long.number.too.large"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - } - } - else if (isFP) { - if (value == null) { - String message = JavaErrorBundle.message("malformed.floating.point.literal"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - } - else if (type == JavaTokenType.CHARACTER_LITERAL) { - if (value == null) { - if (!StringUtil.startsWithChar(text, '\'')) { - return null; - } - if (!StringUtil.endsWithChar(text, '\'') || text.length() == 1) { - String message = JavaErrorBundle.message("unclosed.char.literal"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - text = text.substring(1, text.length() - 1); - - CharSequence chars = CodeInsightUtilCore.parseStringCharacters(text, null); - if (chars == null) { - String message = JavaErrorBundle.message("illegal.escape.character.in.character.literal"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - int length = chars.length(); - if (length > 1) { - String message = JavaErrorBundle.message("too.many.characters.in.character.literal"); - HighlightInfo info = - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createConvertToStringLiteralAction()); - return info; - } - else if (length == 0) { - String message = JavaErrorBundle.message("empty.character.literal"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - } - } - else if (type == JavaTokenType.STRING_LITERAL || type == JavaTokenType.TEXT_BLOCK_LITERAL) { - if (type == JavaTokenType.STRING_LITERAL) { - if (value == null) { - for (PsiElement element = expression.getFirstChild(); element != null; element = element.getNextSibling()) { - if (element instanceof OuterLanguageElement) { - return null; - } - } - - if (!StringUtil.startsWithChar(text, '\"')) { - return null; - } - if (StringUtil.endsWithChar(text, '\"')) { - if (text.length() == 1) { - String message = JavaErrorBundle.message("illegal.line.end.in.string.literal"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - text = text.substring(1, text.length() - 1); - } - else { - String message = JavaErrorBundle.message("illegal.line.end.in.string.literal"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - - if (CodeInsightUtilCore.parseStringCharacters(text, null) == null) { - String message = JavaErrorBundle.message("illegal.escape.character.in.string.literal"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - } - } - else { - if (value == null) { - if (!text.endsWith("\"\"\"")) { - String message = JavaErrorBundle.message("text.block.unclosed"); - int p = expression.getTextRange().getEndOffset(); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(p, p).endOfLine().descriptionAndTooltip(message).create(); - } - else { - StringBuilder chars = new StringBuilder(text.length()); - int[] offsets = new int[text.length() + 1]; - boolean success = CodeInsightUtilCore.parseStringCharacters(text, chars, offsets); - if (!success) { - String message = JavaErrorBundle.message("illegal.escape.character.in.string.literal"); - TextRange textRange = chars.length() < text.length() - 1 ? new TextRange(offsets[chars.length()], offsets[chars.length() + 1]) - : expression.getTextRange(); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) - .range(expression, textRange) - .descriptionAndTooltip(message).create(); - } - else { - String message = JavaErrorBundle.message("text.block.new.line"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); + PsiAssertStatement assertStatement = (PsiAssertStatement)expression.getParent(); + if (expression == assertStatement.getAssertCondition() && !TypeConversionUtil.isBooleanType(type)) { + // addTypeCast quickfix is not applicable here since no type can be cast to boolean + HighlightInfo highlightInfo = createIncompatibleTypeHighlightInfo(PsiType.BOOLEAN, type, expression.getTextRange(), 0); + if (expression instanceof PsiAssignmentExpression && ((PsiAssignmentExpression)expression).getOperationTokenType() == JavaTokenType.EQ) { + QuickFixAction.registerQuickFixAction( + highlightInfo, + QuickFixFactory.getInstance().createAssignmentToComparisonFix((PsiAssignmentExpression)expression) + ); } - } + return highlightInfo; } - else { - if (file != null && containsUnescaped(text, "\\\n")) { - HighlightInfo info = checkFeature(expression, JavaFeature.TEXT_BLOCK_ESCAPES, level, file); - if (info != null) { - return info; - } - } + if (expression == assertStatement.getAssertDescription() && TypeConversionUtil.isVoidType(type)) { + String description = JavaErrorBundle.message("void.type.is.not.allowed"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create(); } - } - if (file != null && containsUnescaped(text, "\\s")) { - HighlightInfo info = checkFeature(expression, JavaFeature.TEXT_BLOCK_ESCAPES, level, file); - if (info != null) { - return info; - } - } - } - - if (value instanceof Float) { - Float number = (Float)value; - if (number.isInfinite()) { - String message = JavaErrorBundle.message("floating.point.number.too.large"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - if (number.floatValue() == 0 && !TypeConversionUtil.isFPZero(text)) { - String message = JavaErrorBundle.message("floating.point.number.too.small"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - } - else if (value instanceof Double) { - Double number = (Double)value; - if (number.isInfinite()) { - String message = JavaErrorBundle.message("floating.point.number.too.large"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - if (number.doubleValue() == 0 && !TypeConversionUtil.isFPZero(text)) { - String message = JavaErrorBundle.message("floating.point.number.too.small"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - } - - return null; - } - - private static final Pattern FP_LITERAL_PARTS = - Pattern.compile("(?:" + "(?:0x([_\\p{XDigit}]*)\\.?([_\\p{XDigit}]*)p[+-]?([_\\d]*))" + "|" + "(?:([_\\d]*)\\.?([_\\d]*)e?[+-]?([_\\d]*))" + ")" + - "[fd]?"); - - private static boolean containsUnescaped(@Nonnull String text, @Nonnull String subText) { - int start = 0; - while ((start = StringUtil.indexOf(text, subText, start)) != -1) { - int nSlashes = 0; - for (int pos = start - 1; pos >= 0; pos--) { - if (text.charAt(pos) != '\\') { - break; - } - nSlashes++; - } - if (nSlashes % 2 == 0) { - return true; - } - start += subText.length(); - } - return false; - } - - private static HighlightInfo checkUnderscores(@Nonnull PsiElement expression, @Nonnull String text, boolean isInt) { - String[] parts = ArrayUtil.EMPTY_STRING_ARRAY; - - if (isInt) { - int start = 0; - if (text.startsWith(PsiLiteralUtil.HEX_PREFIX) || text.startsWith(PsiLiteralUtil.BIN_PREFIX)) { - start += 2; - } - int end = text.length(); - if (StringUtil.endsWithChar(text, 'l')) { - --end; - } - parts = new String[]{text.substring(start, end)}; - } - else { - Matcher matcher = FP_LITERAL_PARTS.matcher(text); - if (matcher.matches()) { - parts = new String[matcher.groupCount()]; - for (int i = 0; i < matcher.groupCount(); i++) { - parts[i] = matcher.group(i + 1); - } - } - } - - for (String part : parts) { - if (part != null && (StringUtil.startsWithChar(part, '_') || StringUtil.endsWithChar(part, '_'))) { - String message = JavaErrorBundle.message("illegal.underscore"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - } - - return null; - } - - @Nullable - public static HighlightInfo checkMustBeBoolean(@Nonnull PsiExpression expr, PsiType type) { - PsiElement parent = expr.getParent(); - if (parent instanceof PsiIfStatement || parent instanceof PsiWhileStatement || parent instanceof PsiForStatement && expr.equals(((PsiForStatement)parent) - .getCondition()) || parent instanceof - PsiDoWhileStatement && expr.equals(((PsiDoWhileStatement)parent).getCondition())) { - if (expr.getNextSibling() instanceof PsiErrorElement) { return null; - } + } - if (!TypeConversionUtil.isBooleanType(type)) { - final HighlightInfo info = createIncompatibleTypeHighlightInfo(PsiType.BOOLEAN, type, expr.getTextRange(), 0); - if (expr instanceof PsiMethodCallExpression) { - final PsiMethodCallExpression methodCall = (PsiMethodCallExpression)expr; - final PsiMethod method = methodCall.resolveMethod(); - if (method != null && PsiType.VOID.equals(method.getReturnType())) { - QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createMethodReturnFix(method, PsiType.BOOLEAN, true)); - } + @Nullable + public static HighlightInfo checkSynchronizedExpressionType( + @Nonnull PsiExpression expression, + @Nullable PsiType type, + @Nonnull PsiFile containingFile + ) { + if (type == null) { + return null; } - else if (expr instanceof PsiAssignmentExpression && ((PsiAssignmentExpression)expr).getOperationTokenType() == JavaTokenType.EQ) { - QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createAssignmentToComparisonFix((PsiAssignmentExpression)expr)); + if (expression.getParent() instanceof PsiSynchronizedStatement) { + PsiSynchronizedStatement synchronizedStatement = (PsiSynchronizedStatement)expression.getParent(); + if (expression == synchronizedStatement.getLockExpression() && (type instanceof PsiPrimitiveType + || TypeConversionUtil.isNullType(type))) { + PsiClassType objectType = PsiType.getJavaLangObject(containingFile.getManager(), expression.getResolveScope()); + return createIncompatibleTypeHighlightInfo(objectType, type, expression.getTextRange(), 0); + } } - return info; - } + return null; } - return null; - } - - @Nonnull - public static Set collectUnhandledExceptions(@Nonnull final PsiTryStatement statement) { - final Set thrownTypes = new HashSet<>(); - final PsiCodeBlock tryBlock = statement.getTryBlock(); - if (tryBlock != null) { - thrownTypes.addAll(ExceptionUtil.collectUnhandledExceptions(tryBlock, tryBlock)); + @Nullable + public static HighlightInfo checkConditionalExpressionBranchTypesMatch(@Nonnull final PsiExpression expression, PsiType type) { + PsiElement parent = expression.getParent(); + if (!(parent instanceof PsiConditionalExpression)) { + return null; + } + PsiConditionalExpression conditionalExpression = (PsiConditionalExpression)parent; + // check else branches only + if (conditionalExpression.getElseExpression() != expression) { + return null; + } + final PsiExpression thenExpression = conditionalExpression.getThenExpression(); + assert thenExpression != null; + PsiType thenType = thenExpression.getType(); + if (thenType == null || type == null) { + return null; + } + if (conditionalExpression.getType() == null) { + if (PsiUtil.isLanguageLevel8OrHigher(conditionalExpression) && PsiPolyExpressionUtil.isPolyExpression(conditionalExpression)) { + return null; + } + // cannot derive type of conditional expression + // elseType will never be cast-able to thenType, so no quick fix here + return createIncompatibleTypeHighlightInfo(thenType, type, expression.getTextRange(), 0); + } + return null; } - final PsiResourceList resources = statement.getResourceList(); - if (resources != null) { - thrownTypes.addAll(ExceptionUtil.collectUnhandledExceptions(resources, resources)); + @SuppressWarnings("StringContatenationInLoop") + public static HighlightInfo createIncompatibleTypeHighlightInfo( + final PsiType lType, + final PsiType rType, + @Nonnull final TextRange textRange, + int navigationShift + ) { + PsiType lType1 = lType; + PsiType rType1 = rType; + PsiTypeParameter[] lTypeParams = PsiTypeParameter.EMPTY_ARRAY; + PsiSubstitutor lTypeSubstitutor = PsiSubstitutor.EMPTY; + if (lType1 instanceof PsiClassType) { + PsiClassType.ClassResolveResult resolveResult = ((PsiClassType)lType1).resolveGenerics(); + lTypeSubstitutor = resolveResult.getSubstitutor(); + PsiClass psiClass = resolveResult.getElement(); + if (psiClass instanceof PsiAnonymousClass) { + lType1 = ((PsiAnonymousClass)psiClass).getBaseClassType(); + resolveResult = ((PsiClassType)lType1).resolveGenerics(); + lTypeSubstitutor = resolveResult.getSubstitutor(); + psiClass = resolveResult.getElement(); + } + lTypeParams = psiClass == null ? PsiTypeParameter.EMPTY_ARRAY : psiClass.getTypeParameters(); + } + PsiTypeParameter[] rTypeParams = PsiTypeParameter.EMPTY_ARRAY; + PsiSubstitutor rTypeSubstitutor = PsiSubstitutor.EMPTY; + if (rType1 instanceof PsiClassType) { + PsiClassType.ClassResolveResult resolveResult = ((PsiClassType)rType1).resolveGenerics(); + rTypeSubstitutor = resolveResult.getSubstitutor(); + PsiClass psiClass = resolveResult.getElement(); + if (psiClass instanceof PsiAnonymousClass) { + rType1 = ((PsiAnonymousClass)psiClass).getBaseClassType(); + resolveResult = ((PsiClassType)rType1).resolveGenerics(); + rTypeSubstitutor = resolveResult.getSubstitutor(); + psiClass = resolveResult.getElement(); + } + rTypeParams = psiClass == null ? PsiTypeParameter.EMPTY_ARRAY : psiClass.getTypeParameters(); + } + + int typeParamColumns = Math.max(lTypeParams.length, rTypeParams.length); + @Language("HTML") @NonNls String requiredRow = ""; + @Language("HTML") @NonNls String foundRow = ""; + for (int i = 0; i < typeParamColumns; i++) { + PsiTypeParameter lTypeParameter = i >= lTypeParams.length ? null : lTypeParams[i]; + PsiTypeParameter rTypeParameter = i >= rTypeParams.length ? null : rTypeParams[i]; + PsiType lSubstitutedType = lTypeParameter == null ? null : lTypeSubstitutor.substitute(lTypeParameter); + PsiType rSubstitutedType = rTypeParameter == null ? null : rTypeSubstitutor.substitute(rTypeParameter); + boolean matches = Comparing.equal(lSubstitutedType, rSubstitutedType); + @NonNls String openBrace = i == 0 ? "<" : ""; + @NonNls String closeBrace = i == typeParamColumns - 1 ? ">" : ","; + requiredRow += "" + (lTypeParams.length == 0 ? "" : openBrace) + + redIfNotMatch(lSubstitutedType, matches) + + (i < lTypeParams.length ? closeBrace : "") + ""; + foundRow += "" + (rTypeParams.length == 0 ? "" : openBrace) + + redIfNotMatch(rSubstitutedType, matches) + + (i < rTypeParams.length ? closeBrace : "") + ""; + } + PsiType lRawType = lType1 instanceof PsiClassType ? ((PsiClassType)lType1).rawType() : lType1; + PsiType rRawType = rType1 instanceof PsiClassType ? ((PsiClassType)rType1).rawType() : rType1; + boolean assignable = lRawType == null || rRawType == null || TypeConversionUtil.isAssignable(lRawType, rRawType); + + String toolTip = JavaErrorBundle.message( + "incompatible.types.html.tooltip", + redIfNotMatch(lRawType, assignable), + requiredRow, + redIfNotMatch(rRawType, assignable), + foundRow + ); + + String description = + JavaErrorBundle.message("incompatible.types", JavaHighlightUtil.formatType(lType1), JavaHighlightUtil.formatType(rType1)); + + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) + .range(textRange) + .description(description) + .escapedToolTip(toolTip) + .navigationShift(navigationShift) + .create(); + } + + @Nullable + public static HighlightInfo checkSingleImportClassConflict( + @Nonnull PsiImportStatement statement, + @Nonnull Map> importedClasses, + @Nonnull PsiFile containingFile + ) { + if (statement.isOnDemand()) { + return null; + } + PsiElement element = statement.resolve(); + if (element instanceof PsiClass) { + String name = ((PsiClass)element).getName(); + Pair imported = importedClasses.get(name); + PsiClass importedClass = imported == null ? null : imported.getSecond(); + if (importedClass != null && !containingFile.getManager().areElementsEquivalent(importedClass, element)) { + String description = JavaErrorBundle.message("single.import.class.conflict", formatClass(importedClass)); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); + } + importedClasses.put(name, Pair.create(null, (PsiClass)element)); + } + return null; } - return thrownTypes; - } - @Nonnull - public static List checkExceptionThrownInTry(@Nonnull final PsiParameter parameter, - @Nonnull final Set thrownTypes) { - final PsiElement declarationScope = parameter.getDeclarationScope(); - if (!(declarationScope instanceof PsiCatchSection)) { - return Collections.emptyList(); + @NonNls + private static String redIfNotMatch(PsiType type, boolean matches) { + if (matches) { + return getFQName(type, false); + } + String color = UIUtil.isUnderDarcula() ? "FF6B68" : "red"; + return "" + getFQName(type, true) + ""; } - final PsiType caughtType = parameter.getType(); - if (caughtType instanceof PsiClassType) { - HighlightInfo info = checkSimpleCatchParameter(parameter, thrownTypes, (PsiClassType)caughtType); - return info == null ? Collections.emptyList() : Collections.singletonList(info); - } - if (caughtType instanceof PsiDisjunctionType) { - return checkMultiCatchParameter(parameter, thrownTypes); + private static String getFQName(@Nullable PsiType type, boolean longName) { + if (type == null) { + return ""; + } + return XmlStringUtil.escapeString(longName ? type.getInternalCanonicalText() : type.getPresentableText()); } - return Collections.emptyList(); - } - - @Nullable - private static HighlightInfo checkSimpleCatchParameter(@Nonnull final PsiParameter parameter, - @Nonnull final Collection thrownTypes, - @Nonnull final PsiClassType caughtType) { - if (ExceptionUtil.isUncheckedExceptionOrSuperclass(caughtType)) { - return null; - } - for (PsiClassType exceptionType : thrownTypes) { - if (exceptionType.isAssignableFrom(caughtType) || caughtType.isAssignableFrom(exceptionType)) { - return null; - } - } - - final String description = JavaErrorBundle.message("exception.never.thrown.try", JavaHighlightUtil.formatType(caughtType)); - final HighlightInfo errorResult = - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(parameter).descriptionAndTooltip(description).create(); - QuickFixAction.registerQuickFixAction(errorResult, QuickFixFactory.getInstance().createDeleteCatchFix(parameter)); - return errorResult; - } - - @Nonnull - private static List checkMultiCatchParameter(@Nonnull final PsiParameter parameter, - @Nonnull final Collection thrownTypes) { - final List typeElements = PsiUtil.getParameterTypeElements(parameter); - final List highlights = new ArrayList<>(typeElements.size()); - - for (final PsiTypeElement typeElement : typeElements) { - final PsiType catchType = typeElement.getType(); - if (catchType instanceof PsiClassType && ExceptionUtil.isUncheckedExceptionOrSuperclass((PsiClassType)catchType)) { - continue; - } - - boolean used = false; - for (PsiClassType exceptionType : thrownTypes) { - if (exceptionType.isAssignableFrom(catchType) || catchType.isAssignableFrom(exceptionType)) { - used = true; - break; - } - } - if (!used) { - final String description = JavaErrorBundle.message("exception.never.thrown.try", JavaHighlightUtil.formatType(catchType)); - final HighlightInfo highlight = - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(description).create(); - QuickFixAction.registerQuickFixAction(highlight, QuickFixFactory.getInstance().createDeleteMultiCatchFix(typeElement)); - highlights.add(highlight); - } - } - - return highlights; - } - - - @Nonnull - public static Collection checkWithImprovedCatchAnalysis(@Nonnull PsiParameter parameter, - @Nonnull Collection thrownInTryStatement, - @Nonnull PsiFile containingFile) { - final PsiElement scope = parameter.getDeclarationScope(); - if (!(scope instanceof PsiCatchSection)) { - return Collections.emptyList(); - } - - final PsiCatchSection catchSection = (PsiCatchSection)scope; - final PsiCatchSection[] allCatchSections = catchSection.getTryStatement().getCatchSections(); - final int idx = ArrayUtil.find(allCatchSections, catchSection); - if (idx <= 0) { - return Collections.emptyList(); - } - - final Collection thrownTypes = new HashSet<>(thrownInTryStatement); - final PsiManager manager = containingFile.getManager(); - final GlobalSearchScope parameterResolveScope = parameter.getResolveScope(); - thrownTypes.add(PsiType.getJavaLangError(manager, parameterResolveScope)); - thrownTypes.add(PsiType.getJavaLangRuntimeException(manager, parameterResolveScope)); - final Collection result = ContainerUtil.newArrayList(); - - final List parameterTypeElements = PsiUtil.getParameterTypeElements(parameter); - final boolean isMultiCatch = parameterTypeElements.size() > 1; - for (PsiTypeElement catchTypeElement : parameterTypeElements) { - final PsiType catchType = catchTypeElement.getType(); - if (ExceptionUtil.isGeneralExceptionType(catchType)) { - continue; - } - - // collect exceptions which are caught by this type - Collection caught = ContainerUtil.findAll(thrownTypes, catchType::isAssignableFrom); - if (caught.isEmpty()) { - continue; - } - final Collection caughtCopy = new HashSet<>(caught); - - // exclude all which are caught by previous catch sections - for (int i = 0; i < idx; i++) { - final PsiParameter prevCatchParameter = allCatchSections[i].getParameter(); - if (prevCatchParameter == null) { - continue; - } - for (PsiTypeElement prevCatchTypeElement : PsiUtil.getParameterTypeElements(prevCatchParameter)) { - final PsiType prevCatchType = prevCatchTypeElement.getType(); - caught.removeIf(prevCatchType::isAssignableFrom); - if (caught.isEmpty()) { - break; - } - } - } - - // check & warn - if (caught.isEmpty()) { - final String message = JavaErrorBundle.message("exception.already.caught.warn", formatTypes(caughtCopy), caughtCopy.size()); - final HighlightInfo highlightInfo = - HighlightInfo.newHighlightInfo(HighlightInfoType.WARNING).range(catchSection).descriptionAndTooltip(message).create(); - if (isMultiCatch) { - QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createDeleteMultiCatchFix(catchTypeElement)); + @Nullable + public static HighlightInfo checkMustBeThrowable(@Nullable PsiType type, @Nonnull PsiElement context, boolean addCastIntention) { + if (type == null) { + return null; } - else { - QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createDeleteCatchFix(parameter)); - } - result.add(highlightInfo); - } - } - - return result; - } - - - @Nullable - public static HighlightInfo checkNotAStatement(@Nonnull PsiStatement statement) { - if (!PsiUtil.isStatement(statement) && !PsiUtilCore.hasErrorElementChild(statement)) { - boolean isDeclarationNotAllowed = false; - if (statement instanceof PsiDeclarationStatement) { - final PsiElement parent = statement.getParent(); - isDeclarationNotAllowed = parent instanceof PsiIfStatement || parent instanceof PsiLoopStatement; - } - - String description = JavaErrorBundle.message(isDeclarationNotAllowed ? "declaration.not.allowed" : "not.a.statement"); - HighlightInfo error = - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); - if (statement instanceof PsiExpressionStatement) { - QuickFixAction.registerQuickFixAction(error, - QuickFixFactory.getInstance() - .createDeleteSideEffectAwareFix((PsiExpressionStatement)statement)); - } - return error; - } - return null; - } - - @Nullable - public static HighlightInfo checkSwitchBlockStatements(@Nonnull PsiSwitchBlock switchBlock, - @Nonnull LanguageLevel languageLevel, - @Nonnull PsiFile file) { - PsiCodeBlock body = switchBlock.getBody(); - if (body != null) { - PsiElement first = PsiTreeUtil.skipWhitespacesAndCommentsForward(body.getLBrace()); - if (first != null && !(first instanceof PsiSwitchLabelStatementBase) && !PsiUtil.isJavaToken(first, JavaTokenType.RBRACE)) { - String description = JavaErrorBundle.message("statement.must.be.prepended.with.case.label"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(first).descriptionAndTooltip(description).create(); - } - - PsiElement element = first; - PsiStatement alien = null; - boolean classicLabels = false; - boolean enhancedLabels = false; - boolean levelChecked = false; - while (element != null && !PsiUtil.isJavaToken(element, JavaTokenType.RBRACE)) { - if (element instanceof PsiSwitchLabeledRuleStatement) { - if (!levelChecked) { - HighlightInfo info = checkFeature(element, JavaFeature.ENHANCED_SWITCH, languageLevel, file); - if (info != null) { - return info; - } - levelChecked = true; - } - if (classicLabels) { - alien = (PsiStatement)element; - break; - } - enhancedLabels = true; - } - else if (element instanceof PsiStatement) { - if (enhancedLabels) { - alien = (PsiStatement)element; - break; - } - classicLabels = true; - } - - if (!levelChecked && element instanceof PsiSwitchLabelStatementBase) { - PsiExpressionList values = ((PsiSwitchLabelStatementBase)element).getCaseValues(); - if (values != null && values.getExpressionCount() > 1) { - HighlightInfo info = checkFeature(values, JavaFeature.ENHANCED_SWITCH, languageLevel, file); - if (info != null) { - return info; + PsiElementFactory factory = JavaPsiFacade.getInstance(context.getProject()).getElementFactory(); + PsiClassType throwable = factory.createTypeByFQClassName("java.lang.Throwable", context.getResolveScope()); + if (!TypeConversionUtil.isAssignable(throwable, type)) { + HighlightInfo highlightInfo = createIncompatibleTypeHighlightInfo(throwable, type, context.getTextRange(), 0); + if (addCastIntention && TypeConversionUtil.areTypesConvertible(type, throwable)) { + if (context instanceof PsiExpression) { + QuickFixAction.registerQuickFixAction( + highlightInfo, + QuickFixFactory.getInstance().createAddTypeCastFix(throwable, (PsiExpression)context) + ); + } } - levelChecked = true; - } - } - element = PsiTreeUtil.skipWhitespacesAndCommentsForward(element); - } - if (alien != null) { - if (enhancedLabels && !(alien instanceof PsiSwitchLabelStatementBase)) { - PsiSwitchLabeledRuleStatement previousRule = PsiTreeUtil.getPrevSiblingOfType(alien, PsiSwitchLabeledRuleStatement.class); - String description = JavaErrorBundle.message("statement.must.be.prepended.with.case.label"); - HighlightInfo info = - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(alien).descriptionAndTooltip(description).create(); - if (previousRule != null) { - QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance() - .createWrapSwitchRuleStatementsIntoBlockFix(previousRule)); - } - return info; - } - String description = JavaErrorBundle.message("different.case.kinds.in.switch"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(alien).descriptionAndTooltip(description).create(); - } - } - - return null; - } - - @Nullable - public static HighlightInfo checkSwitchSelectorType(@Nonnull PsiSwitchBlock switchBlock, @Nonnull LanguageLevel level) { - PsiExpression expression = switchBlock.getExpression(); - if (expression == null) { - return null; - } - PsiType type = expression.getType(); - if (type == null) { - return null; - } - - SelectorKind kind = getSwitchSelectorKind(type); - if (kind == SelectorKind.INT) { - return null; - } - - LanguageLevel requiredLevel = null; - if (kind == SelectorKind.ENUM) { - requiredLevel = LanguageLevel.JDK_1_5; - } - if (kind == SelectorKind.STRING) { - requiredLevel = LanguageLevel.JDK_1_7; - } - - if (kind == null || requiredLevel != null && !level.isAtLeast(requiredLevel)) { - boolean is7 = level.isAtLeast(LanguageLevel.JDK_1_7); - String expected = JavaErrorBundle.message(is7 ? "valid.switch.1_7.selector.types" : "valid.switch.selector.types"); - String message = JavaErrorBundle.message("incompatible.types", expected, JavaHighlightUtil.formatType(type)); - HighlightInfo info = - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - if (switchBlock instanceof PsiSwitchStatement) { - QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createConvertSwitchToIfIntention((PsiSwitchStatement)switchBlock)); - } - if (PsiType.LONG.equals(type) || PsiType.FLOAT.equals(type) || PsiType.DOUBLE.equals(type)) { - QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createAddTypeCastFix(PsiType.INT, expression)); - QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createWrapWithAdapterFix(PsiType.INT, expression)); - } - if (requiredLevel != null) { - QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createIncreaseLanguageLevelFix(requiredLevel)); - } - return info; - } - - PsiClass member = PsiUtil.resolveClassInClassTypeOnly(type); - if (member != null && !PsiUtil.isAccessible(member.getProject(), member, expression, null)) { - String className = PsiFormatUtil.formatClass(member, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_FQ_NAME); - String message = JavaErrorBundle.message("inaccessible.type", className); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - - return null; - } - - private enum SelectorKind { - INT, - ENUM, - STRING - } - - private static SelectorKind getSwitchSelectorKind(@Nonnull PsiType type) { - if (TypeConversionUtil.getTypeRank(type) <= TypeConversionUtil.INT_RANK) { - return SelectorKind.INT; - } - - PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(type); - if (psiClass != null) { - if (psiClass.isEnum()) { - return SelectorKind.ENUM; - } - if (Comparing.strEqual(psiClass.getQualifiedName(), JavaClassNames.JAVA_LANG_STRING)) { - return SelectorKind.STRING; - } - } - - return null; - } - - @Nullable - public static HighlightInfo checkPolyadicOperatorApplicable(@Nonnull PsiPolyadicExpression expression) { - PsiExpression[] operands = expression.getOperands(); - - PsiType lType = operands[0].getType(); - IElementType operationSign = expression.getOperationTokenType(); - for (int i = 1; i < operands.length; i++) { - PsiExpression operand = operands[i]; - PsiType rType = operand.getType(); - if (!TypeConversionUtil.isBinaryOperatorApplicable(operationSign, lType, rType, false)) { - PsiJavaToken token = expression.getTokenBeforeOperand(operand); - String message = JavaErrorBundle.message("binary.operator.not.applicable", - token.getText(), - JavaHighlightUtil.formatType(lType), - JavaHighlightUtil.formatType(rType)); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); - } - lType = TypeConversionUtil.calcTypeForBinaryExpression(lType, rType, operationSign, true); - } - - return null; - } - - - @Nullable - public static HighlightInfo checkUnaryOperatorApplicable(@Nullable PsiJavaToken token, @Nullable PsiExpression expression) { - if (token != null && expression != null && !TypeConversionUtil.isUnaryOperatorApplicable(token, expression)) { - PsiType type = expression.getType(); - if (type == null) { + final PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(type); + if (aClass != null) { + QuickFixAction.registerQuickFixAction( + highlightInfo, + QuickFixFactory.getInstance().createExtendsListFix(aClass, throwable, true) + ); + } + return highlightInfo; + } return null; - } - String message = JavaErrorBundle.message("unary.operator.not.applicable", token.getText(), JavaHighlightUtil.formatType(type)); - - PsiElement parentExpr = token.getParent(); - HighlightInfo highlightInfo = - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(parentExpr).descriptionAndTooltip(message).create(); - if (parentExpr instanceof PsiPrefixExpression && token.getTokenType() == JavaTokenType.EXCL) { - QuickFixAction.registerQuickFixAction(highlightInfo, - QuickFixFactory.getInstance().createNegationBroadScopeFix((PsiPrefixExpression)parentExpr)); - } - return highlightInfo; - } - return null; - } - - @Nullable - public static HighlightInfo checkThisOrSuperExpressionInIllegalContext(@Nonnull PsiExpression expr, - @Nullable PsiJavaCodeReferenceElement qualifier, - @Nonnull LanguageLevel languageLevel) { - if (expr instanceof PsiSuperExpression) { - final PsiElement parent = expr.getParent(); - if (!(parent instanceof PsiReferenceExpression)) { - // like in 'Object o = super;' - final int o = expr.getTextRange().getEndOffset(); - String description = JavaErrorBundle.message("dot.expected.after.super.or.this"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(o, o + 1).descriptionAndTooltip(description).create(); - } - } - - PsiClass aClass; - if (qualifier != null) { - PsiElement resolved = qualifier.advancedResolve(true).getElement(); - if (resolved != null && !(resolved instanceof PsiClass)) { - String description = JavaErrorBundle.message("class.expected"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(qualifier).descriptionAndTooltip(description).create(); - } - aClass = (PsiClass)resolved; - } - else { - aClass = PsiTreeUtil.getParentOfType(expr, PsiClass.class); - if (aClass instanceof PsiAnonymousClass && PsiTreeUtil.isAncestor(((PsiAnonymousClass)aClass).getArgumentList(), expr, false)) { - aClass = PsiTreeUtil.getParentOfType(aClass, PsiClass.class, true); - } - } - if (aClass == null) { - return null; - } - - if (!InheritanceUtil.hasEnclosingInstanceInScope(aClass, expr, false, false)) { - if (!resolvesToImmediateSuperInterface(expr, qualifier, aClass, languageLevel)) { - return HighlightClassUtil.reportIllegalEnclosingUsage(expr, null, aClass, expr); - } - - if (expr instanceof PsiSuperExpression) { - final PsiElement resolved = ((PsiReferenceExpression)expr.getParent()).resolve(); - //15.11.2 - //The form T.super.Identifier refers to the field named Identifier of the lexically enclosing instance corresponding to T, - //but with that instance viewed as an instance of the superclass of T. - if (resolved instanceof PsiField) { - String description = JavaErrorBundle.message("is.not.an.enclosing.class", formatClass(aClass)); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expr).descriptionAndTooltip(description).create(); - } - } - } - - if (qualifier != null && aClass.isInterface() && expr instanceof PsiSuperExpression && languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) { - //15.12.1 for method invocation expressions; 15.13 for method references - //If TypeName denotes an interface, I, then let T be the type declaration immediately enclosing the method reference expression. - //It is a compile-time error if I is not a direct superinterface of T, - //or if there exists some other direct superclass or direct superinterface of T, J, such that J is a subtype of I. - final PsiClass classT = PsiTreeUtil.getParentOfType(expr, PsiClass.class); - if (classT != null) { - final PsiElement parent = expr.getParent(); - final PsiElement resolved = parent instanceof PsiReferenceExpression ? ((PsiReferenceExpression)parent).resolve() : null; - - PsiClass containingClass = - ObjectUtil.notNull(resolved instanceof PsiMethod ? ((PsiMethod)resolved).getContainingClass() : null, aClass); - for (PsiClass superClass : classT.getSupers()) { - if (superClass.isInheritor(containingClass, true)) { - String cause = null; - if (superClass.isInheritor(aClass, true) && superClass.isInterface()) { - cause = "redundant interface " + format(containingClass) + " is extended by "; - } - else if (resolved instanceof PsiMethod && MethodSignatureUtil.findMethodBySuperMethod(superClass, - (PsiMethod)resolved, - true) != resolved) { - cause = "method " + ((PsiMethod)resolved).getName() + " is overridden in "; - } - - if (cause != null) { - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) - .range(qualifier) - .descriptionAndTooltip(JavaErrorBundle.message("bad.qualifier.in.super.method.reference", - cause + formatClass(superClass))) - .create(); - } - } - } - - if (!classT.isInheritor(aClass, false)) { - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) - .range(qualifier) - .descriptionAndTooltip(JavaErrorBundle.message("no.enclosing.instance.in.scope", format(aClass))) - .create(); - } - } - } - - if (expr instanceof PsiThisExpression) { - final PsiMethod psiMethod = PsiTreeUtil.getParentOfType(expr, PsiMethod.class); - if (psiMethod == null || psiMethod.getContainingClass() != aClass && !isInsideDefaultMethod(psiMethod, aClass)) { - if (aClass.isInterface()) { - return thisNotFoundInInterfaceInfo(expr); - } - - if (aClass instanceof PsiAnonymousClass && PsiTreeUtil.isAncestor(((PsiAnonymousClass)aClass).getArgumentList(), expr, true)) { - final PsiClass parentClass = PsiTreeUtil.getParentOfType(aClass, PsiClass.class, true); - if (parentClass != null && parentClass.isInterface()) { - return thisNotFoundInInterfaceInfo(expr); - } - } - } - } - return null; - } - - public static HighlightInfo checkUnqualifiedSuperInDefaultMethod(@Nonnull LanguageLevel languageLevel, - @Nonnull PsiReferenceExpression expr, - PsiExpression qualifier) { - if (languageLevel.isAtLeast(LanguageLevel.JDK_1_8) && qualifier instanceof PsiSuperExpression) { - final PsiMethod method = PsiTreeUtil.getParentOfType(expr, PsiMethod.class); - if (method != null && method.hasModifierProperty(PsiModifier.DEFAULT) && ((PsiSuperExpression)qualifier).getQualifier() == null) { - String description = JavaErrorBundle.message("unqualified.super.disallowed"); - HighlightInfo info = - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expr).descriptionAndTooltip(description).create(); - QualifySuperArgumentFix.registerQuickFixAction((PsiSuperExpression)qualifier, info); - return info; - } - } - return null; - } - - private static boolean isInsideDefaultMethod(PsiMethod method, PsiClass aClass) { - while (method != null && method.getContainingClass() != aClass) { - method = PsiTreeUtil.getParentOfType(method, PsiMethod.class, true); } - return method != null && method.hasModifierProperty(PsiModifier.DEFAULT); - } - - private static HighlightInfo thisNotFoundInInterfaceInfo(@Nonnull PsiExpression expr) { - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) - .range(expr) - .descriptionAndTooltip("Cannot find symbol variable this") - .create(); - } - - private static boolean resolvesToImmediateSuperInterface(@Nonnull PsiExpression expr, - @Nullable PsiJavaCodeReferenceElement qualifier, - @Nonnull PsiClass aClass, - @Nonnull LanguageLevel languageLevel) { - if (!(expr instanceof PsiSuperExpression) || qualifier == null || !languageLevel.isAtLeast(LanguageLevel.JDK_1_8)) { - return false; - } - final PsiType superType = expr.getType(); - if (!(superType instanceof PsiClassType)) { - return false; - } - final PsiClass superClass = ((PsiClassType)superType).resolve(); - return superClass != null && aClass.equals(superClass) && PsiUtil.getEnclosingStaticElement(expr, - PsiTreeUtil.getParentOfType(expr, - PsiClass.class)) == null; - } - - @Nonnull - public static String buildProblemWithStaticDescription(@Nonnull PsiElement refElement) { - String type = FindUsagesProvider.forLanguage(JavaLanguage.INSTANCE).getType(refElement); - String name = HighlightMessageUtil.getSymbolName(refElement, PsiSubstitutor.EMPTY); - return JavaErrorBundle.message("non.static.symbol.referenced.from.static.context", type, name); - } - - public static void registerStaticProblemQuickFixAction(@Nonnull PsiElement refElement, - HighlightInfo errorResult, - @Nonnull PsiJavaCodeReferenceElement place) { - if (refElement instanceof PsiModifierListOwner) { - QuickFixAction.registerQuickFixAction(errorResult, - QuickFixFactory.getInstance().createModifierListFix((PsiModifierListOwner)refElement, - PsiModifier.STATIC, - true, - false)); - } - // make context non static - PsiModifierListOwner staticParent = PsiUtil.getEnclosingStaticElement(place, null); - if (staticParent != null && isInstanceReference(place)) { - QuickFixAction.registerQuickFixAction(errorResult, - QuickFixFactory.getInstance() - .createModifierListFix(staticParent, PsiModifier.STATIC, false, false)); - } - if (place instanceof PsiReferenceExpression && refElement instanceof PsiField) { - QuickFixAction.registerQuickFixAction(errorResult, QuickFixFactory.getInstance().createCreateFieldFromUsageFix((PsiReferenceExpression)place)); - } - } - - private static boolean isInstanceReference(@Nonnull PsiJavaCodeReferenceElement place) { - PsiElement qualifier = place.getQualifier(); - if (qualifier == null) { - return true; - } - if (!(qualifier instanceof PsiJavaCodeReferenceElement)) { - return false; - } - PsiElement q = ((PsiReference)qualifier).resolve(); - if (q instanceof PsiClass) { - return false; - } - if (q != null) { - return true; - } - String qname = ((PsiJavaCodeReferenceElement)qualifier).getQualifiedName(); - return qname == null || !Character.isLowerCase(qname.charAt(0)); - } - - @Nonnull - public static String buildProblemWithAccessDescription(@Nonnull final PsiElement reference, @Nonnull final JavaResolveResult result) { - return buildProblemWithAccessDescription(reference, result, ObjectUtil.notNull(result.getElement())); - } - - @Nonnull - private static String buildProblemWithAccessDescription(@Nonnull final PsiElement reference, - @Nonnull final JavaResolveResult result, - @Nonnull final PsiElement resolved) { - assert resolved instanceof PsiModifierListOwner : resolved; - PsiModifierListOwner refElement = (PsiModifierListOwner)resolved; - String symbolName = HighlightMessageUtil.getSymbolName(refElement, result.getSubstitutor()); - - if (refElement.hasModifierProperty(PsiModifier.PRIVATE)) { - String containerName = getContainerName(refElement, result.getSubstitutor()); - return JavaErrorBundle.message("private.symbol", symbolName, containerName); - } - else if (refElement.hasModifierProperty(PsiModifier.PROTECTED)) { - String containerName = getContainerName(refElement, result.getSubstitutor()); - return JavaErrorBundle.message("protected.symbol", symbolName, containerName); - } - else { - PsiClass packageLocalClass = getPackageLocalClassInTheMiddle(reference); - if (packageLocalClass != null) { - refElement = packageLocalClass; - symbolName = HighlightMessageUtil.getSymbolName(refElement, result.getSubstitutor()); - } - if (refElement.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) || packageLocalClass != null) { - String containerName = getContainerName(refElement, result.getSubstitutor()); - return JavaErrorBundle.message("package.local.symbol", symbolName, containerName); - } - else { - String containerName = getContainerName(refElement, result.getSubstitutor()); - return JavaErrorBundle.message("visibility.access.problem", symbolName, containerName); - } - } - } - - private static PsiElement getContainer(PsiModifierListOwner refElement) { - for (ContainerProvider provider : ContainerProvider.EP_NAME.getExtensions()) { - final PsiElement container = provider.getContainer(refElement); - if (container != null) { - return container; - } - } - return refElement.getParent(); - } - - private static String getContainerName(PsiModifierListOwner refElement, final PsiSubstitutor substitutor) { - final PsiElement container = getContainer(refElement); - return container == null ? "?" : HighlightMessageUtil.getSymbolName(container, substitutor); - } - - @Nullable - public static HighlightInfo checkValidArrayAccessExpression(@Nonnull PsiArrayAccessExpression arrayAccessExpression) { - final PsiExpression arrayExpression = arrayAccessExpression.getArrayExpression(); - final PsiType arrayExpressionType = arrayExpression.getType(); - - if (arrayExpressionType != null && !(arrayExpressionType instanceof PsiArrayType)) { - final String description = JavaErrorBundle.message("array.type.expected", JavaHighlightUtil.formatType(arrayExpressionType)); - final HighlightInfo info = - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(arrayExpression).descriptionAndTooltip(description).create(); - QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createReplaceWithListAccessFix(arrayAccessExpression)); - return info; - } - - final PsiExpression indexExpression = arrayAccessExpression.getIndexExpression(); - return indexExpression != null ? checkAssignability(PsiType.INT, indexExpression.getType(), indexExpression, indexExpression) : null; - } - - - @Nullable - public static HighlightInfo checkCatchParameterIsThrowable(@Nonnull final PsiParameter parameter) { - if (parameter.getDeclarationScope() instanceof PsiCatchSection) { - final PsiType type = parameter.getType(); - return checkMustBeThrowable(type, parameter, true); - } - return null; - } - - @Nullable - public static HighlightInfo checkTryResourceIsAutoCloseable(@Nonnull PsiResourceListElement resource) { - PsiType type = resource.getType(); - if (type == null) { - return null; - } - - PsiElementFactory factory = JavaPsiFacade.getInstance(resource.getProject()).getElementFactory(); - PsiClassType autoCloseable = factory.createTypeByFQClassName(JavaClassNames.JAVA_LANG_AUTO_CLOSEABLE, resource.getResolveScope()); - if (TypeConversionUtil.isAssignable(autoCloseable, type)) { - return null; - } - - return createIncompatibleTypeHighlightInfo(autoCloseable, type, resource.getTextRange(), 0); - } - - @Nullable - public static HighlightInfo checkResourceVariableIsFinal(@Nonnull PsiResourceExpression resource) { - PsiExpression expression = resource.getExpression(); - - if (expression instanceof PsiThisExpression) { - return null; - } - - if (expression instanceof PsiReferenceExpression) { - PsiElement target = ((PsiReferenceExpression)expression).resolve(); - if (target == null) { - return null; - } - - if (target instanceof PsiVariable) { - PsiVariable variable = (PsiVariable)target; - PsiModifierList modifierList = variable.getModifierList(); - if (modifierList != null && modifierList.hasModifierProperty(PsiModifier.FINAL)) { - return null; - } - if (!(variable instanceof PsiField) && HighlightControlFlowUtil.isEffectivelyFinal(variable, - resource, - (PsiJavaCodeReferenceElement)expression)) { - return null; + @Nullable + private static HighlightInfo checkMustBeThrowable(@Nullable PsiClass aClass, @Nonnull PsiElement context) { + if (aClass == null) { + return null; } - } - - String text = JavaErrorBundle.message("resource.variable.must.be.final"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(text).create(); + PsiClassType type = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory().createType(aClass); + return checkMustBeThrowable(type, context, false); } - String text = JavaErrorBundle.message("declaration.or.variable.expected"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(text).create(); - } - @Nonnull - public static Collection checkArrayInitializer(final PsiExpression initializer, PsiType type) { - if (!(initializer instanceof PsiArrayInitializerExpression)) { - return Collections.emptyList(); - } - if (!(type instanceof PsiArrayType)) { - return Collections.emptyList(); + @Nullable + public static HighlightInfo checkLabelDefined(@Nullable PsiIdentifier labelIdentifier, @Nullable PsiStatement exitedStatement) { + if (labelIdentifier == null) { + return null; + } + String label = labelIdentifier.getText(); + if (label == null) { + return null; + } + if (exitedStatement == null) { + String message = JavaErrorBundle.message("unresolved.label", label); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(labelIdentifier).descriptionAndTooltip(message).create(); + } + return null; } - final PsiType componentType = ((PsiArrayType)type).getComponentType(); - final PsiArrayInitializerExpression arrayInitializer = (PsiArrayInitializerExpression)initializer; - boolean arrayTypeFixChecked = false; - VariableArrayTypeFix fix = null; + @Nullable + public static HighlightInfo checkReference( + @Nonnull PsiJavaCodeReferenceElement ref, + @Nonnull JavaResolveResult result, + @Nonnull PsiFile containingFile, + @Nonnull LanguageLevel languageLevel + ) { + PsiElement refName = ref.getReferenceNameElement(); + if (!(refName instanceof PsiIdentifier) && !(refName instanceof PsiKeyword)) { + return null; + } + PsiElement resolved = result.getElement(); - final Collection result = ContainerUtil.newArrayList(); - final PsiExpression[] initializers = arrayInitializer.getInitializers(); - for (PsiExpression expression : initializers) { - final HighlightInfo info = checkArrayInitializerCompatibleTypes(expression, componentType); - if (info != null) { - result.add(info); + HighlightInfo highlightInfo = checkMemberReferencedBeforeConstructorCalled(ref, resolved, containingFile); + if (highlightInfo != null) { + return highlightInfo; + } - if (!arrayTypeFixChecked) { - final PsiType checkResult = JavaHighlightUtil.sameType(initializers); - fix = checkResult != null ? VariableArrayTypeFix.createFix(arrayInitializer, checkResult) : null; - arrayTypeFixChecked = true; + PsiElement refParent = ref.getParent(); + PsiElement granny; + if (refParent instanceof PsiReferenceExpression && (granny = refParent.getParent()) instanceof PsiMethodCallExpression) { + PsiReferenceExpression referenceToMethod = ((PsiMethodCallExpression)granny).getMethodExpression(); + PsiExpression qualifierExpression = referenceToMethod.getQualifierExpression(); + if (qualifierExpression == ref && resolved != null && !(resolved instanceof PsiClass) && !(resolved instanceof PsiVariable)) { + String message = JavaErrorBundle.message("qualifier.must.be.expression"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF) + .range(qualifierExpression) + .descriptionAndTooltip(message) + .create(); + } } - if (fix != null) { - QuickFixAction.registerQuickFixAction(info, new LocalQuickFixOnPsiElementAsIntentionAdapter(fix)); + else if (refParent instanceof PsiMethodCallExpression) { + return null; // methods checked elsewhere } - } - } - return result; - } - @Nullable - private static HighlightInfo checkArrayInitializerCompatibleTypes(@Nonnull PsiExpression initializer, final PsiType componentType) { - PsiType initializerType = initializer.getType(); - if (initializerType == null) { - String description = JavaErrorBundle.message("illegal.initializer", JavaHighlightUtil.formatType(componentType)); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(initializer).descriptionAndTooltip(description).create(); - } - PsiExpression expression = initializer instanceof PsiArrayInitializerExpression ? null : initializer; - return checkAssignability(componentType, initializerType, expression, initializer); - } - - @Nullable - public static HighlightInfo checkExpressionRequired(@Nonnull PsiReferenceExpression expression, - @Nonnull JavaResolveResult resultForIncompleteCode) { - if (expression.getNextSibling() instanceof PsiErrorElement) { - return null; - } - - PsiElement resolved = resultForIncompleteCode.getElement(); - if (resolved == null || resolved instanceof PsiVariable) { - return null; - } + if (resolved == null) { + // do not highlight unknown packages (javac does not care), Javadoc, and module references (checked elsewhere) + PsiElement outerParent = getOuterReferenceParent(ref); + if (outerParent instanceof PsiPackageStatement || result.isPackagePrefixPackageReference() || PsiUtil.isInsideJavadocComment(ref) || outerParent instanceof + PsiPackageAccessibilityStatement) { + return null; + } - PsiElement parent = expression.getParent(); - // String.class or String() are both correct - if (parent instanceof PsiReferenceExpression || parent instanceof PsiMethodCallExpression) { - return null; - } + JavaResolveResult[] results = ref.multiResolve(true); + String description; + if (results.length > 1) { + String t1 = format(ObjectUtil.notNull(results[0].getElement())); + String t2 = format(ObjectUtil.notNull(results[1].getElement())); + description = JavaErrorBundle.message("ambiguous.reference", refName.getText(), t1, t2); + } + else { + description = JavaErrorBundle.message("cannot.resolve.symbol", refName.getText()); + } - String description = JavaErrorBundle.message("expression.expected"); - HighlightInfo info = - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create(); - if (info != null) UnresolvedReferenceQuickFixProvider.registerReferenceFixes(expression, QuickFixActionRegistrar.create(info)); - return info; - } + HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF) + .range(refName) + .descriptionAndTooltip(description) + .createUnconditionally(); + UnresolvedReferenceQuickFixProvider.registerReferenceFixes(ref, QuickFixActionRegistrar.create(info)); + return info; + } - @Nullable - public static HighlightInfo checkArrayInitializerApplicable(@Nonnull PsiArrayInitializerExpression expression) { - /* - JLS 10.6 Array Initializers - An array initializer may be specified in a declaration, or as part of an array creation expression - */ - PsiElement parent = expression.getParent(); - if (parent instanceof PsiVariable) { - PsiVariable variable = (PsiVariable)parent; - if (variable.getType() instanceof PsiArrayType) { - return null; - } - } - else if (parent instanceof PsiNewExpression || parent instanceof PsiArrayInitializerExpression) { - return null; - } - - String description = JavaErrorBundle.message("array.initializer.not.allowed"); - HighlightInfo info = - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create(); - QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createAddNewArrayExpressionFix(expression)); - return info; - } - - - @Nullable - public static HighlightInfo checkCaseStatement(@Nonnull PsiSwitchLabelStatementBase statement) { - PsiSwitchBlock switchBlock = statement.getEnclosingSwitchBlock(); - if (switchBlock == null) { - String description = JavaErrorBundle.message("case.statement.outside.switch"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); - } - - return null; - } - - @Nonnull - public static Collection checkSwitchLabelValues(@Nonnull PsiSwitchBlock switchBlock) { - PsiCodeBlock body = switchBlock.getBody(); - if (body == null) { - return Collections.emptyList(); - } - - PsiExpression selectorExpression = switchBlock.getExpression(); - PsiType selectorType = selectorExpression == null ? PsiType.INT : selectorExpression.getType(); - MultiMap values = new MultiMap<>(); - Object defaultValue = new Object(); - Collection results = new ArrayList<>(); - boolean hasDefaultCase = false; - - for (PsiStatement st : body.getStatements()) { - if (!(st instanceof PsiSwitchLabelStatementBase)) { - continue; - } - PsiSwitchLabelStatementBase labelStatement = (PsiSwitchLabelStatementBase)st; - boolean defaultCase = labelStatement.isDefaultCase(); - - if (defaultCase) { - values.putValue(defaultValue, ObjectUtil.notNull(labelStatement.getFirstChild(), labelStatement)); - hasDefaultCase = true; - } - else { - PsiExpressionList expressionList = labelStatement.getCaseValues(); - if (expressionList != null) { - for (PsiExpression expr : expressionList.getExpressions()) { - if (selectorExpression != null) { - HighlightInfo result = checkAssignability(selectorType, expr.getType(), expr, expr); - if (result != null) { - results.add(result); - continue; - } + if (!result.isValidResult() && !PsiUtil.isInsideJavadocComment(ref)) { + if (!result.isAccessible()) { + String message = buildProblemWithAccessDescription(ref, result, resolved); + HighlightInfo info = + HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(refName).descriptionAndTooltip(message).create(); + if (result.isStaticsScopeCorrect()) { + registerAccessQuickFixAction((PsiMember)resolved, ref, info, result.getCurrentFileResolveScope()); + if (ref instanceof PsiReferenceExpression) { + QuickFixAction.registerQuickFixAction( + info, + QuickFixFactory.getInstance().createRenameWrongRefFix((PsiReferenceExpression)ref) + ); + } + } + if (info != null) { + UnresolvedReferenceQuickFixProvider.registerReferenceFixes(ref, QuickFixActionRegistrar.create(info)); + } + return info; } - Object value = null; - if (expr instanceof PsiReferenceExpression) { - PsiElement element = ((PsiReferenceExpression)expr).resolve(); - if (element instanceof PsiEnumConstant) { - value = ((PsiEnumConstant)element).getName(); - if (((PsiReferenceExpression)expr).getQualifier() != null) { - String message = JavaErrorBundle.message("qualified.enum.constant.in.switch"); - results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expr).descriptionAndTooltip(message).create()); - continue; + if (!result.isStaticsScopeCorrect()) { + String description = buildProblemWithStaticDescription(resolved); + HighlightInfo info = + HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(refName).descriptionAndTooltip(description).create(); + registerStaticProblemQuickFixAction(resolved, info, ref); + if (ref instanceof PsiReferenceExpression) { + QuickFixAction.registerQuickFixAction( + info, + QuickFixFactory.getInstance().createRenameWrongRefFix((PsiReferenceExpression)ref) + ); } - } + return info; } - if (value == null) { - value = ConstantExpressionUtil.computeCastTo(expr, selectorType); - } - if (value == null) { - String description = JavaErrorBundle.message("constant.expression.required"); - results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expr).descriptionAndTooltip(description).create()); - continue; - } - - values.putValue(value, expr); - } - } - } - } - - for (Map.Entry> entry : values.entrySet()) { - if (entry.getValue().size() > 1) { - Object value = entry.getKey(); - String description = value == defaultValue ? JavaErrorBundle.message("duplicate.default.switch.label") : JavaErrorBundle.message( - "duplicate.switch.label", - value); - for (PsiElement element : entry.getValue()) { - results.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description).create()); - } - } - } - - if (results.isEmpty() && switchBlock instanceof PsiSwitchExpression) { - Set missingConstants = new HashSet<>(); - boolean exhaustive = hasDefaultCase; - if (!exhaustive) { - if (!values.isEmpty() && selectorType instanceof PsiClassType) { - PsiClass type = ((PsiClassType)selectorType).resolve(); - if (type != null && type.isEnum()) { - for (PsiField field : type.getFields()) { - if (field instanceof PsiEnumConstant && !values.containsKey(field.getName())) { - missingConstants.add(field.getName()); - } - } - exhaustive = missingConstants.isEmpty(); - } - } - } - if (!exhaustive) { - PsiElement range = ObjectUtil.notNull(selectorExpression, switchBlock); - String message = JavaErrorBundle.message(values.isEmpty() ? "switch.expr.empty" : "switch.expr.incomplete"); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(range).descriptionAndTooltip(message).create(); - if (!missingConstants.isEmpty()) { - QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance() - .createAddMissingEnumBranchesFix(switchBlock, missingConstants)); - } - QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createAddSwitchDefaultFix(switchBlock, null)); - results.add(info); - } - } - - return results; - } - - - /** - * see JLS 8.3.2.3 - */ - @Nullable - public static HighlightInfo checkIllegalForwardReferenceToField(@Nonnull PsiReferenceExpression expression, - @Nonnull PsiField referencedField) { - Boolean isIllegalForwardReference = isIllegalForwardReferenceToField(expression, referencedField, false); - if (isIllegalForwardReference == null) { - return null; - } - String description = JavaErrorBundle.message(isIllegalForwardReference ? "illegal.forward.reference" : "illegal.self.reference"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create(); - } - - public static Boolean isIllegalForwardReferenceToField(@Nonnull PsiReferenceExpression expression, - @Nonnull PsiField referencedField, - boolean acceptQualified) { - PsiClass containingClass = referencedField.getContainingClass(); - if (containingClass == null) { - return null; - } - if (expression.getContainingFile() != referencedField.getContainingFile()) { - return null; - } - if (expression.getTextRange().getStartOffset() >= referencedField.getTextRange().getEndOffset()) { - return null; - } - // only simple reference can be illegal - if (!acceptQualified && expression.getQualifierExpression() != null) { - return null; - } - PsiField initField = findEnclosingFieldInitializer(expression); - PsiClassInitializer classInitializer = findParentClassInitializer(expression); - if (initField == null && classInitializer == null) { - return null; - } - // instance initializers may access static fields - boolean isStaticClassInitializer = classInitializer != null && classInitializer.hasModifierProperty(PsiModifier.STATIC); - boolean isStaticInitField = initField != null && initField.hasModifierProperty(PsiModifier.STATIC); - boolean inStaticContext = isStaticInitField || isStaticClassInitializer; - if (!inStaticContext && referencedField.hasModifierProperty(PsiModifier.STATIC)) { - return null; - } - if (PsiUtil.isOnAssignmentLeftHand(expression) && !PsiUtil.isAccessedForReading(expression)) { - return null; - } - if (!containingClass.getManager().areElementsEquivalent(containingClass, PsiTreeUtil.getParentOfType(expression, PsiClass.class))) { - return null; - } - return initField != referencedField; - } - - /** - * @return field that has initializer with this element as subexpression or null if not found - */ - @Nullable - public static PsiField findEnclosingFieldInitializer(@Nullable PsiElement element) { - while (element != null) { - PsiElement parent = element.getParent(); - if (parent instanceof PsiField) { - PsiField field = (PsiField)parent; - if (element == field.getInitializer()) { - return field; - } - if (field instanceof PsiEnumConstant && element == ((PsiEnumConstant)field).getArgumentList()) { - return field; - } - } - if (element instanceof PsiClass || element instanceof PsiMethod) { - return null; - } - element = parent; - } - return null; - } - - @Nullable - private static PsiClassInitializer findParentClassInitializer(@Nullable PsiElement element) { - while (element != null) { - if (element instanceof PsiClassInitializer) { - return (PsiClassInitializer)element; - } - if (element instanceof PsiClass || element instanceof PsiMethod) { - return null; - } - element = element.getParent(); - } - return null; - } + } + if ((resolved instanceof PsiLocalVariable || resolved instanceof PsiParameter) && !(resolved instanceof ImplicitVariable)) { + return HighlightControlFlowUtil.checkVariableMustBeFinal((PsiVariable)resolved, ref, languageLevel); + } + if (resolved instanceof PsiClass && ((PsiClass)resolved).getContainingClass() == null + && PsiTreeUtil.getParentOfType(ref, PsiImportStatementBase.class) != null + && PsiUtil.isFromDefaultPackage((PsiClass)resolved)) { + String description = JavaErrorBundle.message("cannot.resolve.symbol", refName.getText()); + return HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(refName).descriptionAndTooltip(description).create(); + } - @Nullable - public static HighlightInfo checkIllegalType(@Nullable PsiTypeElement typeElement) { - if (typeElement == null || typeElement.getParent() instanceof PsiTypeElement) { - return null; + return null; } - if (PsiUtil.isInsideJavadocComment(typeElement)) { - return null; + @Nonnull + private static String format(@Nonnull PsiElement element) { + if (element instanceof PsiClass) { + return formatClass((PsiClass)element); + } + if (element instanceof PsiMethod) { + return JavaHighlightUtil.formatMethod((PsiMethod)element); + } + if (element instanceof PsiField) { + return formatField((PsiField)element); + } + return ElementDescriptionUtil.getElementDescription(element, HighlightUsagesDescriptionLocation.INSTANCE); } - PsiType type = typeElement.getType(); - PsiType componentType = type.getDeepComponentType(); - if (componentType instanceof PsiClassType) { - PsiClass aClass = PsiUtil.resolveClassInType(componentType); - if (aClass == null) { - String canonicalText = type.getCanonicalText(); - String description = JavaErrorBundle.message("unknown.class", canonicalText); - HighlightInfo info = - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(description).create(); - - PsiJavaCodeReferenceElement referenceElement = typeElement.getInnermostComponentReferenceElement(); - if (referenceElement != null && info != null) { - UnresolvedReferenceQuickFixProvider.registerReferenceFixes(referenceElement, QuickFixActionRegistrar.create(info)); + private static PsiElement getOuterReferenceParent(PsiJavaCodeReferenceElement ref) { + PsiElement element = ref; + while (element instanceof PsiJavaCodeReferenceElement) { + element = element.getParent(); } - return info; - } + return element; } - return null; - } + @Nullable + public static HighlightInfo checkPackageAndClassConflict(@Nonnull PsiJavaCodeReferenceElement ref, @Nonnull PsiFile containingFile) { + if (ref.isQualified() && getOuterReferenceParent(ref) instanceof PsiPackageStatement) { + VirtualFile file = containingFile.getVirtualFile(); + if (file != null) { + Module module = ProjectFileIndex.SERVICE.getInstance(ref.getProject()).getModuleForFile(file); + if (module != null) { + GlobalSearchScope scope = GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module, false); + PsiClass aClass = JavaPsiFacade.getInstance(ref.getProject()).findClass(ref.getCanonicalText(), scope); + if (aClass != null) { + String message = JavaErrorBundle.message("package.clashes.with.class", ref.getText()); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(ref).descriptionAndTooltip(message).create(); + } + } + } + } - @Nullable - public static HighlightInfo checkIllegalVoidType(@Nonnull PsiKeyword type) { - if (!PsiKeyword.VOID.equals(type.getText())) { - return null; + return null; } - PsiElement parent = type.getParent(); - if (parent instanceof PsiTypeElement) { - PsiElement typeOwner = parent.getParent(); - if (typeOwner != null) { - // do not highlight incomplete declarations - if (PsiUtilCore.hasErrorElementChild(typeOwner)) { - return null; - } - } - - if (typeOwner instanceof PsiMethod) { - PsiMethod method = (PsiMethod)typeOwner; - if (method.getReturnTypeElement() == parent && PsiType.VOID.equals(method.getReturnType())) { - return null; - } - } - else if (typeOwner instanceof PsiClassObjectAccessExpression) { - if (TypeConversionUtil.isVoidType(((PsiClassObjectAccessExpression)typeOwner).getOperand().getType())) { - return null; + @Nullable + public static HighlightInfo checkElementInReferenceList( + @Nonnull PsiJavaCodeReferenceElement ref, + @Nonnull PsiReferenceList referenceList, + @Nonnull JavaResolveResult resolveResult + ) { + PsiElement resolved = resolveResult.getElement(); + HighlightInfo highlightInfo = null; + PsiElement refGrandParent = referenceList.getParent(); + if (resolved instanceof PsiClass aClass) { + if (refGrandParent instanceof PsiClass parentClass) { + if (refGrandParent instanceof PsiTypeParameter typeParameter) { + highlightInfo = + GenericsHighlightUtil.checkElementInTypeParameterExtendsList(referenceList, typeParameter, resolveResult, ref); + } + else if (referenceList.equals(parentClass.getImplementsList()) || + referenceList.equals(parentClass.getExtendsList())) { + highlightInfo = HighlightClassUtil.checkExtendsClassAndImplementsInterface(referenceList, resolveResult, ref); + if (highlightInfo == null) { + highlightInfo = HighlightClassUtil.checkCannotInheritFromFinal(aClass, ref); + } + if (highlightInfo == null) { + // TODO highlightInfo = HighlightClassUtil.checkExtendsProhibitedClass(aClass, parentClass, ref); + } + if (highlightInfo == null) { + highlightInfo = GenericsHighlightUtil.checkCannotInheritFromTypeParameter(aClass, ref); + } + if (highlightInfo == null) { + // TODO highlightInfo = HighlightClassUtil.checkExtendsSealedClass(parentClass, aClass, ref); + } + } + } + else if (refGrandParent instanceof PsiMethod method && method.getThrowsList() == referenceList) { + highlightInfo = checkMustBeThrowable(aClass, ref); + } } - } - else if (typeOwner instanceof JavaCodeFragment) { - if (typeOwner.getUserData(PsiUtil.VALID_VOID_TYPE_IN_CODE_FRAGMENT) != null) { - return null; + else if (refGrandParent instanceof PsiMethod method && referenceList == method.getThrowsList()) { + String description = JavaErrorBundle.message("class.name.expected"); + highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(ref).descriptionAndTooltip(description).create(); } - } + return highlightInfo; } - String description = JavaErrorBundle.message("illegal.type.void"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(type).descriptionAndTooltip(description).create(); - } - @Nullable - public static HighlightInfo checkMemberReferencedBeforeConstructorCalled(@Nonnull PsiElement expression, - PsiElement resolved, - @Nonnull PsiFile containingFile) { - PsiClass referencedClass; - @NonNls String resolvedName; - PsiType type; - if (expression instanceof PsiJavaCodeReferenceElement) { - // redirected ctr - if (PsiKeyword.THIS.equals(((PsiJavaCodeReferenceElement)expression).getReferenceName()) && resolved instanceof PsiMethod && ((PsiMethod)resolved) - .isConstructor()) { - return null; - } - PsiElement qualifier = ((PsiJavaCodeReferenceElement)expression).getQualifier(); - type = qualifier instanceof PsiExpression ? ((PsiExpression)qualifier).getType() : null; - referencedClass = PsiUtil.resolveClassInType(type); + public static boolean isSerializationImplicitlyUsedField(@Nonnull PsiField field) { + final String name = field.getName(); + if (!SERIAL_VERSION_UID_FIELD_NAME.equals(name) && !SERIAL_PERSISTENT_FIELDS_FIELD_NAME.equals(name)) { + return false; + } + if (!field.hasModifierProperty(PsiModifier.STATIC)) { + return false; + } + PsiClass aClass = field.getContainingClass(); + return aClass == null || JavaHighlightUtil.isSerializable(aClass); + } - boolean isSuperCall = RefactoringChangeUtil.isSuperMethodCall(expression.getParent()); - if (resolved == null && isSuperCall) { + @Nullable + public static HighlightInfo checkClassReferenceAfterQualifier( + @Nonnull final PsiReferenceExpression expression, + final PsiElement resolved + ) { + if (!(resolved instanceof PsiClass)) { + return null; + } + final PsiExpression qualifier = expression.getQualifierExpression(); + if (qualifier == null) { + return null; + } if (qualifier instanceof PsiReferenceExpression) { - resolved = ((PsiReferenceExpression)qualifier).resolve(); - expression = qualifier; - type = ((PsiReferenceExpression)qualifier).getType(); - referencedClass = PsiUtil.resolveClassInType(type); - } - else if (qualifier == null) { - resolved = PsiTreeUtil.getParentOfType(expression, PsiMethod.class, true, PsiMember.class); - if (resolved != null) { - referencedClass = ((PsiMethod)resolved).getContainingClass(); - } - } - else if (qualifier instanceof PsiThisExpression) { - referencedClass = PsiUtil.resolveClassInType(((PsiThisExpression)qualifier).getType()); - } - } - if (resolved instanceof PsiField) { - PsiField referencedField = (PsiField)resolved; - if (referencedField.hasModifierProperty(PsiModifier.STATIC)) { - return null; - } - resolvedName = PsiFormatUtil.formatVariable(referencedField, - PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME, - PsiSubstitutor.EMPTY); - referencedClass = referencedField.getContainingClass(); - } - else if (resolved instanceof PsiMethod) { - PsiMethod method = (PsiMethod)resolved; - if (method.hasModifierProperty(PsiModifier.STATIC)) { - return null; - } - PsiElement nameElement = - expression instanceof PsiThisExpression ? expression : ((PsiJavaCodeReferenceElement)expression).getReferenceNameElement(); - String name = nameElement == null ? null : nameElement.getText(); - if (isSuperCall) { - if (referencedClass == null) { - return null; - } - if (qualifier == null) { - PsiClass superClass = referencedClass.getSuperClass(); - if (superClass != null && PsiUtil.isInnerClass(superClass) && InheritanceUtil.isInheritorOrSelf(referencedClass, - superClass.getContainingClass(), - true)) { - // by default super() is considered this. - qualified - resolvedName = PsiKeyword.THIS; + PsiElement qualifierResolved = ((PsiReferenceExpression)qualifier).resolve(); + if (qualifierResolved instanceof PsiClass || qualifierResolved instanceof PsiPackage) { + return null; } - else { - return null; - } - } - else { - resolvedName = qualifier.getText(); - } } - else if (PsiKeyword.THIS.equals(name)) { - resolvedName = PsiKeyword.THIS; - } - else { - resolvedName = PsiFormatUtil.formatMethod(method, - PsiSubstitutor.EMPTY, - PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME, - 0); - if (referencedClass == null) { - referencedClass = method.getContainingClass(); - } - } - } - else if (resolved instanceof PsiClass) { - PsiClass aClass = (PsiClass)resolved; - if (aClass.hasModifierProperty(PsiModifier.STATIC)) { - return null; - } - referencedClass = aClass.getContainingClass(); - if (referencedClass == null) { - return null; + String description = JavaErrorBundle.message("expected.class.or.package"); + HighlightInfo info = + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(qualifier).descriptionAndTooltip(description).create(); + QuickFixAction.registerQuickFixAction( + info, + QuickFixFactory.getInstance().createRemoveQualifierFix(qualifier, expression, (PsiClass)resolved) + ); + return info; + } + + public static void registerChangeVariableTypeFixes( + @Nonnull PsiVariable parameter, + PsiType itemType, + @Nullable PsiExpression expr, + @Nonnull HighlightInfo highlightInfo + ) { + for (IntentionAction action : getChangeVariableTypeFixes(parameter, itemType)) { + QuickFixAction.registerQuickFixAction(highlightInfo, action); } - resolvedName = PsiFormatUtil.formatClass(aClass, PsiFormatUtilBase.SHOW_NAME); - } - else { - return null; - } - } - else if (expression instanceof PsiThisExpression) { - PsiThisExpression thisExpression = (PsiThisExpression)expression; - type = thisExpression.getType(); - referencedClass = PsiUtil.resolveClassInType(type); - if (thisExpression.getQualifier() != null) { - resolvedName = referencedClass == null ? null : PsiFormatUtil.formatClass(referencedClass, PsiFormatUtilBase.SHOW_NAME) + ".this"; - } - else { - resolvedName = "this"; - } - } - else { - return null; - } - if (referencedClass == null) { - return null; - } - return checkReferenceToOurInstanceInsideThisOrSuper(expression, referencedClass, resolvedName, containingFile); - } - - @Nullable - private static HighlightInfo checkReferenceToOurInstanceInsideThisOrSuper(@Nonnull final PsiElement expression, - @Nonnull PsiClass referencedClass, - final String resolvedName, - @Nonnull PsiFile containingFile) { - if (PsiTreeUtil.getParentOfType(expression, PsiReferenceParameterList.class) != null) { - return null; - } - PsiElement element = expression.getParent(); - while (element != null) { - // check if expression inside super()/this() call - if (RefactoringChangeUtil.isSuperOrThisMethodCall(element)) { - PsiElement parentClass = new PsiMatcherImpl(element).parent(PsiMatchers.hasClass(PsiExpressionStatement.class)) - .parent(PsiMatchers.hasClass(PsiCodeBlock.class)) - .parent(PsiMatchers - .hasClass(PsiMethod.class)) - .dot(JavaMatchers.isConstructor(true)) - .parent(PsiMatchers.hasClass(PsiClass.class)) - .getElement(); - if (parentClass == null) { - return null; - } - - // only this class/superclasses instance methods are not allowed to call - PsiClass aClass = (PsiClass)parentClass; - if (PsiUtil.isInnerClass(aClass) && referencedClass == aClass.getContainingClass()) { - return null; - } - // field or method should be declared in this class or super - if (!InheritanceUtil.isInheritorOrSelf(aClass, referencedClass, true)) { - return null; - } - // and point to our instance - if (expression instanceof PsiReferenceExpression && !thisOrSuperReference(((PsiReferenceExpression)expression).getQualifierExpression(), - aClass)) { - return null; - } - - if (expression instanceof PsiJavaCodeReferenceElement && !aClass.equals(PsiTreeUtil.getParentOfType(expression, - PsiClass.class)) && PsiTreeUtil.getParentOfType( - expression, - PsiTypeElement.class) != null) { - return null; - } - - if (expression instanceof PsiJavaCodeReferenceElement && PsiTreeUtil.getParentOfType(expression, - PsiClassObjectAccessExpression.class) != null) { - return null; - } - - final HighlightInfo highlightInfo = createMemberReferencedError(resolvedName, expression.getTextRange()); - if (expression instanceof PsiReferenceExpression && PsiUtil.isInnerClass(aClass)) { - final String referenceName = ((PsiReferenceExpression)expression).getReferenceName(); - final PsiClass containingClass = aClass.getContainingClass(); - LOG.assertTrue(containingClass != null); - final PsiField fieldInContainingClass = containingClass.findFieldByName(referenceName, true); - if (fieldInContainingClass != null && ((PsiReferenceExpression)expression).getQualifierExpression() == null) { - QuickFixAction.registerQuickFixAction(highlightInfo, new QualifyWithThisFix(containingClass, expression)); - } + if (expr instanceof PsiMethodCallExpression) { + final PsiMethod method = ((PsiMethodCallExpression)expr).resolveMethod(); + if (method != null) { + QuickFixAction.registerQuickFixAction( + highlightInfo, + PriorityActionWrapper.lowPriority( + method, + QuickFixFactory.getInstance().createMethodReturnFix( + method, + parameter.getType(), + true + ) + ) + ); + } } + } - return highlightInfo; - } - - if (element instanceof PsiReferenceExpression) { - final PsiElement resolve; - if (element instanceof PsiReferenceExpressionImpl) { - PsiReferenceExpressionImpl referenceExpression = (PsiReferenceExpressionImpl)element; - JavaResolveResult[] results = JavaResolveUtil.resolveWithContainingFile(referenceExpression, - PsiReferenceExpressionImpl.OurGenericsResolver.INSTANCE, - true, - false, - containingFile); - resolve = results.length == 1 ? results[0].getElement() : null; + @Nonnull + public static List getChangeVariableTypeFixes(@Nonnull PsiVariable parameter, PsiType itemType) { + if (itemType instanceof PsiMethodReferenceType) { + return Collections.emptyList(); } - else { - resolve = ((PsiReferenceExpression)element).resolve(); + List result = new ArrayList<>(); + if (itemType != null) { + for (ChangeVariableTypeQuickFixProvider fixProvider : Extensions.getExtensions(ChangeVariableTypeQuickFixProvider.EP_NAME)) { + Collections.addAll(result, fixProvider.getFixes(parameter, itemType)); + } } - if (resolve instanceof PsiField && ((PsiField)resolve).hasModifierProperty(PsiModifier.STATIC)) { - return null; + IntentionAction changeFix = getChangeParameterClassFix(parameter.getType(), itemType); + if (changeFix != null) { + result.add(changeFix); } - } + return result; + } - element = element.getParent(); - if (element instanceof PsiClass && InheritanceUtil.isInheritorOrSelf((PsiClass)element, referencedClass, true)) { + @Nullable + public static HighlightInfo checkAnnotationMethodParameters(@Nonnull PsiParameterList list) { + final PsiElement parent = list.getParent(); + if (PsiUtil.isAnnotationMethod(parent) && list.getParametersCount() > 0) { + final String message = JavaErrorBundle.message("annotation.interface.members.may.not.have.parameters"); + final HighlightInfo highlightInfo = + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(list).descriptionAndTooltip(message).create(); + QuickFixAction.registerQuickFixAction( + highlightInfo, + QuickFixFactory.getInstance().createRemoveParameterListFix((PsiMethod)parent) + ); + return highlightInfo; + } return null; - } - } - return null; - } - - private static HighlightInfo createMemberReferencedError(@NonNls final String resolvedName, @Nonnull TextRange textRange) { - String description = JavaErrorBundle.message("member.referenced.before.constructor.called", resolvedName); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).descriptionAndTooltip(description).create(); - } - - @Nullable - public static HighlightInfo checkImplicitThisReferenceBeforeSuper(@Nonnull PsiClass aClass, @Nonnull JavaSdkVersion javaSdkVersion) { - if (javaSdkVersion.isAtLeast(JavaSdkVersion.JDK_1_7)) { - return null; - } - if (aClass instanceof PsiAnonymousClass || aClass instanceof PsiTypeParameter) { - return null; - } - PsiClass superClass = aClass.getSuperClass(); - if (superClass == null || !PsiUtil.isInnerClass(superClass)) { - return null; - } - PsiClass outerClass = superClass.getContainingClass(); - if (!InheritanceUtil.isInheritorOrSelf(aClass, outerClass, true)) { - return null; - } - // 'this' can be used as an (implicit) super() qualifier - PsiMethod[] constructors = aClass.getConstructors(); - if (constructors.length == 0) { - TextRange range = HighlightNamesUtil.getClassDeclarationTextRange(aClass); - return createMemberReferencedError(aClass.getName() + ".this", range); - } - for (PsiMethod constructor : constructors) { - if (!isSuperCalledInConstructor(constructor)) { - return createMemberReferencedError(aClass.getName() + ".this", HighlightNamesUtil.getMethodDeclarationTextRange(constructor)); - } - } - return null; - } - - private static boolean isSuperCalledInConstructor(@Nonnull final PsiMethod constructor) { - final PsiCodeBlock body = constructor.getBody(); - if (body == null) { - return false; - } - final PsiStatement[] statements = body.getStatements(); - if (statements.length == 0) { - return false; - } - final PsiStatement statement = statements[0]; - final PsiElement element = new PsiMatcherImpl(statement).dot(PsiMatchers.hasClass(PsiExpressionStatement.class)) - .firstChild(PsiMatchers.hasClass(PsiMethodCallExpression.class)) - .firstChild - (PsiMatchers.hasClass(PsiReferenceExpression.class)) - .firstChild(PsiMatchers.hasClass(PsiKeyword.class)) - .dot(PsiMatchers.hasText(PsiKeyword.SUPER)) - .getElement(); - return element != null; - } - - private static boolean thisOrSuperReference(@Nullable PsiExpression qualifierExpression, PsiClass aClass) { - if (qualifierExpression == null) { - return true; - } - PsiJavaCodeReferenceElement qualifier; - if (qualifierExpression instanceof PsiThisExpression) { - qualifier = ((PsiThisExpression)qualifierExpression).getQualifier(); - } - else if (qualifierExpression instanceof PsiSuperExpression) { - qualifier = ((PsiSuperExpression)qualifierExpression).getQualifier(); - } - else { - return false; - } - if (qualifier == null) { - return true; - } - PsiElement resolved = qualifier.resolve(); - return resolved instanceof PsiClass && InheritanceUtil.isInheritorOrSelf(aClass, (PsiClass)resolved, true); - } - - - @Nullable - public static HighlightInfo checkLabelWithoutStatement(@Nonnull PsiLabeledStatement statement) { - if (statement.getStatement() == null) { - String description = JavaErrorBundle.message("label.without.statement"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); - } - return null; - } - - - @Nullable - public static HighlightInfo checkLabelAlreadyInUse(@Nonnull PsiLabeledStatement statement) { - PsiIdentifier identifier = statement.getLabelIdentifier(); - String text = identifier.getText(); - PsiElement element = statement; - while (element != null) { - if (element instanceof PsiMethod || element instanceof PsiClass) { - break; - } - if (element instanceof PsiLabeledStatement && element != statement && Comparing.equal(((PsiLabeledStatement)element).getLabelIdentifier() - .getText(), - text)) { - String description = JavaErrorBundle.message("duplicate.label", text); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(identifier).descriptionAndTooltip(description).create(); - } - element = element.getParent(); - } - return null; - } - - - @Nullable - public static HighlightInfo checkUnclosedComment(@Nonnull PsiComment comment) { - if (!(comment instanceof PsiDocComment) && comment.getTokenType() != JavaTokenType.C_STYLE_COMMENT) { - return null; - } - if (!comment.getText().endsWith("*/")) { - int start = comment.getTextRange().getEndOffset() - 1; - int end = start + 1; - String description = JavaErrorBundle.message("unclosed.comment"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(start, end).descriptionAndTooltip(description).create(); - } - return null; - } - - - @Nonnull - public static Collection checkCatchTypeIsDisjoint(@Nonnull final PsiParameter parameter) { - if (!(parameter.getType() instanceof PsiDisjunctionType)) { - return Collections.emptyList(); - } - - final Collection result = ContainerUtil.newArrayList(); - final List typeElements = PsiUtil.getParameterTypeElements(parameter); - for (int i = 0, size = typeElements.size(); i < size; i++) { - final PsiClass class1 = PsiUtil.resolveClassInClassTypeOnly(typeElements.get(i).getType()); - if (class1 == null) { - continue; - } - for (int j = i + 1; j < size; j++) { - final PsiClass class2 = PsiUtil.resolveClassInClassTypeOnly(typeElements.get(j).getType()); - if (class2 == null) { - continue; - } - final boolean sub = InheritanceUtil.isInheritorOrSelf(class1, class2, true); - final boolean sup = InheritanceUtil.isInheritorOrSelf(class2, class1, true); - if (sub || sup) { - final String name1 = PsiFormatUtil.formatClass(class1, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_FQ_NAME); - final String name2 = PsiFormatUtil.formatClass(class2, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_FQ_NAME); - final String message = JavaErrorBundle.message("exception.must.be.disjoint", sub ? name1 : name2, sub ? name2 : name1); - final PsiTypeElement element = typeElements.get(sub ? i : j); - final HighlightInfo highlight = - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(message).create(); - QuickFixAction.registerQuickFixAction(highlight, QuickFixFactory.getInstance().createDeleteMultiCatchFix(element)); - result.add(highlight); - break; - } - } - } - - return result; - } - - - @Nonnull - public static Collection checkExceptionAlreadyCaught(@Nonnull final PsiParameter parameter) { - final PsiElement scope = parameter.getDeclarationScope(); - if (!(scope instanceof PsiCatchSection)) { - return Collections.emptyList(); - } - - final PsiCatchSection catchSection = (PsiCatchSection)scope; - final PsiCatchSection[] allCatchSections = catchSection.getTryStatement().getCatchSections(); - final int startFrom = ArrayUtil.find(allCatchSections, catchSection) - 1; - if (startFrom < 0) { - return Collections.emptyList(); - } - - final List typeElements = PsiUtil.getParameterTypeElements(parameter); - final boolean isInMultiCatch = typeElements.size() > 1; - final Collection result = ContainerUtil.newArrayList(); - - for (PsiTypeElement typeElement : typeElements) { - final PsiClass catchClass = PsiUtil.resolveClassInClassTypeOnly(typeElement.getType()); - if (catchClass == null) { - continue; - } - - for (int i = startFrom; i >= 0; i--) { - final PsiCatchSection upperCatchSection = allCatchSections[i]; - final PsiType upperCatchType = upperCatchSection.getCatchType(); - - final boolean highlight = upperCatchType instanceof PsiDisjunctionType ? checkMultipleTypes(catchClass, - ((PsiDisjunctionType)upperCatchType).getDisjunctions()) : checkSingleType - (catchClass, upperCatchType); - if (highlight) { - final String className = PsiFormatUtil.formatClass(catchClass, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_FQ_NAME); - final String description = JavaErrorBundle.message("exception.already.caught", className); - final HighlightInfo highlightInfo = - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(description).create(); - result.add(highlightInfo); - - QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance() - .createMoveCatchUpFix(catchSection, upperCatchSection)); - if (isInMultiCatch) { - QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createDeleteMultiCatchFix(typeElement)); - } - else { - QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createDeleteCatchFix(parameter)); - } - } - } - } - - return result; - } - - private static boolean checkMultipleTypes(final PsiClass catchClass, @Nonnull final List upperCatchTypes) { - for (int i = upperCatchTypes.size() - 1; i >= 0; i--) { - if (checkSingleType(catchClass, upperCatchTypes.get(i))) { - return true; - } - } - return false; - } - - private static boolean checkSingleType(final PsiClass catchClass, final PsiType upperCatchType) { - final PsiClass upperCatchClass = PsiUtil.resolveClassInType(upperCatchType); - return upperCatchClass != null && InheritanceUtil.isInheritorOrSelf(catchClass, upperCatchClass, true); - } - - - @Nullable - public static HighlightInfo checkTernaryOperatorConditionIsBoolean(@Nonnull PsiExpression expression, PsiType type) { - if (expression.getParent() instanceof PsiConditionalExpression && ((PsiConditionalExpression)expression.getParent()).getCondition() == expression && !TypeConversionUtil - .isBooleanType(type)) { - return createIncompatibleTypeHighlightInfo(PsiType.BOOLEAN, type, expression.getTextRange(), 0); - } - return null; - } - - - @Nullable - public static HighlightInfo checkStatementPrependedWithCaseInsideSwitch(@Nonnull PsiSwitchStatement statement) { - PsiCodeBlock body = statement.getBody(); - if (body != null) { - PsiElement first = PsiTreeUtil.skipSiblingsForward(body.getLBrace(), PsiWhiteSpace.class, PsiComment.class); - if (first != null && !(first instanceof PsiSwitchLabelStatement) && !PsiUtil.isJavaToken(first, JavaTokenType.RBRACE)) { - String description = JavaErrorBundle.message("statement.must.be.prepended.with.case.label"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(first).descriptionAndTooltip(description).create(); - } - } - - return null; - } - - - @Nullable - public static HighlightInfo checkAssertOperatorTypes(@Nonnull PsiExpression expression, @Nullable PsiType type) { - if (type == null) { - return null; - } - if (!(expression.getParent() instanceof PsiAssertStatement)) { - return null; - } - PsiAssertStatement assertStatement = (PsiAssertStatement)expression.getParent(); - if (expression == assertStatement.getAssertCondition() && !TypeConversionUtil.isBooleanType(type)) { - // addTypeCast quickfix is not applicable here since no type can be cast to boolean - HighlightInfo highlightInfo = createIncompatibleTypeHighlightInfo(PsiType.BOOLEAN, type, expression.getTextRange(), 0); - if (expression instanceof PsiAssignmentExpression && ((PsiAssignmentExpression)expression).getOperationTokenType() == JavaTokenType.EQ) { - QuickFixAction.registerQuickFixAction(highlightInfo, - QuickFixFactory.getInstance().createAssignmentToComparisonFix((PsiAssignmentExpression)expression)); - } - return highlightInfo; - } - if (expression == assertStatement.getAssertDescription() && TypeConversionUtil.isVoidType(type)) { - String description = JavaErrorBundle.message("void.type.is.not.allowed"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create(); - } - return null; - } - - - @Nullable - public static HighlightInfo checkSynchronizedExpressionType(@Nonnull PsiExpression expression, - @Nullable PsiType type, - @Nonnull PsiFile containingFile) { - if (type == null) { - return null; - } - if (expression.getParent() instanceof PsiSynchronizedStatement) { - PsiSynchronizedStatement synchronizedStatement = (PsiSynchronizedStatement)expression.getParent(); - if (expression == synchronizedStatement.getLockExpression() && (type instanceof PsiPrimitiveType || TypeConversionUtil.isNullType(type))) { - PsiClassType objectType = PsiType.getJavaLangObject(containingFile.getManager(), expression.getResolveScope()); - return createIncompatibleTypeHighlightInfo(objectType, type, expression.getTextRange(), 0); - } - } - return null; - } - - - @Nullable - public static HighlightInfo checkConditionalExpressionBranchTypesMatch(@Nonnull final PsiExpression expression, PsiType type) { - PsiElement parent = expression.getParent(); - if (!(parent instanceof PsiConditionalExpression)) { - return null; } - PsiConditionalExpression conditionalExpression = (PsiConditionalExpression)parent; - // check else branches only - if (conditionalExpression.getElseExpression() != expression) { - return null; - } - final PsiExpression thenExpression = conditionalExpression.getThenExpression(); - assert thenExpression != null; - PsiType thenType = thenExpression.getType(); - if (thenType == null || type == null) { - return null; - } - if (conditionalExpression.getType() == null) { - if (PsiUtil.isLanguageLevel8OrHigher(conditionalExpression) && PsiPolyExpressionUtil.isPolyExpression(conditionalExpression)) { - return null; - } - // cannot derive type of conditional expression - // elseType will never be cast-able to thenType, so no quick fix here - return createIncompatibleTypeHighlightInfo(thenType, type, expression.getTextRange(), 0); - } - return null; - } - - @SuppressWarnings("StringContatenationInLoop") - public static HighlightInfo createIncompatibleTypeHighlightInfo(final PsiType lType, - final PsiType rType, - @Nonnull final TextRange textRange, - int navigationShift) { - PsiType lType1 = lType; - PsiType rType1 = rType; - PsiTypeParameter[] lTypeParams = PsiTypeParameter.EMPTY_ARRAY; - PsiSubstitutor lTypeSubstitutor = PsiSubstitutor.EMPTY; - if (lType1 instanceof PsiClassType) { - PsiClassType.ClassResolveResult resolveResult = ((PsiClassType)lType1).resolveGenerics(); - lTypeSubstitutor = resolveResult.getSubstitutor(); - PsiClass psiClass = resolveResult.getElement(); - if (psiClass instanceof PsiAnonymousClass) { - lType1 = ((PsiAnonymousClass)psiClass).getBaseClassType(); - resolveResult = ((PsiClassType)lType1).resolveGenerics(); - lTypeSubstitutor = resolveResult.getSubstitutor(); - psiClass = resolveResult.getElement(); - } - lTypeParams = psiClass == null ? PsiTypeParameter.EMPTY_ARRAY : psiClass.getTypeParameters(); - } - PsiTypeParameter[] rTypeParams = PsiTypeParameter.EMPTY_ARRAY; - PsiSubstitutor rTypeSubstitutor = PsiSubstitutor.EMPTY; - if (rType1 instanceof PsiClassType) { - PsiClassType.ClassResolveResult resolveResult = ((PsiClassType)rType1).resolveGenerics(); - rTypeSubstitutor = resolveResult.getSubstitutor(); - PsiClass psiClass = resolveResult.getElement(); - if (psiClass instanceof PsiAnonymousClass) { - rType1 = ((PsiAnonymousClass)psiClass).getBaseClassType(); - resolveResult = ((PsiClassType)rType1).resolveGenerics(); - rTypeSubstitutor = resolveResult.getSubstitutor(); - psiClass = resolveResult.getElement(); - } - rTypeParams = psiClass == null ? PsiTypeParameter.EMPTY_ARRAY : psiClass.getTypeParameters(); - } - - int typeParamColumns = Math.max(lTypeParams.length, rTypeParams.length); - @Language("HTML") @NonNls String requiredRow = ""; - @Language("HTML") @NonNls String foundRow = ""; - for (int i = 0; i < typeParamColumns; i++) { - PsiTypeParameter lTypeParameter = i >= lTypeParams.length ? null : lTypeParams[i]; - PsiTypeParameter rTypeParameter = i >= rTypeParams.length ? null : rTypeParams[i]; - PsiType lSubstitutedType = lTypeParameter == null ? null : lTypeSubstitutor.substitute(lTypeParameter); - PsiType rSubstitutedType = rTypeParameter == null ? null : rTypeSubstitutor.substitute(rTypeParameter); - boolean matches = Comparing.equal(lSubstitutedType, rSubstitutedType); - @NonNls String openBrace = i == 0 ? "<" : ""; - @NonNls String closeBrace = i == typeParamColumns - 1 ? ">" : ","; - requiredRow += "" + (lTypeParams.length == 0 ? "" : openBrace) + redIfNotMatch(lSubstitutedType, - matches) + (i < lTypeParams.length ? closeBrace : "") + ""; - foundRow += "" + (rTypeParams.length == 0 ? "" : openBrace) + redIfNotMatch(rSubstitutedType, - matches) + (i < rTypeParams.length ? closeBrace : "") + ""; - } - PsiType lRawType = lType1 instanceof PsiClassType ? ((PsiClassType)lType1).rawType() : lType1; - PsiType rRawType = rType1 instanceof PsiClassType ? ((PsiClassType)rType1).rawType() : rType1; - boolean assignable = lRawType == null || rRawType == null || TypeConversionUtil.isAssignable(lRawType, rRawType); - - String toolTip = JavaErrorBundle.message("incompatible.types.html.tooltip", - redIfNotMatch(lRawType, assignable), - requiredRow, - redIfNotMatch(rRawType, assignable), - foundRow); - - String description = - JavaErrorBundle.message("incompatible.types", JavaHighlightUtil.formatType(lType1), JavaHighlightUtil.formatType(rType1)); - - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR) - .range(textRange) - .description(description) - .escapedToolTip(toolTip) - .navigationShift(navigationShift) - .create(); - } - @Nullable - public static HighlightInfo checkSingleImportClassConflict(@Nonnull PsiImportStatement statement, - @Nonnull Map> importedClasses, - @Nonnull PsiFile containingFile) { - if (statement.isOnDemand()) { - return null; - } - PsiElement element = statement.resolve(); - if (element instanceof PsiClass) { - String name = ((PsiClass)element).getName(); - Pair imported = importedClasses.get(name); - PsiClass importedClass = imported == null ? null : imported.getSecond(); - if (importedClass != null && !containingFile.getManager().areElementsEquivalent(importedClass, element)) { - String description = JavaErrorBundle.message("single.import.class.conflict", formatClass(importedClass)); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); - } - importedClasses.put(name, Pair.create(null, (PsiClass)element)); - } - return null; - } - - - @NonNls - private static String redIfNotMatch(PsiType type, boolean matches) { - if (matches) { - return getFQName(type, false); - } - String color = UIUtil.isUnderDarcula() ? "FF6B68" : "red"; - return "" + getFQName(type, true) + ""; - } - - private static String getFQName(@Nullable PsiType type, boolean longName) { - if (type == null) { - return ""; - } - return XmlStringUtil.escapeString(longName ? type.getInternalCanonicalText() : type.getPresentableText()); - } - - - @Nullable - public static HighlightInfo checkMustBeThrowable(@Nullable PsiType type, @Nonnull PsiElement context, boolean addCastIntention) { - if (type == null) { - return null; - } - PsiElementFactory factory = JavaPsiFacade.getInstance(context.getProject()).getElementFactory(); - PsiClassType throwable = factory.createTypeByFQClassName("java.lang.Throwable", context.getResolveScope()); - if (!TypeConversionUtil.isAssignable(throwable, type)) { - HighlightInfo highlightInfo = createIncompatibleTypeHighlightInfo(throwable, type, context.getTextRange(), 0); - if (addCastIntention && TypeConversionUtil.areTypesConvertible(type, throwable)) { - if (context instanceof PsiExpression) { - QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createAddTypeCastFix(throwable, (PsiExpression)context)); - } - } - - final PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(type); - if (aClass != null) { - QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createExtendsListFix(aClass, throwable, true)); - } - return highlightInfo; - } - return null; - } - - - @Nullable - private static HighlightInfo checkMustBeThrowable(@Nullable PsiClass aClass, @Nonnull PsiElement context) { - if (aClass == null) { - return null; - } - PsiClassType type = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory().createType(aClass); - return checkMustBeThrowable(type, context, false); - } - - - @Nullable - public static HighlightInfo checkLabelDefined(@Nullable PsiIdentifier labelIdentifier, @Nullable PsiStatement exitedStatement) { - if (labelIdentifier == null) { - return null; - } - String label = labelIdentifier.getText(); - if (label == null) { - return null; - } - if (exitedStatement == null) { - String message = JavaErrorBundle.message("unresolved.label", label); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(labelIdentifier).descriptionAndTooltip(message).create(); - } - return null; - } - - - @Nullable - public static HighlightInfo checkReference(@Nonnull PsiJavaCodeReferenceElement ref, - @Nonnull JavaResolveResult result, - @Nonnull PsiFile containingFile, - @Nonnull LanguageLevel languageLevel) { - PsiElement refName = ref.getReferenceNameElement(); - if (!(refName instanceof PsiIdentifier) && !(refName instanceof PsiKeyword)) { - return null; - } - PsiElement resolved = result.getElement(); - - HighlightInfo highlightInfo = checkMemberReferencedBeforeConstructorCalled(ref, resolved, containingFile); - if (highlightInfo != null) { - return highlightInfo; - } + @Nullable + public static HighlightInfo checkForStatement(@Nonnull PsiForStatement statement) { + PsiStatement init = statement.getInitialization(); + if (init == null || init instanceof PsiEmptyStatement || init instanceof PsiDeclarationStatement && ArrayUtil.getFirstElement(((PsiDeclarationStatement)init) + .getDeclaredElements()) + instanceof PsiLocalVariable || init instanceof PsiExpressionStatement || init instanceof PsiExpressionListStatement) { + return null; + } - PsiElement refParent = ref.getParent(); - PsiElement granny; - if (refParent instanceof PsiReferenceExpression && (granny = refParent.getParent()) instanceof PsiMethodCallExpression) { - PsiReferenceExpression referenceToMethod = ((PsiMethodCallExpression)granny).getMethodExpression(); - PsiExpression qualifierExpression = referenceToMethod.getQualifierExpression(); - if (qualifierExpression == ref && resolved != null && !(resolved instanceof PsiClass) && !(resolved instanceof PsiVariable)) { - String message = JavaErrorBundle.message("qualifier.must.be.expression"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF) - .range(qualifierExpression) - .descriptionAndTooltip(message) - .create(); - } + String message = JavaErrorBundle.message("invalid.statement"); + return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(init).descriptionAndTooltip(message).create(); } - else if (refParent instanceof PsiMethodCallExpression) { - return null; // methods checked elsewhere + + private static void registerChangeParameterClassFix(PsiType lType, PsiType rType, HighlightInfo info) { + QuickFixAction.registerQuickFixAction(info, getChangeParameterClassFix(lType, rType)); } - if (resolved == null) { - // do not highlight unknown packages (javac does not care), Javadoc, and module references (checked elsewhere) - PsiElement outerParent = getOuterReferenceParent(ref); - if (outerParent instanceof PsiPackageStatement || result.isPackagePrefixPackageReference() || PsiUtil.isInsideJavadocComment(ref) || outerParent instanceof - PsiPackageAccessibilityStatement) { - return null; - } - - JavaResolveResult[] results = ref.multiResolve(true); - String description; - if (results.length > 1) { - String t1 = format(ObjectUtil.notNull(results[0].getElement())); - String t2 = format(ObjectUtil.notNull(results[1].getElement())); - description = JavaErrorBundle.message("ambiguous.reference", refName.getText(), t1, t2); - } - else { - description = JavaErrorBundle.message("cannot.resolve.symbol", refName.getText()); - } - - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF) - .range(refName) - .descriptionAndTooltip(description) - .createUnconditionally(); - UnresolvedReferenceQuickFixProvider.registerReferenceFixes(ref, QuickFixActionRegistrar.create(info)); - return info; - } - - if (!result.isValidResult() && !PsiUtil.isInsideJavadocComment(ref)) { - if (!result.isAccessible()) { - String message = buildProblemWithAccessDescription(ref, result, resolved); - HighlightInfo info = - HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(refName).descriptionAndTooltip(message).create(); - if (result.isStaticsScopeCorrect()) { - registerAccessQuickFixAction((PsiMember)resolved, ref, info, result.getCurrentFileResolveScope()); - if (ref instanceof PsiReferenceExpression) { - QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createRenameWrongRefFix((PsiReferenceExpression)ref)); - } - } - if (info != null) UnresolvedReferenceQuickFixProvider.registerReferenceFixes(ref, QuickFixActionRegistrar.create(info)); - return info; - } + @Nullable + private static IntentionAction getChangeParameterClassFix(PsiType lType, PsiType rType) { + final PsiClass lClass = PsiUtil.resolveClassInClassTypeOnly(lType); + final PsiClass rClass = PsiUtil.resolveClassInClassTypeOnly(rType); - if (!result.isStaticsScopeCorrect()) { - String description = buildProblemWithStaticDescription(resolved); - HighlightInfo info = - HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(refName).descriptionAndTooltip(description).create(); - registerStaticProblemQuickFixAction(resolved, info, ref); - if (ref instanceof PsiReferenceExpression) { - QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createRenameWrongRefFix((PsiReferenceExpression)ref)); + if (rClass == null || lClass == null) { + return null; + } + if (rClass instanceof PsiAnonymousClass) { + return null; + } + if (rClass.isInheritor(lClass, true)) { + return null; + } + if (lClass.isInheritor(rClass, true)) { + return null; + } + if (lClass == rClass) { + return null; } - return info; - } - } - if ((resolved instanceof PsiLocalVariable || resolved instanceof PsiParameter) && !(resolved instanceof ImplicitVariable)) { - return HighlightControlFlowUtil.checkVariableMustBeFinal((PsiVariable)resolved, ref, languageLevel); - } - if (resolved instanceof PsiClass && ((PsiClass)resolved).getContainingClass() == null && PsiTreeUtil.getParentOfType(ref, - PsiImportStatementBase.class) != null && PsiUtil - .isFromDefaultPackage((PsiClass)resolved)) { - String description = JavaErrorBundle.message("cannot.resolve.symbol", refName.getText()); - return HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(refName).descriptionAndTooltip(description).create(); + return QuickFixFactory.getInstance().createChangeParameterClassFix(rClass, (PsiClassType)lType); + } + + private static void registerReplaceInaccessibleFieldWithGetterSetterFix( + PsiMember refElement, + PsiJavaCodeReferenceElement place, + PsiClass accessObjectClass, + HighlightInfo error + ) { + if (refElement instanceof PsiField && place instanceof PsiReferenceExpression) { + final PsiField psiField = (PsiField)refElement; + final PsiClass containingClass = psiField.getContainingClass(); + if (containingClass != null) { + if (PsiUtil.isOnAssignmentLeftHand((PsiExpression)place)) { + final PsiMethod setterPrototype = PropertyUtil.generateSetterPrototype(psiField); + final PsiMethod setter = containingClass.findMethodBySignature(setterPrototype, true); + if (setter != null && PsiUtil.isAccessible(setter, place, accessObjectClass)) { + final PsiElement element = PsiTreeUtil.skipParentsOfType(place, PsiParenthesizedExpression.class); + if (element instanceof PsiAssignmentExpression && ((PsiAssignmentExpression)element).getOperationTokenType() == JavaTokenType.EQ) { + QuickFixAction.registerQuickFixAction( + error, + QuickFixFactory.getInstance().createReplaceInaccessibleFieldWithGetterSetterFix( + place, + setter, + true + ) + ); + } + } + } + else if (PsiUtil.isAccessedForReading((PsiExpression)place)) { + final PsiMethod getterPrototype = PropertyUtil.generateGetterPrototype(psiField); + final PsiMethod getter = containingClass.findMethodBySignature(getterPrototype, true); + if (getter != null && PsiUtil.isAccessible(getter, place, accessObjectClass)) { + QuickFixAction.registerQuickFixAction( + error, + QuickFixFactory.getInstance().createReplaceInaccessibleFieldWithGetterSetterFix(place, getter, false) + ); + } + } + } + } } - return null; - } + @Nullable + public static HighlightInfo checkFeature( + @Nonnull PsiElement element, + @Nonnull JavaFeature feature, + @Nonnull LanguageLevel level, + @Nonnull PsiFile file + ) { + if (file.getManager().isInProject(file) && !level.isAtLeast(feature.getMinimumLevel())) { + String message = getUnsupportedFeatureMessage(element, feature, level, file); + HighlightInfo info = + HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(message).create(); + QuickFixAction.registerQuickFixAction( + info, + QuickFixFactory.getInstance().createIncreaseLanguageLevelFix(feature.getMinimumLevel()) + ); + QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createShowModulePropertiesFix(element)); + return info; + } - @Nonnull - private static String format(@Nonnull PsiElement element) { - if (element instanceof PsiClass) { - return formatClass((PsiClass)element); - } - if (element instanceof PsiMethod) { - return JavaHighlightUtil.formatMethod((PsiMethod)element); - } - if (element instanceof PsiField) { - return formatField((PsiField)element); + return null; } - return ElementDescriptionUtil.getElementDescription(element, HighlightUsagesDescriptionLocation.INSTANCE); - } - private static PsiElement getOuterReferenceParent(PsiJavaCodeReferenceElement ref) { - PsiElement element = ref; - while (element instanceof PsiJavaCodeReferenceElement) { - element = element.getParent(); - } - return element; - } + @RequiredReadAction + private static String getUnsupportedFeatureMessage(PsiElement element, JavaFeature feature, LanguageLevel level, PsiFile file) { + String name = feature.getFeatureName(); + String message = JavaErrorBundle.message("insufficient.language.level", name, level.getCompilerComplianceDefaultOption()); - @Nullable - public static HighlightInfo checkPackageAndClassConflict(@Nonnull PsiJavaCodeReferenceElement ref, @Nonnull PsiFile containingFile) { - if (ref.isQualified() && getOuterReferenceParent(ref) instanceof PsiPackageStatement) { - VirtualFile file = containingFile.getVirtualFile(); - if (file != null) { - Module module = ProjectFileIndex.SERVICE.getInstance(ref.getProject()).getModuleForFile(file); + Module module = element.getModule(); if (module != null) { - GlobalSearchScope scope = GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module, false); - PsiClass aClass = JavaPsiFacade.getInstance(ref.getProject()).findClass(ref.getCanonicalText(), scope); - if (aClass != null) { - String message = JavaErrorBundle.message("package.clashes.with.class", ref.getText()); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(ref).descriptionAndTooltip(message).create(); - } - } - } - } - - return null; - } - - @Nullable - public static HighlightInfo checkElementInReferenceList(@Nonnull PsiJavaCodeReferenceElement ref, - @Nonnull PsiReferenceList referenceList, - @Nonnull JavaResolveResult resolveResult) { - PsiElement resolved = resolveResult.getElement(); - HighlightInfo highlightInfo = null; - PsiElement refGrandParent = referenceList.getParent(); - if (resolved instanceof PsiClass aClass) { - if (refGrandParent instanceof PsiClass parentClass) { - if (refGrandParent instanceof PsiTypeParameter typeParameter) { - highlightInfo = GenericsHighlightUtil.checkElementInTypeParameterExtendsList(referenceList, typeParameter, resolveResult, ref); - } - else if (referenceList.equals(parentClass.getImplementsList()) || - referenceList.equals(parentClass.getExtendsList())) { - highlightInfo = HighlightClassUtil.checkExtendsClassAndImplementsInterface(referenceList, resolveResult, ref); - if (highlightInfo == null) { - highlightInfo = HighlightClassUtil.checkCannotInheritFromFinal(aClass, ref); - } - if (highlightInfo == null) { - // TODO highlightInfo = HighlightClassUtil.checkExtendsProhibitedClass(aClass, parentClass, ref); - } - if (highlightInfo == null) { - highlightInfo = GenericsHighlightUtil.checkCannotInheritFromTypeParameter(aClass, ref); - } - if (highlightInfo == null) { - // TODO highlightInfo = HighlightClassUtil.checkExtendsSealedClass(parentClass, aClass, ref); - } - } - } - else if (refGrandParent instanceof PsiMethod method && method.getThrowsList() == referenceList) { - highlightInfo = checkMustBeThrowable(aClass, ref); - } - } - else if (refGrandParent instanceof PsiMethod method && referenceList == method.getThrowsList()) { - String description = JavaErrorBundle.message("class.name.expected"); - highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(ref).descriptionAndTooltip(description).create(); - } - return highlightInfo; - } - - - public static boolean isSerializationImplicitlyUsedField(@Nonnull PsiField field) { - final String name = field.getName(); - if (!SERIAL_VERSION_UID_FIELD_NAME.equals(name) && !SERIAL_PERSISTENT_FIELDS_FIELD_NAME.equals(name)) { - return false; - } - if (!field.hasModifierProperty(PsiModifier.STATIC)) { - return false; - } - PsiClass aClass = field.getContainingClass(); - return aClass == null || JavaHighlightUtil.isSerializable(aClass); - } - - @Nullable - public static HighlightInfo checkClassReferenceAfterQualifier(@Nonnull final PsiReferenceExpression expression, - final PsiElement resolved) { - if (!(resolved instanceof PsiClass)) { - return null; - } - final PsiExpression qualifier = expression.getQualifierExpression(); - if (qualifier == null) { - return null; - } - if (qualifier instanceof PsiReferenceExpression) { - PsiElement qualifierResolved = ((PsiReferenceExpression)qualifier).resolve(); - if (qualifierResolved instanceof PsiClass || qualifierResolved instanceof PsiPackage) { - return null; - } - } - String description = JavaErrorBundle.message("expected.class.or.package"); - HighlightInfo info = - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(qualifier).descriptionAndTooltip(description).create(); - QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createRemoveQualifierFix(qualifier, expression, (PsiClass)resolved)); - return info; - } - - public static void registerChangeVariableTypeFixes(@Nonnull PsiVariable parameter, - PsiType itemType, - @Nullable PsiExpression expr, - @Nonnull HighlightInfo highlightInfo) { - for (IntentionAction action : getChangeVariableTypeFixes(parameter, itemType)) { - QuickFixAction.registerQuickFixAction(highlightInfo, action); - } - if (expr instanceof PsiMethodCallExpression) { - final PsiMethod method = ((PsiMethodCallExpression)expr).resolveMethod(); - if (method != null) { - QuickFixAction.registerQuickFixAction(highlightInfo, - PriorityActionWrapper.lowPriority(method, - QuickFixFactory.getInstance().createMethodReturnFix(method, - parameter.getType(), - true))); - } - } - } - - @Nonnull - public static List getChangeVariableTypeFixes(@Nonnull PsiVariable parameter, PsiType itemType) { - if (itemType instanceof PsiMethodReferenceType) { - return Collections.emptyList(); - } - List result = new ArrayList<>(); - if (itemType != null) { - for (ChangeVariableTypeQuickFixProvider fixProvider : Extensions.getExtensions(ChangeVariableTypeQuickFixProvider.EP_NAME)) { - Collections.addAll(result, fixProvider.getFixes(parameter, itemType)); - } - } - IntentionAction changeFix = getChangeParameterClassFix(parameter.getType(), itemType); - if (changeFix != null) { - result.add(changeFix); - } - return result; - } - - @Nullable - public static HighlightInfo checkAnnotationMethodParameters(@Nonnull PsiParameterList list) { - final PsiElement parent = list.getParent(); - if (PsiUtil.isAnnotationMethod(parent) && list.getParametersCount() > 0) { - final String message = JavaErrorBundle.message("annotation.interface.members.may.not.have.parameters"); - final HighlightInfo highlightInfo = - HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(list).descriptionAndTooltip(message).create(); - QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createRemoveParameterListFix((PsiMethod)parent)); - return highlightInfo; - } - return null; - } - - @Nullable - public static HighlightInfo checkForStatement(@Nonnull PsiForStatement statement) { - PsiStatement init = statement.getInitialization(); - if (init == null || init instanceof PsiEmptyStatement || init instanceof PsiDeclarationStatement && ArrayUtil.getFirstElement(((PsiDeclarationStatement)init) - .getDeclaredElements()) - instanceof PsiLocalVariable || init instanceof PsiExpressionStatement || init instanceof PsiExpressionListStatement) { - return null; - } - - String message = JavaErrorBundle.message("invalid.statement"); - return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(init).descriptionAndTooltip(message).create(); - } - - private static void registerChangeParameterClassFix(PsiType lType, PsiType rType, HighlightInfo info) { - QuickFixAction.registerQuickFixAction(info, getChangeParameterClassFix(lType, rType)); - } - - @Nullable - private static IntentionAction getChangeParameterClassFix(PsiType lType, PsiType rType) { - final PsiClass lClass = PsiUtil.resolveClassInClassTypeOnly(lType); - final PsiClass rClass = PsiUtil.resolveClassInClassTypeOnly(rType); - - if (rClass == null || lClass == null) { - return null; - } - if (rClass instanceof PsiAnonymousClass) { - return null; - } - if (rClass.isInheritor(lClass, true)) { - return null; - } - if (lClass.isInheritor(rClass, true)) { - return null; - } - if (lClass == rClass) { - return null; - } - - return QuickFixFactory.getInstance().createChangeParameterClassFix(rClass, (PsiClassType)lType); - } - - private static void registerReplaceInaccessibleFieldWithGetterSetterFix(PsiMember refElement, - PsiJavaCodeReferenceElement place, - PsiClass accessObjectClass, - HighlightInfo error) { - if (refElement instanceof PsiField && place instanceof PsiReferenceExpression) { - final PsiField psiField = (PsiField)refElement; - final PsiClass containingClass = psiField.getContainingClass(); - if (containingClass != null) { - if (PsiUtil.isOnAssignmentLeftHand((PsiExpression)place)) { - final PsiMethod setterPrototype = PropertyUtil.generateSetterPrototype(psiField); - final PsiMethod setter = containingClass.findMethodBySignature(setterPrototype, true); - if (setter != null && PsiUtil.isAccessible(setter, place, accessObjectClass)) { - final PsiElement element = PsiTreeUtil.skipParentsOfType(place, PsiParenthesizedExpression.class); - if (element instanceof PsiAssignmentExpression && ((PsiAssignmentExpression)element).getOperationTokenType() == JavaTokenType.EQ) { - QuickFixAction.registerQuickFixAction(error, - QuickFixFactory.getInstance().createReplaceInaccessibleFieldWithGetterSetterFix(place, - setter, - true)); - } - } - } - else if (PsiUtil.isAccessedForReading((PsiExpression)place)) { - final PsiMethod getterPrototype = PropertyUtil.generateGetterPrototype(psiField); - final PsiMethod getter = containingClass.findMethodBySignature(getterPrototype, true); - if (getter != null && PsiUtil.isAccessible(getter, place, accessObjectClass)) { - QuickFixAction.registerQuickFixAction(error, - QuickFixFactory.getInstance().createReplaceInaccessibleFieldWithGetterSetterFix(place, - getter, - false)); - } - } - } - } - } - - @Nullable - public static HighlightInfo checkFeature(@Nonnull PsiElement element, - @Nonnull JavaFeature feature, - @Nonnull LanguageLevel level, - @Nonnull PsiFile file) { - if (file.getManager().isInProject(file) && !level.isAtLeast(feature.getMinimumLevel())) { - String message = getUnsupportedFeatureMessage(element, feature, level, file); - HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(message).create(); - QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createIncreaseLanguageLevelFix(feature.getMinimumLevel())); - QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createShowModulePropertiesFix(element)); - return info; - } - - return null; - } - - @RequiredReadAction - private static String getUnsupportedFeatureMessage(PsiElement element, JavaFeature feature, LanguageLevel level, PsiFile file) { - String name = feature.getFeatureName(); - String message = JavaErrorBundle.message("insufficient.language.level", name, level.getCompilerComplianceDefaultOption()); - - Module module = element.getModule(); - if (module != null) { - LanguageLevel moduleLanguageLevel = EffectiveLanguageLevelUtil.getEffectiveLanguageLevel(module); - if (moduleLanguageLevel.isAtLeast(feature.getMinimumLevel())) { - for (FilePropertyPusher pusher : FilePropertyPusher.EP_NAME.getExtensionList()) { - if (pusher instanceof JavaLanguageLevelPusher) { - String newMessage = ((JavaLanguageLevelPusher)pusher).getInconsistencyLanguageLevelMessage(message, element, level, file); - if (newMessage != null) { - return newMessage; - } - } - } - } - } - - return message; - } + LanguageLevel moduleLanguageLevel = EffectiveLanguageLevelUtil.getEffectiveLanguageLevel(module); + if (moduleLanguageLevel.isAtLeast(feature.getMinimumLevel())) { + for (FilePropertyPusher pusher : FilePropertyPusher.EP_NAME.getExtensionList()) { + if (pusher instanceof JavaLanguageLevelPusher) { + String newMessage = + ((JavaLanguageLevelPusher)pusher).getInconsistencyLanguageLevelMessage(message, element, level, file); + if (newMessage != null) { + return newMessage; + } + } + } + } + } + + return message; + } } \ No newline at end of file diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/quickfix/ChangeVariableTypeQuickFixProvider.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/quickfix/ChangeVariableTypeQuickFixProvider.java index db31251516..321a1777e8 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/quickfix/ChangeVariableTypeQuickFixProvider.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInsight/quickfix/ChangeVariableTypeQuickFixProvider.java @@ -13,11 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -/* - * User: anna - * Date: 27-Aug-2009 - */ package com.intellij.java.analysis.impl.codeInsight.quickfix; import consulo.annotation.component.ComponentScope; @@ -27,9 +22,13 @@ import com.intellij.java.language.psi.PsiType; import com.intellij.java.language.psi.PsiVariable; +/** + * User: anna + * Date: 27-Aug-2009 + */ @ExtensionAPI(ComponentScope.APPLICATION) public interface ChangeVariableTypeQuickFixProvider { - ExtensionPointName EP_NAME = ExtensionPointName.create(ChangeVariableTypeQuickFixProvider.class); + ExtensionPointName EP_NAME = ExtensionPointName.create(ChangeVariableTypeQuickFixProvider.class); - IntentionAction[] getFixes(PsiVariable variable, PsiType toReturn); + IntentionAction[] getFixes(PsiVariable variable, PsiType toReturn); } \ No newline at end of file diff --git a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/canBeFinal/CanBeFinalHandler.java b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/canBeFinal/CanBeFinalHandler.java index b1b880cda6..3278fcb9f6 100644 --- a/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/canBeFinal/CanBeFinalHandler.java +++ b/java-analysis-impl/src/main/java/com/intellij/java/analysis/impl/codeInspection/canBeFinal/CanBeFinalHandler.java @@ -26,14 +26,16 @@ */ @ExtensionAPI(ComponentScope.APPLICATION) public abstract class CanBeFinalHandler { - public static final ExtensionPointName EP_NAME = ExtensionPointName.create(CanBeFinalHandler.class); + public static final ExtensionPointName EP_NAME = ExtensionPointName.create(CanBeFinalHandler.class); - public abstract boolean canBeFinal(PsiMember member); + public abstract boolean canBeFinal(PsiMember member); - public static boolean allowToBeFinal(PsiMember member) { - for (CanBeFinalHandler handler : EP_NAME.getExtensionList()) { - if (!handler.canBeFinal(member)) return false; + public static boolean allowToBeFinal(PsiMember member) { + for (CanBeFinalHandler handler : EP_NAME.getExtensionList()) { + if (!handler.canBeFinal(member)) { + return false; + } + } + return true; } - return true; - } } diff --git a/java-compiler-impl/src/main/java/com/intellij/java/compiler/impl/javaCompiler/BackendCompiler.java b/java-compiler-impl/src/main/java/com/intellij/java/compiler/impl/javaCompiler/BackendCompiler.java index aa641d2e49..4da1b38bb3 100644 --- a/java-compiler-impl/src/main/java/com/intellij/java/compiler/impl/javaCompiler/BackendCompiler.java +++ b/java-compiler-impl/src/main/java/com/intellij/java/compiler/impl/javaCompiler/BackendCompiler.java @@ -28,36 +28,43 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.io.IOException; @ExtensionAPI(ComponentScope.PROJECT) public interface BackendCompiler { - ExtensionPointName EP_NAME = ExtensionPointName.create(BackendCompiler.class); - - @Nonnull - String getPresentableName(); - - @Nullable - default OutputParser createErrorParser(BackendCompilerProcessBuilder processBuilder, @Nonnull String outputDir, ProcessHandler process) { - return null; - } - - @Nullable - default OutputParser createOutputParser(BackendCompilerProcessBuilder processBuilder, @Nonnull String outputDir) { - return null; - } - - @Nullable - default BackendCompilerMonitor createMonitor(BackendCompilerProcessBuilder processBuilder) { - return null; - } - - default boolean checkCompiler(final CompileScope scope) { - return true; - } - - @Nonnull - BackendCompilerProcessBuilder prepareProcess(@Nonnull ModuleChunk chunk, - @Nonnull String outputDir, - @Nonnull CompileContext compileContext) throws IOException; + ExtensionPointName EP_NAME = ExtensionPointName.create(BackendCompiler.class); + + @Nonnull + String getPresentableName(); + + @Nullable + default OutputParser createErrorParser( + BackendCompilerProcessBuilder processBuilder, + @Nonnull String outputDir, + ProcessHandler process + ) { + return null; + } + + @Nullable + default OutputParser createOutputParser(BackendCompilerProcessBuilder processBuilder, @Nonnull String outputDir) { + return null; + } + + @Nullable + default BackendCompilerMonitor createMonitor(BackendCompilerProcessBuilder processBuilder) { + return null; + } + + default boolean checkCompiler(final CompileScope scope) { + return true; + } + + @Nonnull + BackendCompilerProcessBuilder prepareProcess( + @Nonnull ModuleChunk chunk, + @Nonnull String outputDir, + @Nonnull CompileContext compileContext + ) throws IOException; } diff --git a/java-compiler-impl/src/main/java/com/intellij/java/compiler/impl/javaCompiler/JavaCompilerConfiguration.java b/java-compiler-impl/src/main/java/com/intellij/java/compiler/impl/javaCompiler/JavaCompilerConfiguration.java index 305722a957..bcef528162 100644 --- a/java-compiler-impl/src/main/java/com/intellij/java/compiler/impl/javaCompiler/JavaCompilerConfiguration.java +++ b/java-compiler-impl/src/main/java/com/intellij/java/compiler/impl/javaCompiler/JavaCompilerConfiguration.java @@ -25,379 +25,382 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.io.File; import java.util.*; @Singleton @State( - name = "JavaCompilerConfiguration", - storages = { - @Storage(file = StoragePathMacros.PROJECT_CONFIG_DIR + "/compiler.xml") - }) + name = "JavaCompilerConfiguration", + storages = { + @Storage(file = StoragePathMacros.PROJECT_CONFIG_DIR + "/compiler.xml") + }) @ServiceAPI(ComponentScope.PROJECT) @ServiceImpl public class JavaCompilerConfiguration implements PersistentStateComponent { - @Nonnull - public static JavaCompilerConfiguration getInstance(@Nonnull Project project) { - return ServiceManager.getService(project, JavaCompilerConfiguration.class); - } + @Nonnull + public static JavaCompilerConfiguration getInstance(@Nonnull Project project) { + return ServiceManager.getService(project, JavaCompilerConfiguration.class); + } - public static final String ANNOTATION_PROCESSING = "annotation-processing"; - public static final String BYTECODE_TARGET_LEVEL = "bytecode-target-level"; + public static final String ANNOTATION_PROCESSING = "annotation-processing"; + public static final String BYTECODE_TARGET_LEVEL = "bytecode-target-level"; - public static final String ADD_NOTNULL_ASSERTIONS = "add-not-null-assertions"; - public static final String ENTRY = "entry"; - public static final String NAME = "name"; - public static final String ENABLED = "enabled"; - public static final String MODULE = "module"; - public static final String TARGET_ATTRIBUTE = "target"; + public static final String ADD_NOTNULL_ASSERTIONS = "add-not-null-assertions"; + public static final String ENTRY = "entry"; + public static final String NAME = "name"; + public static final String ENABLED = "enabled"; + public static final String MODULE = "module"; + public static final String TARGET_ATTRIBUTE = "target"; - public static final String DEFAULT_COMPILER = "JavacCompiler"; + public static final String DEFAULT_COMPILER = "JavacCompiler"; - @Nonnull - private final Project myProject; + @Nonnull + private final Project myProject; - private BackendCompiler myBackendCompilerCache; + private BackendCompiler myBackendCompilerCache; - private final ProcessorConfigProfile myDefaultProcessorsProfile = new ProcessorConfigProfileImpl("Default"); - private final List myModuleProcessorProfiles = new ArrayList(); + private final ProcessorConfigProfile myDefaultProcessorsProfile = new ProcessorConfigProfileImpl("Default"); + private final List myModuleProcessorProfiles = new ArrayList(); - // the map is calculated by module processor profiles list for faster access to module settings - private Map myProcessorsProfilesMap = null; - private boolean myAddNotNullAssertions; + // the map is calculated by module processor profiles list for faster access to module settings + private Map myProcessorsProfilesMap = null; + private boolean myAddNotNullAssertions; - @Nullable - private String myBytecodeTargetLevel = null; // null means compiler default + @Nullable + private String myBytecodeTargetLevel = null; // null means compiler default - @Deprecated - @DeprecationInfo("We used JavaModuleExtension for store info about bytecode version") - private final Map myModuleBytecodeTarget = new HashMap(); + @Deprecated + @DeprecationInfo("We used JavaModuleExtension for store info about bytecode version") + private final Map myModuleBytecodeTarget = new HashMap(); - @Inject - public JavaCompilerConfiguration(@Nonnull Project project) { - myProject = project; - } + @Inject + public JavaCompilerConfiguration(@Nonnull Project project) { + myProject = project; + } + + @Nullable + @RequiredReadAction + public String getBytecodeTargetLevel(@Nonnull Module module) { + String level = myModuleBytecodeTarget.get(module.getName()); + if (level != null) { + return level.isEmpty() ? null : level; + } - @Nullable - @RequiredReadAction - public String getBytecodeTargetLevel(@Nonnull Module module) { - String level = myModuleBytecodeTarget.get(module.getName()); - if (level != null) { - return level.isEmpty() ? null : level; + JavaModuleExtension extension = ModuleUtilCore.getExtension(module, JavaModuleExtension.class); + if (extension != null) { + String bytecodeVersion = extension.getBytecodeVersion(); + if (bytecodeVersion != null) { + return StringUtil.nullize(bytecodeVersion); + } + } + return myBytecodeTargetLevel; } - JavaModuleExtension extension = ModuleUtilCore.getExtension(module, JavaModuleExtension.class); - if (extension != null) { - String bytecodeVersion = extension.getBytecodeVersion(); - if (bytecodeVersion != null) { - return StringUtil.nullize(bytecodeVersion); - } + @Nonnull + public BackendCompiler getActiveCompiler() { + if (myBackendCompilerCache == null) { + myBackendCompilerCache = findCompiler(DEFAULT_COMPILER); + } + return myBackendCompilerCache; } - return myBytecodeTargetLevel; - } - @Nonnull - public BackendCompiler getActiveCompiler() { - if (myBackendCompilerCache == null) { - myBackendCompilerCache = findCompiler(DEFAULT_COMPILER); + public void setActiveCompiler(@Nonnull BackendCompiler key) { + myBackendCompilerCache = key; } - return myBackendCompilerCache; - } - - public void setActiveCompiler(@Nonnull BackendCompiler key) { - myBackendCompilerCache = key; - } - - @Nullable - public BackendCompiler findCompiler(@Nonnull String className) { - for (BackendCompiler backendCompiler : BackendCompiler.EP_NAME.getExtensionList(myProject)) { - if (className.equals(backendCompiler.getClass().getSimpleName())) { - return backendCompiler; - } + + @Nullable + public BackendCompiler findCompiler(@Nonnull String className) { + for (BackendCompiler backendCompiler : BackendCompiler.EP_NAME.getExtensionList(myProject)) { + if (className.equals(backendCompiler.getClass().getSimpleName())) { + return backendCompiler; + } + } + return null; } - return null; - } - - @Nullable - @Override - public Element getState() { - Element parentNode = new Element("state"); - - final Element annotationProcessingSettings = addChild(parentNode, ANNOTATION_PROCESSING); - final Element defaultProfileElem = addChild(annotationProcessingSettings, "profile").setAttribute("default", "true"); - AnnotationProcessorProfileSerializer.writeExternal(myDefaultProcessorsProfile, defaultProfileElem); - for (ProcessorConfigProfile profile : myModuleProcessorProfiles) { - final Element profileElem = addChild(annotationProcessingSettings, "profile").setAttribute("default", "false"); - AnnotationProcessorProfileSerializer.writeExternal(profile, profileElem); + + @Nullable + @Override + public Element getState() { + Element parentNode = new Element("state"); + + final Element annotationProcessingSettings = addChild(parentNode, ANNOTATION_PROCESSING); + final Element defaultProfileElem = addChild(annotationProcessingSettings, "profile").setAttribute("default", "true"); + AnnotationProcessorProfileSerializer.writeExternal(myDefaultProcessorsProfile, defaultProfileElem); + for (ProcessorConfigProfile profile : myModuleProcessorProfiles) { + final Element profileElem = addChild(annotationProcessingSettings, "profile").setAttribute("default", "false"); + AnnotationProcessorProfileSerializer.writeExternal(profile, profileElem); + } + + parentNode.setAttribute( + "compiler", + myBackendCompilerCache == null ? DEFAULT_COMPILER : myBackendCompilerCache.getClass().getSimpleName() + ); + if (myAddNotNullAssertions) { + parentNode.setAttribute(ADD_NOTNULL_ASSERTIONS, String.valueOf(Boolean.TRUE)); + } + + if (!StringUtil.isEmpty(myBytecodeTargetLevel) || !myModuleBytecodeTarget.isEmpty()) { + final Element bytecodeTarget = addChild(parentNode, BYTECODE_TARGET_LEVEL); + if (!StringUtil.isEmpty(myBytecodeTargetLevel)) { + bytecodeTarget.setAttribute(TARGET_ATTRIBUTE, myBytecodeTargetLevel); + } + if (!myModuleBytecodeTarget.isEmpty()) { + final List moduleNames = new ArrayList(myModuleBytecodeTarget.keySet()); + Collections.sort(moduleNames, String.CASE_INSENSITIVE_ORDER); + for (String name : moduleNames) { + final Element moduleElement = addChild(bytecodeTarget, MODULE); + moduleElement.setAttribute(NAME, name); + final String value = myModuleBytecodeTarget.get(name); + moduleElement.setAttribute(TARGET_ATTRIBUTE, value != null ? value : ""); + } + } + } + return parentNode; } - parentNode.setAttribute("compiler", - myBackendCompilerCache == null ? DEFAULT_COMPILER : myBackendCompilerCache.getClass().getSimpleName()); - if (myAddNotNullAssertions) { - parentNode.setAttribute(ADD_NOTNULL_ASSERTIONS, String.valueOf(Boolean.TRUE)); + private static Element addChild(Element parent, final String childName) { + final Element child = new Element(childName); + parent.addContent(child); + return child; } - if (!StringUtil.isEmpty(myBytecodeTargetLevel) || !myModuleBytecodeTarget.isEmpty()) { - final Element bytecodeTarget = addChild(parentNode, BYTECODE_TARGET_LEVEL); - if (!StringUtil.isEmpty(myBytecodeTargetLevel)) { - bytecodeTarget.setAttribute(TARGET_ATTRIBUTE, myBytecodeTargetLevel); - } - if (!myModuleBytecodeTarget.isEmpty()) { - final List moduleNames = new ArrayList(myModuleBytecodeTarget.keySet()); - Collections.sort(moduleNames, String.CASE_INSENSITIVE_ORDER); - for (String name : moduleNames) { - final Element moduleElement = addChild(bytecodeTarget, MODULE); - moduleElement.setAttribute(NAME, name); - final String value = myModuleBytecodeTarget.get(name); - moduleElement.setAttribute(TARGET_ATTRIBUTE, value != null ? value : ""); + @Override + public void loadState(Element parentNode) { + myBackendCompilerCache = findCompiler(parentNode.getAttributeValue("compiler")); + myAddNotNullAssertions = Boolean.parseBoolean(parentNode.getAttributeValue(ADD_NOTNULL_ASSERTIONS, String.valueOf(Boolean.FALSE))); + + final Element annotationProcessingSettings = parentNode.getChild(ANNOTATION_PROCESSING); + if (annotationProcessingSettings != null) { + final List profiles = annotationProcessingSettings.getChildren("profile"); + if (!profiles.isEmpty()) { + for (Object elem : profiles) { + final Element profileElement = (Element)elem; + final boolean isDefault = "true".equals(profileElement.getAttributeValue("default")); + if (isDefault) { + AnnotationProcessorProfileSerializer.readExternal(myDefaultProcessorsProfile, profileElement); + } + else { + final ProcessorConfigProfile profile = new ProcessorConfigProfileImpl(""); + AnnotationProcessorProfileSerializer.readExternal(profile, profileElement); + myModuleProcessorProfiles.add(profile); + } + } + } + else { + // assuming older format + loadProfilesFromOldFormat(annotationProcessingSettings); + } } - } - } - return parentNode; - } - - private static Element addChild(Element parent, final String childName) { - final Element child = new Element(childName); - parent.addContent(child); - return child; - } - - @Override - public void loadState(Element parentNode) { - myBackendCompilerCache = findCompiler(parentNode.getAttributeValue("compiler")); - myAddNotNullAssertions = Boolean.parseBoolean(parentNode.getAttributeValue(ADD_NOTNULL_ASSERTIONS, String.valueOf(Boolean.FALSE))); - - final Element annotationProcessingSettings = parentNode.getChild(ANNOTATION_PROCESSING); - if (annotationProcessingSettings != null) { - final List profiles = annotationProcessingSettings.getChildren("profile"); - if (!profiles.isEmpty()) { - for (Object elem : profiles) { - final Element profileElement = (Element)elem; - final boolean isDefault = "true".equals(profileElement.getAttributeValue("default")); - if (isDefault) { - AnnotationProcessorProfileSerializer.readExternal(myDefaultProcessorsProfile, profileElement); - } - else { - final ProcessorConfigProfile profile = new ProcessorConfigProfileImpl(""); - AnnotationProcessorProfileSerializer.readExternal(profile, profileElement); - myModuleProcessorProfiles.add(profile); - } + + myBytecodeTargetLevel = null; + myModuleBytecodeTarget.clear(); + Element bytecodeTargetElement = parentNode.getChild(BYTECODE_TARGET_LEVEL); + if (bytecodeTargetElement != null) { + myBytecodeTargetLevel = bytecodeTargetElement.getAttributeValue(TARGET_ATTRIBUTE); + for (Element elem : bytecodeTargetElement.getChildren(MODULE)) { + String name = elem.getAttributeValue(NAME); + if (name == null) { + continue; + } + String target = elem.getAttributeValue(TARGET_ATTRIBUTE); + if (target == null) { + continue; + } + myModuleBytecodeTarget.put(name, target); + } } - } - else { - // assuming older format - loadProfilesFromOldFormat(annotationProcessingSettings); - } + } - myBytecodeTargetLevel = null; - myModuleBytecodeTarget.clear(); - Element bytecodeTargetElement = parentNode.getChild(BYTECODE_TARGET_LEVEL); - if (bytecodeTargetElement != null) { - myBytecodeTargetLevel = bytecodeTargetElement.getAttributeValue(TARGET_ATTRIBUTE); - for (Element elem : bytecodeTargetElement.getChildren(MODULE)) { - String name = elem.getAttributeValue(NAME); - if (name == null) { - continue; + private void loadProfilesFromOldFormat(Element processing) { + // collect data + final boolean isEnabled = Boolean.parseBoolean(processing.getAttributeValue(ENABLED, "false")); + final boolean isUseClasspath = Boolean.parseBoolean(processing.getAttributeValue("useClasspath", "true")); + final StringBuilder processorPath = new StringBuilder(); + final Set optionPairs = new HashSet(); + final Set processors = new HashSet(); + final List> modulesToProcess = new ArrayList>(); + + for (Object child : processing.getChildren("processorPath")) { + final Element pathElement = (Element)child; + final String path = pathElement.getAttributeValue("value", (String)null); + if (path != null) { + if (processorPath.length() > 0) { + processorPath.append(File.pathSeparator); + } + processorPath.append(path); + } } - String target = elem.getAttributeValue(TARGET_ATTRIBUTE); - if (target == null) { - continue; + + for (Object child : processing.getChildren("processor")) { + final Element processorElement = (Element)child; + final String proc = processorElement.getAttributeValue(NAME, (String)null); + if (proc != null) { + processors.add(proc); + } + final StringTokenizer tokenizer = new StringTokenizer(processorElement.getAttributeValue("options", ""), " ", false); + while (tokenizer.hasMoreTokens()) { + final String pair = tokenizer.nextToken(); + optionPairs.add(pair); + } + } + + for (Object child : processing.getChildren("processModule")) { + final Element moduleElement = (Element)child; + final String name = moduleElement.getAttributeValue(NAME, (String)null); + if (name == null) { + continue; + } + final String dir = moduleElement.getAttributeValue("generatedDirName", (String)null); + modulesToProcess.add(Pair.create(name, dir)); } - myModuleBytecodeTarget.put(name, target); - } - } - } - - private void loadProfilesFromOldFormat(Element processing) { - // collect data - final boolean isEnabled = Boolean.parseBoolean(processing.getAttributeValue(ENABLED, "false")); - final boolean isUseClasspath = Boolean.parseBoolean(processing.getAttributeValue("useClasspath", "true")); - final StringBuilder processorPath = new StringBuilder(); - final Set optionPairs = new HashSet(); - final Set processors = new HashSet(); - final List> modulesToProcess = new ArrayList>(); - - for (Object child : processing.getChildren("processorPath")) { - final Element pathElement = (Element)child; - final String path = pathElement.getAttributeValue("value", (String)null); - if (path != null) { + myDefaultProcessorsProfile.setEnabled(false); + myDefaultProcessorsProfile.setObtainProcessorsFromClasspath(isUseClasspath); if (processorPath.length() > 0) { - processorPath.append(File.pathSeparator); + myDefaultProcessorsProfile.setProcessorPath(processorPath.toString()); + } + if (!optionPairs.isEmpty()) { + for (String pair : optionPairs) { + final int index = pair.indexOf("="); + if (index > 0) { + myDefaultProcessorsProfile.setOption(pair.substring(0, index), pair.substring(index + 1)); + } + } + } + for (String processor : processors) { + myDefaultProcessorsProfile.addProcessor(processor); } - processorPath.append(path); - } - } - for (Object child : processing.getChildren("processor")) { - final Element processorElement = (Element)child; - final String proc = processorElement.getAttributeValue(NAME, (String)null); - if (proc != null) { - processors.add(proc); - } - final StringTokenizer tokenizer = new StringTokenizer(processorElement.getAttributeValue("options", ""), " ", false); - while (tokenizer.hasMoreTokens()) { - final String pair = tokenizer.nextToken(); - optionPairs.add(pair); - } - } + final Map> dirNameToModulesMap = new HashMap>(); + for (Pair moduleDirPair : modulesToProcess) { + final String dir = moduleDirPair.getSecond(); + Set set = dirNameToModulesMap.get(dir); + if (set == null) { + set = new HashSet(); + dirNameToModulesMap.put(dir, set); + } + set.add(moduleDirPair.getFirst()); + } - for (Object child : processing.getChildren("processModule")) { - final Element moduleElement = (Element)child; - final String name = moduleElement.getAttributeValue(NAME, (String)null); - if (name == null) { - continue; - } - final String dir = moduleElement.getAttributeValue("generatedDirName", (String)null); - modulesToProcess.add(Pair.create(name, dir)); + int profileIndex = 0; + for (Map.Entry> entry : dirNameToModulesMap.entrySet()) { + final String dirName = entry.getKey(); + final ProcessorConfigProfile profile = new ProcessorConfigProfileImpl(myDefaultProcessorsProfile); + profile.setName("Profile" + (++profileIndex)); + profile.setEnabled(isEnabled); + profile.setGeneratedSourcesDirectoryName(dirName, false); + for (String moduleName : entry.getValue()) { + profile.addModuleName(moduleName); + } + myModuleProcessorProfiles.add(profile); + } } - myDefaultProcessorsProfile.setEnabled(false); - myDefaultProcessorsProfile.setObtainProcessorsFromClasspath(isUseClasspath); - if (processorPath.length() > 0) { - myDefaultProcessorsProfile.setProcessorPath(processorPath.toString()); - } - if (!optionPairs.isEmpty()) { - for (String pair : optionPairs) { - final int index = pair.indexOf("="); - if (index > 0) { - myDefaultProcessorsProfile.setOption(pair.substring(0, index), pair.substring(index + 1)); + @Nonnull + public ProcessorConfigProfile getAnnotationProcessingConfiguration(Module module) { + Map map = myProcessorsProfilesMap; + if (map == null) { + map = new HashMap(); + final Map namesMap = new HashMap(); + for (Module m : ModuleManager.getInstance(module.getProject()).getModules()) { + namesMap.put(m.getName(), m); + } + if (!namesMap.isEmpty()) { + for (ProcessorConfigProfile profile : myModuleProcessorProfiles) { + for (String name : profile.getModuleNames()) { + final Module mod = namesMap.get(name); + if (mod != null) { + map.put(mod, profile); + } + } + } + } + myProcessorsProfilesMap = map; } - } + final ProcessorConfigProfile profile = map.get(module); + return profile != null ? profile : myDefaultProcessorsProfile; + } + + @Nonnull + public ProcessorConfigProfile getDefaultProcessorProfile() { + return myDefaultProcessorsProfile; } - for (String processor : processors) { - myDefaultProcessorsProfile.addProcessor(processor); + + @Nonnull + public List getModuleProcessorProfiles() { + return myModuleProcessorProfiles; } - final Map> dirNameToModulesMap = new HashMap>(); - for (Pair moduleDirPair : modulesToProcess) { - final String dir = moduleDirPair.getSecond(); - Set set = dirNameToModulesMap.get(dir); - if (set == null) { - set = new HashSet(); - dirNameToModulesMap.put(dir, set); - } - set.add(moduleDirPair.getFirst()); + public void setDefaultProcessorProfile(ProcessorConfigProfile profile) { + myDefaultProcessorsProfile.initFrom(profile); } - int profileIndex = 0; - for (Map.Entry> entry : dirNameToModulesMap.entrySet()) { - final String dirName = entry.getKey(); - final ProcessorConfigProfile profile = new ProcessorConfigProfileImpl(myDefaultProcessorsProfile); - profile.setName("Profile" + (++profileIndex)); - profile.setEnabled(isEnabled); - profile.setGeneratedSourcesDirectoryName(dirName, false); - for (String moduleName : entry.getValue()) { - profile.addModuleName(moduleName); - } - myModuleProcessorProfiles.add(profile); + public void setModuleProcessorProfiles(Collection moduleProfiles) { + myModuleProcessorProfiles.clear(); + myModuleProcessorProfiles.addAll(moduleProfiles); + myProcessorsProfilesMap = null; } - } - - @Nonnull - public ProcessorConfigProfile getAnnotationProcessingConfiguration(Module module) { - Map map = myProcessorsProfilesMap; - if (map == null) { - map = new HashMap(); - final Map namesMap = new HashMap(); - for (Module m : ModuleManager.getInstance(module.getProject()).getModules()) { - namesMap.put(m.getName(), m); - } - if (!namesMap.isEmpty()) { + + public boolean isAnnotationProcessorsEnabled() { + if (myDefaultProcessorsProfile.isEnabled()) { + return true; + } for (ProcessorConfigProfile profile : myModuleProcessorProfiles) { - for (String name : profile.getModuleNames()) { - final Module mod = namesMap.get(name); - if (mod != null) { - map.put(mod, profile); + if (profile.isEnabled()) { + return true; } - } } - } - myProcessorsProfilesMap = map; + return false; } - final ProcessorConfigProfile profile = map.get(module); - return profile != null ? profile : myDefaultProcessorsProfile; - } - - @Nonnull - public ProcessorConfigProfile getDefaultProcessorProfile() { - return myDefaultProcessorsProfile; - } - - @Nonnull - public List getModuleProcessorProfiles() { - return myModuleProcessorProfiles; - } - - public void setDefaultProcessorProfile(ProcessorConfigProfile profile) { - myDefaultProcessorsProfile.initFrom(profile); - } - - public void setModuleProcessorProfiles(Collection moduleProfiles) { - myModuleProcessorProfiles.clear(); - myModuleProcessorProfiles.addAll(moduleProfiles); - myProcessorsProfilesMap = null; - } - - public boolean isAnnotationProcessorsEnabled() { - if (myDefaultProcessorsProfile.isEnabled()) { - return true; - } - for (ProcessorConfigProfile profile : myModuleProcessorProfiles) { - if (profile.isEnabled()) { - return true; - } + + public void removeModuleProcessorProfile(ProcessorConfigProfile profile) { + myModuleProcessorProfiles.remove(profile); + myProcessorsProfilesMap = null; // clear cache } - return false; - } - - public void removeModuleProcessorProfile(ProcessorConfigProfile profile) { - myModuleProcessorProfiles.remove(profile); - myProcessorsProfilesMap = null; // clear cache - } - - public void addModuleProcessorProfile(@Nonnull ProcessorConfigProfile profile) { - myModuleProcessorProfiles.add(profile); - myProcessorsProfilesMap = null; // clear cache - } - - @Nullable - public ProcessorConfigProfile findModuleProcessorProfile(@Nonnull String name) { - for (ProcessorConfigProfile profile : myModuleProcessorProfiles) { - if (name.equals(profile.getName())) { - return profile; - } + + public void addModuleProcessorProfile(@Nonnull ProcessorConfigProfile profile) { + myModuleProcessorProfiles.add(profile); + myProcessorsProfilesMap = null; // clear cache } - return null; - } + @Nullable + public ProcessorConfigProfile findModuleProcessorProfile(@Nonnull String name) { + for (ProcessorConfigProfile profile : myModuleProcessorProfiles) { + if (name.equals(profile.getName())) { + return profile; + } + } - public void setAddNotNullAssertions(boolean addNotNullAssertions) { - myAddNotNullAssertions = addNotNullAssertions; - } + return null; + } + + public void setAddNotNullAssertions(boolean addNotNullAssertions) { + myAddNotNullAssertions = addNotNullAssertions; + } - public boolean isAddNotNullAssertions() { - return myAddNotNullAssertions; - } + public boolean isAddNotNullAssertions() { + return myAddNotNullAssertions; + } - @Deprecated - public void setModulesBytecodeTargetMap(@Nonnull Map mapping) { - myModuleBytecodeTarget.clear(); - myModuleBytecodeTarget.putAll(mapping); - } + @Deprecated + public void setModulesBytecodeTargetMap(@Nonnull Map mapping) { + myModuleBytecodeTarget.clear(); + myModuleBytecodeTarget.putAll(mapping); + } - @Deprecated - public Map getModulesBytecodeTargetMap() { - return myModuleBytecodeTarget; - } + @Deprecated + public Map getModulesBytecodeTargetMap() { + return myModuleBytecodeTarget; + } - public void setProjectBytecodeTarget(@Nullable String level) { - myBytecodeTargetLevel = level; - } + public void setProjectBytecodeTarget(@Nullable String level) { + myBytecodeTargetLevel = level; + } - @Nullable - public String getProjectBytecodeTarget() { - return myBytecodeTargetLevel; - } + @Nullable + public String getProjectBytecodeTarget() { + return myBytecodeTargetLevel; + } } diff --git a/java-coverage-impl/src/main/java/com/intellij/java/coverage/JavaCoverageEngine.java b/java-coverage-impl/src/main/java/com/intellij/java/coverage/JavaCoverageEngine.java index df50218da6..b143a90c10 100644 --- a/java-coverage-impl/src/main/java/com/intellij/java/coverage/JavaCoverageEngine.java +++ b/java-coverage-impl/src/main/java/com/intellij/java/coverage/JavaCoverageEngine.java @@ -57,510 +57,572 @@ */ @ExtensionImpl public class JavaCoverageEngine extends CoverageEngine { - private static final Logger LOG = Logger.getInstance(JavaCoverageEngine.class); + private static final Logger LOG = Logger.getInstance(JavaCoverageEngine.class); - @Override - public boolean isApplicableTo(@Nullable final RunConfigurationBase conf) { - if (conf instanceof CommonJavaRunConfigurationParameters) { - return true; - } - for (JavaCoverageEngineExtension extension : JavaCoverageEngineExtension.EP_NAME.getExtensionList()) { - if (extension.isApplicableTo(conf)) { - return true; - } + @Override + public boolean isApplicableTo(@Nullable final RunConfigurationBase conf) { + if (conf instanceof CommonJavaRunConfigurationParameters) { + return true; + } + for (JavaCoverageEngineExtension extension : JavaCoverageEngineExtension.EP_NAME.getExtensionList()) { + if (extension.isApplicableTo(conf)) { + return true; + } + } + return false; } - return false; - } - - @Override - public boolean canHavePerTestCoverage(@Nullable RunConfigurationBase conf) { - return !(conf instanceof ApplicationConfiguration) && conf instanceof CommonJavaRunConfigurationParameters; - } - - @Nonnull - @Override - public CoverageEnabledConfiguration createCoverageEnabledConfiguration(@Nullable final RunConfigurationBase conf) { - return new JavaCoverageEnabledConfiguration(conf, this); - } - - @Nullable - @Override - public CoverageSuite createCoverageSuite(@Nonnull final CoverageRunner covRunner, - @Nonnull final String name, - @Nonnull final CoverageFileProvider coverageDataFileProvider, - String[] filters, - long lastCoverageTimeStamp, - String suiteToMerge, - boolean coverageByTestEnabled, - boolean tracingEnabled, - boolean trackTestFolders, Project project) { - - return createSuite(covRunner, name, coverageDataFileProvider, filters, lastCoverageTimeStamp, coverageByTestEnabled, - tracingEnabled, trackTestFolders, project); - } - - @Override - public CoverageSuite createCoverageSuite( - @Nonnull final CoverageRunner covRunner, - @Nonnull final String name, - @Nonnull final CoverageFileProvider coverageDataFileProvider, - @Nonnull final CoverageEnabledConfiguration config - ) { - if (config instanceof JavaCoverageEnabledConfiguration javaConfig) { - return createSuite(covRunner, name, coverageDataFileProvider, - javaConfig.getPatterns(), - new Date().getTime(), - javaConfig.isTrackPerTestCoverage() && !javaConfig.isSampling(), - !javaConfig.isSampling(), - javaConfig.isTrackTestFolders(), config.getConfiguration().getProject()); + + @Override + public boolean canHavePerTestCoverage(@Nullable RunConfigurationBase conf) { + return !(conf instanceof ApplicationConfiguration) && conf instanceof CommonJavaRunConfigurationParameters; } - return null; - } - - @Nullable - @Override - public CoverageSuite createEmptyCoverageSuite(@Nonnull CoverageRunner coverageRunner) { - return new JavaCoverageSuite(this); - } - - @Nonnull - @Override - public CoverageAnnotator getCoverageAnnotator(Project project) { - return JavaCoverageAnnotator.getInstance(project); - } - - /** - * Determines if coverage information should be displayed for given file - * - * @param psiFile - * @return - */ - public boolean coverageEditorHighlightingApplicableTo(@Nonnull final PsiFile psiFile) { - if (!(psiFile instanceof PsiClassOwner)) { - return false; + + @Nonnull + @Override + public CoverageEnabledConfiguration createCoverageEnabledConfiguration(@Nullable final RunConfigurationBase conf) { + return new JavaCoverageEnabledConfiguration(conf, this); } - // let's show coverage only for module files - final Module module = psiFile.getApplication().runReadAction(new Computable() { - @Nullable - public Module compute() { - return ModuleUtilCore.findModuleForPsiElement(psiFile); - } - }); - return module != null; - } - - public boolean acceptedByFilters(@Nonnull final PsiFile psiFile, @Nonnull final CoverageSuitesBundle suite) { - final VirtualFile virtualFile = psiFile.getVirtualFile(); - if (virtualFile == null) return false; - final Project project = psiFile.getProject(); - final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex(); - if (!suite.isTrackTestFolders() && fileIndex.isInTestSourceContent(virtualFile)) { - return false; + + @Nullable + @Override + public CoverageSuite createCoverageSuite( + @Nonnull final CoverageRunner covRunner, + @Nonnull final String name, + @Nonnull final CoverageFileProvider coverageDataFileProvider, + String[] filters, + long lastCoverageTimeStamp, + String suiteToMerge, + boolean coverageByTestEnabled, + boolean tracingEnabled, + boolean trackTestFolders, Project project + ) { + return createSuite( + covRunner, + name, + coverageDataFileProvider, + filters, + lastCoverageTimeStamp, + coverageByTestEnabled, + tracingEnabled, + trackTestFolders, + project + ); } - for (CoverageSuite coverageSuite : suite.getSuites()) { - final JavaCoverageSuite javaSuite = (JavaCoverageSuite) coverageSuite; - - final List packages = javaSuite.getCurrentSuitePackages(project); - if (isUnderFilteredPackages((PsiClassOwner) psiFile, packages)) { - return true; - } else { - final List classes = javaSuite.getCurrentSuiteClasses(project); - for (PsiClass aClass : classes) { - final PsiFile containingFile = aClass.getContainingFile(); - if (psiFile.equals(containingFile)) { - return true; - } + @Override + public CoverageSuite createCoverageSuite( + @Nonnull final CoverageRunner covRunner, + @Nonnull final String name, + @Nonnull final CoverageFileProvider coverageDataFileProvider, + @Nonnull final CoverageEnabledConfiguration config + ) { + if (config instanceof JavaCoverageEnabledConfiguration javaConfig) { + return createSuite( + covRunner, + name, + coverageDataFileProvider, + javaConfig.getPatterns(), + new Date().getTime(), + javaConfig.isTrackPerTestCoverage() && !javaConfig.isSampling(), + !javaConfig.isSampling(), + javaConfig.isTrackTestFolders(), + config.getConfiguration().getProject() + ); } - } + return null; } - return false; - } - - @Override - public boolean recompileProjectAndRerunAction(@Nonnull final Module module, @Nonnull final CoverageSuitesBundle suite, - @Nonnull final Runnable chooseSuiteAction) { - final VirtualFile outputpath = ModuleCompilerPathsManager.getInstance(module).getCompilerOutput(ProductionContentFolderTypeProvider.getInstance - ()); - final VirtualFile testOutputpath = ModuleCompilerPathsManager.getInstance(module).getCompilerOutput(TestContentFolderTypeProvider.getInstance()); - - if ((outputpath == null && isModuleOutputNeeded(module, ProductionContentFolderTypeProvider.getInstance())) - || (suite.isTrackTestFolders() && testOutputpath == null && isModuleOutputNeeded(module, TestContentFolderTypeProvider.getInstance()))) { - final Project project = module.getProject(); - if (suite.isModuleChecked(module)) return false; - suite.checkModule(module); - final Runnable runnable = () -> { - if (Messages.showOkCancelDialog( - "Project class files are out of date. Would you like to recompile?" + - " The refusal to do it will result in incomplete coverage information", - "Project is out of date", - UIUtil.getWarningIcon() - ) == Messages.OK) { - final CompilerManager compilerManager = CompilerManager.getInstance(project); - compilerManager.make( - compilerManager.createProjectCompileScope(), - (aborted, errors, warnings, compileContext) -> { - if (aborted || errors != 0) return; - Application.get().invokeLater(() -> { - if (project.isDisposed()) return; - CoverageDataManager.getInstance(project).chooseSuitesBundle(suite); - }); - } - ); - } else if (!project.isDisposed()) { - CoverageDataManager.getInstance(project).chooseSuitesBundle(null); - } - }; - project.getApplication().invokeLater(runnable); - return true; + + @Nullable + @Override + public CoverageSuite createEmptyCoverageSuite(@Nonnull CoverageRunner coverageRunner) { + return new JavaCoverageSuite(this); } - return false; - } - - private static boolean isModuleOutputNeeded(Module module, final ContentFolderTypeProvider rootType) { - return ModuleRootManager.getInstance(module).getContentFolderFiles(it -> it.equals(rootType)).length != 0; - } - - public static boolean isUnderFilteredPackages(final PsiClassOwner javaFile, final List packages) { - final String hisPackageName = Application.get().runReadAction((Computable)javaFile::getPackageName); - PsiPackage hisPackage = JavaPsiFacade.getInstance(javaFile.getProject()).findPackage(hisPackageName); - if (hisPackage == null) return false; - for (PsiPackage aPackage : packages) { - if (PsiTreeUtil.isAncestor(aPackage, hisPackage, false)) return true; + + @Nonnull + @Override + public CoverageAnnotator getCoverageAnnotator(Project project) { + return JavaCoverageAnnotator.getInstance(project); } - return false; - } - - @Nullable - public List collectSrcLinesForUntouchedFile(@Nonnull final File classFile, @Nonnull final CoverageSuitesBundle suite) { - final List uncoveredLines = new ArrayList<>(); - - final byte[] content; - try { - content = Files.readAllBytes(classFile.toPath()); - } catch (IOException e) { - return null; + + /** + * Determines if coverage information should be displayed for given file + * + * @param psiFile + * @return + */ + public boolean coverageEditorHighlightingApplicableTo(@Nonnull final PsiFile psiFile) { + if (!(psiFile instanceof PsiClassOwner)) { + return false; + } + // let's show coverage only for module files + final Module module = psiFile.getApplication().runReadAction(new Computable() { + @Nullable + public Module compute() { + return ModuleUtilCore.findModuleForPsiElement(psiFile); + } + }); + return module != null; } - try { - SourceLineCounterUtil.collectSrcLinesForUntouchedFiles(uncoveredLines, content, suite.isTracingEnabled()); - } catch (Exception e) { - LOG.error("Fail to process class from: " + classFile.getPath(), e); + public boolean acceptedByFilters(@Nonnull final PsiFile psiFile, @Nonnull final CoverageSuitesBundle suite) { + final VirtualFile virtualFile = psiFile.getVirtualFile(); + if (virtualFile == null) { + return false; + } + final Project project = psiFile.getProject(); + final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex(); + if (!suite.isTrackTestFolders() && fileIndex.isInTestSourceContent(virtualFile)) { + return false; + } + + for (CoverageSuite coverageSuite : suite.getSuites()) { + final JavaCoverageSuite javaSuite = (JavaCoverageSuite)coverageSuite; + + final List packages = javaSuite.getCurrentSuitePackages(project); + if (isUnderFilteredPackages((PsiClassOwner)psiFile, packages)) { + return true; + } + else { + final List classes = javaSuite.getCurrentSuiteClasses(project); + for (PsiClass aClass : classes) { + final PsiFile containingFile = aClass.getContainingFile(); + if (psiFile.equals(containingFile)) { + return true; + } + } + } + } + return false; } - return uncoveredLines; - } - - public boolean includeUntouchedFileInCoverage(@Nonnull final String qualifiedName, - @Nonnull final File outputFile, - @Nonnull final PsiFile sourceFile, @Nonnull CoverageSuitesBundle suite) { - for (CoverageSuite coverageSuite : suite.getSuites()) { - final JavaCoverageSuite javaSuite = (JavaCoverageSuite) coverageSuite; - if (javaSuite.isClassFiltered(qualifiedName) || javaSuite.isPackageFiltered(getPackageName(sourceFile))) - return true; + + @Override + public boolean recompileProjectAndRerunAction( + @Nonnull final Module module, @Nonnull final CoverageSuitesBundle suite, + @Nonnull final Runnable chooseSuiteAction + ) { + final VirtualFile outputpath = + ModuleCompilerPathsManager.getInstance(module).getCompilerOutput(ProductionContentFolderTypeProvider.getInstance + ()); + final VirtualFile testOutputpath = + ModuleCompilerPathsManager.getInstance(module).getCompilerOutput(TestContentFolderTypeProvider.getInstance()); + + if ((outputpath == null && isModuleOutputNeeded(module, ProductionContentFolderTypeProvider.getInstance())) + || (suite.isTrackTestFolders() && testOutputpath == null + && isModuleOutputNeeded(module, TestContentFolderTypeProvider.getInstance()))) { + final Project project = module.getProject(); + if (suite.isModuleChecked(module)) { + return false; + } + suite.checkModule(module); + final Runnable runnable = () -> { + if (Messages.showOkCancelDialog( + "Project class files are out of date. Would you like to recompile?" + + " The refusal to do it will result in incomplete coverage information", + "Project is out of date", + UIUtil.getWarningIcon() + ) == Messages.OK) { + final CompilerManager compilerManager = CompilerManager.getInstance(project); + compilerManager.make( + compilerManager.createProjectCompileScope(), + (aborted, errors, warnings, compileContext) -> { + if (aborted || errors != 0) { + return; + } + Application.get().invokeLater(() -> { + if (project.isDisposed()) { + return; + } + CoverageDataManager.getInstance(project).chooseSuitesBundle(suite); + }); + } + ); + } + else if (!project.isDisposed()) { + CoverageDataManager.getInstance(project).chooseSuitesBundle(null); + } + }; + project.getApplication().invokeLater(runnable); + return true; + } + return false; } - return false; - } - - - public String getQualifiedName(@Nonnull final File outputFile, @Nonnull final PsiFile sourceFile) { - final String packageFQName = getPackageName(sourceFile); - return StringUtil.getQualifiedName(packageFQName, FileUtil.getNameWithoutExtension(outputFile)); - } - - @Nonnull - @Override - public Set getQualifiedNames(@Nonnull final PsiFile sourceFile) { - Application application = sourceFile.getApplication(); - final PsiClass[] classes = application.runReadAction((Computable)((PsiClassOwner)sourceFile)::getClasses); - final Set qNames = new HashSet<>(); - for (final JavaCoverageEngineExtension nameExtension : Extensions.getExtensions(JavaCoverageEngineExtension.EP_NAME)) { - if (application.runReadAction((Computable)() -> nameExtension.suggestQualifiedName(sourceFile, classes, qNames))) { - return qNames; - } + + private static boolean isModuleOutputNeeded(Module module, final ContentFolderTypeProvider rootType) { + return ModuleRootManager.getInstance(module).getContentFolderFiles(it -> it.equals(rootType)).length != 0; } - for (final PsiClass aClass : classes) { - final String qName = application.runReadAction(new Computable() { - @Nullable - public String compute() { - return aClass.getQualifiedName(); + + public static boolean isUnderFilteredPackages(final PsiClassOwner javaFile, final List packages) { + final String hisPackageName = Application.get().runReadAction((Computable)javaFile::getPackageName); + PsiPackage hisPackage = JavaPsiFacade.getInstance(javaFile.getProject()).findPackage(hisPackageName); + if (hisPackage == null) { + return false; + } + for (PsiPackage aPackage : packages) { + if (PsiTreeUtil.isAncestor(aPackage, hisPackage, false)) { + return true; + } } - }); - if (qName == null) continue; - qNames.add(qName); + return false; } - return qNames; - } - - @Nonnull - public Set getCorrespondingOutputFiles( - @Nonnull final PsiFile srcFile, - @Nullable final Module module, - @Nonnull final CoverageSuitesBundle suite - ) { - if (module == null) { - return Collections.emptySet(); + + @Nullable + public List collectSrcLinesForUntouchedFile(@Nonnull final File classFile, @Nonnull final CoverageSuitesBundle suite) { + final List uncoveredLines = new ArrayList<>(); + + final byte[] content; + try { + content = Files.readAllBytes(classFile.toPath()); + } + catch (IOException e) { + return null; + } + + try { + SourceLineCounterUtil.collectSrcLinesForUntouchedFiles(uncoveredLines, content, suite.isTracingEnabled()); + } + catch (Exception e) { + LOG.error("Fail to process class from: " + classFile.getPath(), e); + } + return uncoveredLines; } - final Set classFiles = new HashSet<>(); - final ModuleCompilerPathsManager pathsManager = ModuleCompilerPathsManager.getInstance(module); - final VirtualFile outputpath = pathsManager.getCompilerOutput(ProductionContentFolderTypeProvider.getInstance()); - final VirtualFile testOutputpath = pathsManager.getCompilerOutput(TestContentFolderTypeProvider.getInstance()); - for (JavaCoverageEngineExtension extension : Extensions.getExtensions(JavaCoverageEngineExtension.EP_NAME)) { - if (extension.collectOutputFiles(srcFile, outputpath, testOutputpath, suite, classFiles)) return classFiles; + public boolean includeUntouchedFileInCoverage( + @Nonnull final String qualifiedName, + @Nonnull final File outputFile, + @Nonnull final PsiFile sourceFile, @Nonnull CoverageSuitesBundle suite + ) { + for (CoverageSuite coverageSuite : suite.getSuites()) { + final JavaCoverageSuite javaSuite = (JavaCoverageSuite)coverageSuite; + if (javaSuite.isClassFiltered(qualifiedName) || javaSuite.isPackageFiltered(getPackageName(sourceFile))) { + return true; + } + } + return false; } - final String packageFQName = getPackageName(srcFile); - final String packageVmName = packageFQName.replace('.', '/'); - final List children = new ArrayList<>(); - final File vDir = - outputpath == null - ? null : packageVmName.length() > 0 - ? new File(outputpath.getPath() + File.separator + packageVmName) : VirtualFileUtil.virtualToIoFile(outputpath); - if (vDir != null && vDir.exists()) { - Collections.addAll(children, vDir.listFiles()); + public String getQualifiedName(@Nonnull final File outputFile, @Nonnull final PsiFile sourceFile) { + final String packageFQName = getPackageName(sourceFile); + return StringUtil.getQualifiedName(packageFQName, FileUtil.getNameWithoutExtension(outputFile)); } - if (suite.isTrackTestFolders()) { - final File testDir = - testOutputpath == null - ? null : packageVmName.length() > 0 - ? new File(testOutputpath.getPath() + File.separator + packageVmName) : VirtualFileUtil.virtualToIoFile(testOutputpath); - if (testDir != null && testDir.exists()) { - Collections.addAll(children, testDir.listFiles()); - } + @Nonnull + @Override + public Set getQualifiedNames(@Nonnull final PsiFile sourceFile) { + Application application = sourceFile.getApplication(); + final PsiClass[] classes = application.runReadAction((Computable)((PsiClassOwner)sourceFile)::getClasses); + final Set qNames = new HashSet<>(); + for (final JavaCoverageEngineExtension nameExtension : Extensions.getExtensions(JavaCoverageEngineExtension.EP_NAME)) { + if (application.runReadAction((Computable)() -> nameExtension.suggestQualifiedName(sourceFile, classes, qNames))) { + return qNames; + } + } + for (final PsiClass aClass : classes) { + final String qName = application.runReadAction(new Computable() { + @Nullable + public String compute() { + return aClass.getQualifiedName(); + } + }); + if (qName == null) { + continue; + } + qNames.add(qName); + } + return qNames; } - Application application = srcFile.getApplication(); - final PsiClass[] classes = application.runReadAction((Computable)((PsiClassOwner)srcFile)::getClasses); - for (final PsiClass psiClass : classes) { - final String className = application.runReadAction((Computable)psiClass::getName); - for (File child : children) { - if (FileUtil.extensionEquals(child.getName(), JavaClassFileType.INSTANCE.getDefaultExtension())) { - final String childName = FileUtil.getNameWithoutExtension(child); - if (childName.equals(className) || //class or inner - childName.startsWith(className) && childName.charAt(className.length()) == '$') { - classFiles.add(child); - } + @Nonnull + public Set getCorrespondingOutputFiles( + @Nonnull final PsiFile srcFile, + @Nullable final Module module, + @Nonnull final CoverageSuitesBundle suite + ) { + if (module == null) { + return Collections.emptySet(); } - } - } - return classFiles; - } - - @RequiredReadAction - public String generateBriefReport( - @Nonnull Editor editor, - @Nonnull PsiFile psiFile, - int lineNumber, - int startOffset, - int endOffset, - @Nullable LineData lineData - ) { - final StringBuilder buf = new StringBuilder(); - buf.append("Hits: "); - if (lineData == null) { - buf.append(0); - return buf.toString(); - } - buf.append(lineData.getHits()).append("\n"); - - final List expressions = new ArrayList<>(); - - final Project project = editor.getProject(); - for (int offset = startOffset; offset < endOffset; offset++) { - PsiElement parent = PsiTreeUtil.getParentOfType(psiFile.findElementAt(offset), PsiStatement.class); - PsiElement condition = null; - if (parent instanceof PsiIfStatement ifStatement) { - condition = ifStatement.getCondition(); - } else if (parent instanceof PsiSwitchStatement switchStatement) { - condition = switchStatement.getExpression(); - } else if (parent instanceof PsiDoWhileStatement doWhileStatement) { - condition = doWhileStatement.getCondition(); - } else if (parent instanceof PsiForStatement forStatement) { - condition = forStatement.getCondition(); - } else if (parent instanceof PsiWhileStatement whileStatement) { - condition = whileStatement.getCondition(); - } else if (parent instanceof PsiForeachStatement foreachStatement) { - condition = foreachStatement.getIteratedValue(); - } else if (parent instanceof PsiAssertStatement assertStatement) { - condition = assertStatement.getAssertCondition(); - } - if (condition != null && PsiTreeUtil.isAncestor(condition, psiFile.findElementAt(offset), false)) { - try { - final ControlFlow controlFlow = ControlFlowFactory.getInstance(project) - .getControlFlow(parent, AllVariablesControlFlowPolicy.getInstance()); - for (Instruction instruction : controlFlow.getInstructions()) { - if (instruction instanceof ConditionalBranchingInstruction branchingInstruction) { - final PsiExpression expression = branchingInstruction.expression; - if (!expressions.contains(expression)) { - expressions.add(expression); - } + final Set classFiles = new HashSet<>(); + final ModuleCompilerPathsManager pathsManager = ModuleCompilerPathsManager.getInstance(module); + final VirtualFile outputpath = pathsManager.getCompilerOutput(ProductionContentFolderTypeProvider.getInstance()); + final VirtualFile testOutputpath = pathsManager.getCompilerOutput(TestContentFolderTypeProvider.getInstance()); + + for (JavaCoverageEngineExtension extension : Extensions.getExtensions(JavaCoverageEngineExtension.EP_NAME)) { + if (extension.collectOutputFiles(srcFile, outputpath, testOutputpath, suite, classFiles)) { + return classFiles; + } + } + + final String packageFQName = getPackageName(srcFile); + final String packageVmName = packageFQName.replace('.', '/'); + + final List children = new ArrayList<>(); + final File vDir = + outputpath == null + ? null : packageVmName.length() > 0 + ? new File(outputpath.getPath() + File.separator + packageVmName) : VirtualFileUtil.virtualToIoFile(outputpath); + if (vDir != null && vDir.exists()) { + Collections.addAll(children, vDir.listFiles()); + } + + if (suite.isTrackTestFolders()) { + final File testDir = + testOutputpath == null + ? null : packageVmName.length() > 0 + ? new File(testOutputpath.getPath() + File.separator + packageVmName) : VirtualFileUtil.virtualToIoFile(testOutputpath); + if (testDir != null && testDir.exists()) { + Collections.addAll(children, testDir.listFiles()); } - } - } catch (AnalysisCanceledException e) { - return buf.toString(); } - } + + Application application = srcFile.getApplication(); + final PsiClass[] classes = application.runReadAction((Computable)((PsiClassOwner)srcFile)::getClasses); + for (final PsiClass psiClass : classes) { + final String className = application.runReadAction((Computable)psiClass::getName); + for (File child : children) { + if (FileUtil.extensionEquals(child.getName(), JavaClassFileType.INSTANCE.getDefaultExtension())) { + final String childName = FileUtil.getNameWithoutExtension(child); + if (childName.equals(className) || //class or inner + childName.startsWith(className) && childName.charAt(className.length()) == '$') { + classFiles.add(child); + } + } + } + } + return classFiles; } - final String indent = " "; - try { - int idx = 0; - int hits = 0; - if (lineData.getJumps() != null) { - for (Object o : lineData.getJumps()) { - final JumpData jumpData = (JumpData) o; - if (jumpData.getTrueHits() + jumpData.getFalseHits() > 0) { - final PsiExpression expression = expressions.get(idx++); - final PsiElement parentExpression = expression.getParent(); - boolean reverse = parentExpression instanceof PsiPolyadicExpression polyExpr && polyExpr.getOperationTokenType() == JavaTokenType.OROR - || parentExpression instanceof PsiDoWhileStatement || parentExpression instanceof PsiAssertStatement; - buf.append(indent).append(expression.getText()).append("\n"); - buf.append(indent).append(indent).append("true hits: ").append(reverse ? jumpData.getFalseHits() : jumpData.getTrueHits()).append("\n"); - buf.append(indent).append(indent).append("false hits: ").append(reverse ? jumpData.getTrueHits() : jumpData.getFalseHits()).append("\n"); - hits += jumpData.getTrueHits() + jumpData.getFalseHits(); - } + @RequiredReadAction + public String generateBriefReport( + @Nonnull Editor editor, + @Nonnull PsiFile psiFile, + int lineNumber, + int startOffset, + int endOffset, + @Nullable LineData lineData + ) { + final StringBuilder buf = new StringBuilder(); + buf.append("Hits: "); + if (lineData == null) { + buf.append(0); + return buf.toString(); } - } - - if (lineData.getSwitches() != null) { - for (Object o : lineData.getSwitches()) { - final SwitchData switchData = (SwitchData) o; - final PsiExpression conditionExpression = expressions.get(idx++); - buf.append(indent).append(conditionExpression.getText()).append("\n"); - int i = 0; - for (int key : switchData.getKeys()) { - final int switchHits = switchData.getHits()[i++]; - buf.append(indent).append(indent).append("case ").append(key).append(": ").append(switchHits).append("\n"); - hits += switchHits; - } - int defaultHits = switchData.getDefaultHits(); - final boolean hasDefaultLabel = hasDefaultLabel(conditionExpression); - if (hasDefaultLabel || defaultHits > 0) { - if (!hasDefaultLabel) { - defaultHits -= hits; + buf.append(lineData.getHits()).append("\n"); + + final List expressions = new ArrayList<>(); + + final Project project = editor.getProject(); + for (int offset = startOffset; offset < endOffset; offset++) { + PsiElement parent = PsiTreeUtil.getParentOfType(psiFile.findElementAt(offset), PsiStatement.class); + PsiElement condition = null; + if (parent instanceof PsiIfStatement ifStatement) { + condition = ifStatement.getCondition(); + } + else if (parent instanceof PsiSwitchStatement switchStatement) { + condition = switchStatement.getExpression(); + } + else if (parent instanceof PsiDoWhileStatement doWhileStatement) { + condition = doWhileStatement.getCondition(); + } + else if (parent instanceof PsiForStatement forStatement) { + condition = forStatement.getCondition(); + } + else if (parent instanceof PsiWhileStatement whileStatement) { + condition = whileStatement.getCondition(); + } + else if (parent instanceof PsiForeachStatement foreachStatement) { + condition = foreachStatement.getIteratedValue(); + } + else if (parent instanceof PsiAssertStatement assertStatement) { + condition = assertStatement.getAssertCondition(); + } + if (condition != null && PsiTreeUtil.isAncestor(condition, psiFile.findElementAt(offset), false)) { + try { + final ControlFlow controlFlow = ControlFlowFactory.getInstance(project) + .getControlFlow(parent, AllVariablesControlFlowPolicy.getInstance()); + for (Instruction instruction : controlFlow.getInstructions()) { + if (instruction instanceof ConditionalBranchingInstruction branchingInstruction) { + final PsiExpression expression = branchingInstruction.expression; + if (!expressions.contains(expression)) { + expressions.add(expression); + } + } + } + } + catch (AnalysisCanceledException e) { + return buf.toString(); + } + } + } + + final String indent = " "; + try { + int idx = 0; + int hits = 0; + if (lineData.getJumps() != null) { + for (Object o : lineData.getJumps()) { + final JumpData jumpData = (JumpData)o; + if (jumpData.getTrueHits() + jumpData.getFalseHits() > 0) { + final PsiExpression expression = expressions.get(idx++); + final PsiElement parentExpression = expression.getParent(); + boolean reverse = parentExpression instanceof PsiPolyadicExpression polyExpr + && polyExpr.getOperationTokenType() == JavaTokenType.OROR + || parentExpression instanceof PsiDoWhileStatement + || parentExpression instanceof PsiAssertStatement; + buf.append(indent).append(expression.getText()).append("\n"); + buf.append(indent) + .append(indent) + .append("true hits: ") + .append(reverse ? jumpData.getFalseHits() : jumpData.getTrueHits()) + .append("\n"); + buf.append(indent) + .append(indent) + .append("false hits: ") + .append(reverse ? jumpData.getTrueHits() : jumpData.getFalseHits()) + .append("\n"); + hits += jumpData.getTrueHits() + jumpData.getFalseHits(); + } + } } - if (hasDefaultLabel || defaultHits > 0) { - buf.append(indent).append(indent).append("default: ").append(defaultHits).append("\n"); - hits += defaultHits; + if (lineData.getSwitches() != null) { + for (Object o : lineData.getSwitches()) { + final SwitchData switchData = (SwitchData)o; + final PsiExpression conditionExpression = expressions.get(idx++); + buf.append(indent).append(conditionExpression.getText()).append("\n"); + int i = 0; + for (int key : switchData.getKeys()) { + final int switchHits = switchData.getHits()[i++]; + buf.append(indent).append(indent).append("case ").append(key).append(": ").append(switchHits).append("\n"); + hits += switchHits; + } + int defaultHits = switchData.getDefaultHits(); + final boolean hasDefaultLabel = hasDefaultLabel(conditionExpression); + if (hasDefaultLabel || defaultHits > 0) { + if (!hasDefaultLabel) { + defaultHits -= hits; + } + + if (hasDefaultLabel || defaultHits > 0) { + buf.append(indent).append(indent).append("default: ").append(defaultHits).append("\n"); + hits += defaultHits; + } + } + } + } + if (lineData.getHits() > hits && hits > 0) { + buf.append("Unknown outcome: ").append(lineData.getHits() - hits); } - } } - } - if (lineData.getHits() > hits && hits > 0) { - buf.append("Unknown outcome: ").append(lineData.getHits() - hits); - } - } catch (Exception e) { - LOG.info(e); - return "Hits: " + lineData.getHits(); + catch (Exception e) { + LOG.info(e); + return "Hits: " + lineData.getHits(); + } + return buf.toString(); } - return buf.toString(); - } - - @Nullable - public String getTestMethodName( - @Nonnull final PsiElement element, - @Nonnull final AbstractTestProxy testProxy - ) { - return testProxy.toString(); - } - - @Nonnull - public List findTestsByNames(@Nonnull String[] testNames, @Nonnull Project project) { - final List elements = new ArrayList<>(); - final JavaPsiFacade facade = JavaPsiFacade.getInstance(project); - final GlobalSearchScope projectScope = GlobalSearchScope.projectScope(project); - for (String testName : testNames) { - PsiClass psiClass = - facade.findClass(StringUtil.getPackageName(testName, '_').replaceAll("\\_", "\\."), projectScope); - int lastIdx = testName.lastIndexOf("_"); - if (psiClass != null) { - collectTestsByName(elements, testName, psiClass, lastIdx); - } else { - String className = testName; - while (lastIdx > 0) { - className = className.substring(0, lastIdx - 1); - psiClass = facade.findClass(StringUtil.getPackageName(className, '_').replaceAll("\\_", "\\."), projectScope); - lastIdx = className.lastIndexOf("_"); - if (psiClass != null) { - collectTestsByName(elements, testName, psiClass, lastIdx); - break; - } + + @Nullable + public String getTestMethodName( + @Nonnull final PsiElement element, + @Nonnull final AbstractTestProxy testProxy + ) { + return testProxy.toString(); + } + + @Nonnull + public List findTestsByNames(@Nonnull String[] testNames, @Nonnull Project project) { + final List elements = new ArrayList<>(); + final JavaPsiFacade facade = JavaPsiFacade.getInstance(project); + final GlobalSearchScope projectScope = GlobalSearchScope.projectScope(project); + for (String testName : testNames) { + PsiClass psiClass = + facade.findClass(StringUtil.getPackageName(testName, '_').replaceAll("\\_", "\\."), projectScope); + int lastIdx = testName.lastIndexOf("_"); + if (psiClass != null) { + collectTestsByName(elements, testName, psiClass, lastIdx); + } + else { + String className = testName; + while (lastIdx > 0) { + className = className.substring(0, lastIdx - 1); + psiClass = facade.findClass(StringUtil.getPackageName(className, '_').replaceAll("\\_", "\\."), projectScope); + lastIdx = className.lastIndexOf("_"); + if (psiClass != null) { + collectTestsByName(elements, testName, psiClass, lastIdx); + break; + } + } + } } - } + return elements; } - return elements; - } - private static void collectTestsByName(List elements, String testName, PsiClass psiClass, int lastIdx) { - final PsiMethod[] testsByName = psiClass.findMethodsByName(testName.substring(lastIdx + 1), true); - if (testsByName.length == 1) { - elements.add(testsByName[0]); + private static void collectTestsByName(List elements, String testName, PsiClass psiClass, int lastIdx) { + final PsiMethod[] testsByName = psiClass.findMethodsByName(testName.substring(lastIdx + 1), true); + if (testsByName.length == 1) { + elements.add(testsByName[0]); + } } - } - - private static boolean hasDefaultLabel(final PsiElement conditionExpression) { - boolean hasDefault = false; - final PsiSwitchStatement switchStatement = PsiTreeUtil.getParentOfType(conditionExpression, PsiSwitchStatement.class); - final PsiCodeBlock body = ((PsiSwitchStatementImpl) conditionExpression.getParent()).getBody(); - if (body != null) { - final PsiElement bodyElement = body.getFirstBodyElement(); - if (bodyElement != null) { - PsiSwitchLabelStatement label = PsiTreeUtil.getNextSiblingOfType(bodyElement, PsiSwitchLabelStatement.class); - while (label != null) { - if (label.getEnclosingSwitchStatement() == switchStatement) { - hasDefault |= label.isDefaultCase(); - } - label = PsiTreeUtil.getNextSiblingOfType(label, PsiSwitchLabelStatement.class); + + private static boolean hasDefaultLabel(final PsiElement conditionExpression) { + boolean hasDefault = false; + final PsiSwitchStatement switchStatement = PsiTreeUtil.getParentOfType(conditionExpression, PsiSwitchStatement.class); + final PsiCodeBlock body = ((PsiSwitchStatementImpl)conditionExpression.getParent()).getBody(); + if (body != null) { + final PsiElement bodyElement = body.getFirstBodyElement(); + if (bodyElement != null) { + PsiSwitchLabelStatement label = PsiTreeUtil.getNextSiblingOfType(bodyElement, PsiSwitchLabelStatement.class); + while (label != null) { + if (label.getEnclosingSwitchStatement() == switchStatement) { + hasDefault |= label.isDefaultCase(); + } + label = PsiTreeUtil.getNextSiblingOfType(label, PsiSwitchLabelStatement.class); + } + } } - } + return hasDefault; + } + + protected JavaCoverageSuite createSuite( + CoverageRunner acceptedCovRunner, + String name, + CoverageFileProvider coverageDataFileProvider, + String[] filters, + long lastCoverageTimeStamp, + boolean coverageByTestEnabled, + boolean tracingEnabled, + boolean trackTestFolders, + Project project + ) { + return new JavaCoverageSuite( + name, + coverageDataFileProvider, + filters, + lastCoverageTimeStamp, + coverageByTestEnabled, + tracingEnabled, + trackTestFolders, + acceptedCovRunner, + this, + project + ); + } + + @Nonnull + protected static String getPackageName(final PsiFile sourceFile) { + return sourceFile.getApplication().runReadAction((Computable)((PsiClassOwner)sourceFile)::getPackageName); + } + + @Override + public String getPresentableText() { + return "Java Coverage"; + } + + @Override + public CoverageViewExtension createCoverageViewExtension( + Project project, + CoverageSuitesBundle suiteBundle, + CoverageViewManager.StateBean stateBean + ) { + return new JavaCoverageViewExtension((JavaCoverageAnnotator)getCoverageAnnotator(project), project, suiteBundle, stateBean); } - return hasDefault; - } - - protected JavaCoverageSuite createSuite( - CoverageRunner acceptedCovRunner, - String name, - CoverageFileProvider coverageDataFileProvider, - String[] filters, - long lastCoverageTimeStamp, - boolean coverageByTestEnabled, - boolean tracingEnabled, - boolean trackTestFolders, - Project project - ) { - return new JavaCoverageSuite( - name, - coverageDataFileProvider, - filters, - lastCoverageTimeStamp, - coverageByTestEnabled, - tracingEnabled, - trackTestFolders, - acceptedCovRunner, - this, - project - ); - } - - @Nonnull - protected static String getPackageName(final PsiFile sourceFile) { - return sourceFile.getApplication().runReadAction((Computable)((PsiClassOwner)sourceFile)::getPackageName); - } - - @Override - public String getPresentableText() { - return "Java Coverage"; - } - - @Override - public CoverageViewExtension createCoverageViewExtension( - Project project, - CoverageSuitesBundle suiteBundle, - CoverageViewManager.StateBean stateBean - ) { - return new JavaCoverageViewExtension((JavaCoverageAnnotator) getCoverageAnnotator(project), project, suiteBundle, stateBean); - } } diff --git a/java-coverage-impl/src/main/java/com/intellij/java/coverage/JavaCoverageEngineExtension.java b/java-coverage-impl/src/main/java/com/intellij/java/coverage/JavaCoverageEngineExtension.java index 3c01fe76f1..3344a8b78b 100644 --- a/java-coverage-impl/src/main/java/com/intellij/java/coverage/JavaCoverageEngineExtension.java +++ b/java-coverage-impl/src/main/java/com/intellij/java/coverage/JavaCoverageEngineExtension.java @@ -20,20 +20,22 @@ */ @ExtensionAPI(ComponentScope.APPLICATION) public abstract class JavaCoverageEngineExtension { - public static final ExtensionPointName EP_NAME = - ExtensionPointName.create(JavaCoverageEngineExtension.class); + public static final ExtensionPointName EP_NAME = + ExtensionPointName.create(JavaCoverageEngineExtension.class); - public abstract boolean isApplicableTo(@Nullable RunConfigurationBase conf); + public abstract boolean isApplicableTo(@Nullable RunConfigurationBase conf); - public boolean suggestQualifiedName(@Nonnull PsiFile sourceFile, PsiClass[] classes, Set names) { - return false; - } + public boolean suggestQualifiedName(@Nonnull PsiFile sourceFile, PsiClass[] classes, Set names) { + return false; + } - public boolean collectOutputFiles(@Nonnull final PsiFile srcFile, - @Nullable final VirtualFile output, - @Nullable final VirtualFile testoutput, - @Nonnull final CoverageSuitesBundle suite, - @Nonnull final Set classFiles) { - return false; - } + public boolean collectOutputFiles( + @Nonnull final PsiFile srcFile, + @Nullable final VirtualFile output, + @Nullable final VirtualFile testoutput, + @Nonnull final CoverageSuitesBundle suite, + @Nonnull final Set classFiles + ) { + return false; + } } diff --git a/java-debugger-api/src/main/java/com/intellij/java/debugger/PositionManagerFactory.java b/java-debugger-api/src/main/java/com/intellij/java/debugger/PositionManagerFactory.java index 604c46957b..8c5ac07e53 100644 --- a/java-debugger-api/src/main/java/com/intellij/java/debugger/PositionManagerFactory.java +++ b/java-debugger-api/src/main/java/com/intellij/java/debugger/PositionManagerFactory.java @@ -26,8 +26,8 @@ */ @ExtensionAPI(ComponentScope.PROJECT) public abstract class PositionManagerFactory { - public static final ExtensionPointName EP_NAME = ExtensionPointName.create(PositionManagerFactory.class); + public static final ExtensionPointName EP_NAME = ExtensionPointName.create(PositionManagerFactory.class); - @Nullable - public abstract PositionManager createPositionManager(DebugProcess process); + @Nullable + public abstract PositionManager createPositionManager(DebugProcess process); } diff --git a/java-debugger-api/src/main/java/com/intellij/java/debugger/engine/DebuggerUtils.java b/java-debugger-api/src/main/java/com/intellij/java/debugger/engine/DebuggerUtils.java index cdbe017dc7..7197fec4ed 100644 --- a/java-debugger-api/src/main/java/com/intellij/java/debugger/engine/DebuggerUtils.java +++ b/java-debugger-api/src/main/java/com/intellij/java/debugger/engine/DebuggerUtils.java @@ -53,490 +53,526 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.*; @ServiceAPI(ComponentScope.APPLICATION) public abstract class DebuggerUtils { - private static final Logger LOG = Logger.getInstance(DebuggerUtils.class); - private static final Key TO_STRING_METHOD_KEY = new Key("CachedToStringMethod"); - public static final Set ourPrimitiveTypeNames = new HashSet(Arrays.asList("byte", "short", "int", "long", "float", "double", "boolean", "char")); - - public static void cleanupAfterProcessFinish(DebugProcess debugProcess) { - debugProcess.putUserData(TO_STRING_METHOD_KEY, null); - } - - @NonNls - public static String getValueAsString(final EvaluationContext evaluationContext, Value value) throws EvaluateException { - try { - if (value == null) { - return "null"; - } - if (value instanceof StringReference) { - return ((StringReference) value).value(); - } - if (isInteger(value)) { - long v = ((PrimitiveValue) value).longValue(); - return String.valueOf(v); - } - if (isNumeric(value)) { - double v = ((PrimitiveValue) value).doubleValue(); - return String.valueOf(v); - } - if (value instanceof BooleanValue) { - boolean v = ((PrimitiveValue) value).booleanValue(); - return String.valueOf(v); - } - if (value instanceof CharValue) { - char v = ((PrimitiveValue) value).charValue(); - return String.valueOf(v); - } - if (value instanceof ObjectReference) { - if (value instanceof ArrayReference) { - final StringBuilder builder = new StringBuilder(); - builder.append("["); - for (Iterator iterator = ((ArrayReference) value).getValues().iterator(); iterator.hasNext(); ) { - final Value element = iterator.next(); - builder.append(getValueAsString(evaluationContext, element)); - if (iterator.hasNext()) { - builder.append(","); - } - } - builder.append("]"); - return builder.toString(); - } - - final ObjectReference objRef = (ObjectReference) value; - final DebugProcess debugProcess = evaluationContext.getDebugProcess(); - Method toStringMethod = debugProcess.getUserData(TO_STRING_METHOD_KEY); - if (toStringMethod == null) { - try { - ReferenceType refType = objRef.virtualMachine().classesByName(JavaClassNames.JAVA_LANG_OBJECT).get(0); - toStringMethod = findMethod(refType, "toString", "()Ljava/lang/String;"); - debugProcess.putUserData(TO_STRING_METHOD_KEY, toStringMethod); - } catch (Exception ignored) { - throw EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message("evaluation.error.cannot.evaluate.tostring", objRef.referenceType().name())); - } - } - if (toStringMethod == null) { - throw EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message("evaluation.error.cannot.evaluate.tostring", objRef.referenceType().name())); - } - // while result must be of com.sun.jdi.StringReference type, it turns out that sometimes (jvm bugs?) - // it is a plain com.sun.tools.jdi.ObjectReferenceImpl - final Value result = debugProcess.invokeInstanceMethod(evaluationContext, objRef, toStringMethod, Collections.emptyList(), 0); - if (result == null) { - return "null"; - } - return result instanceof StringReference ? ((StringReference) result).value() : result.toString(); - } - throw EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message("evaluation.error.unsupported.expression.type")); - } catch (ObjectCollectedException ignored) { - throw EvaluateExceptionUtil.OBJECT_WAS_COLLECTED; - } - } - - public static final int MAX_DISPLAY_LABEL_LENGTH = 1024 * 5; - - public static String convertToPresentationString(String str) { - if (str.length() > MAX_DISPLAY_LABEL_LENGTH) { - str = translateStringValue(str.substring(0, MAX_DISPLAY_LABEL_LENGTH)); - StringBuilder buf = new StringBuilder(); - buf.append(str); - if (!str.endsWith("...")) { - buf.append("..."); - } - return buf.toString(); + private static final Logger LOG = Logger.getInstance(DebuggerUtils.class); + private static final Key TO_STRING_METHOD_KEY = new Key("CachedToStringMethod"); + public static final Set ourPrimitiveTypeNames = + new HashSet(Arrays.asList("byte", "short", "int", "long", "float", "double", "boolean", "char")); + + public static void cleanupAfterProcessFinish(DebugProcess debugProcess) { + debugProcess.putUserData(TO_STRING_METHOD_KEY, null); } - return translateStringValue(str); - } - - @Nullable - public static Method findMethod(@Nonnull ReferenceType refType, @NonNls String methodName, @NonNls String methodSignature) { - if (refType instanceof ArrayType) { - // for array types methodByName() in JDI always returns empty list - final Method method = findMethod(refType.virtualMachine().classesByName(JavaClassNames.JAVA_LANG_OBJECT).get(0), methodName, methodSignature); - if (method != null) { - return method; - } + + @NonNls + public static String getValueAsString(final EvaluationContext evaluationContext, Value value) throws EvaluateException { + try { + if (value == null) { + return "null"; + } + if (value instanceof StringReference) { + return ((StringReference)value).value(); + } + if (isInteger(value)) { + long v = ((PrimitiveValue)value).longValue(); + return String.valueOf(v); + } + if (isNumeric(value)) { + double v = ((PrimitiveValue)value).doubleValue(); + return String.valueOf(v); + } + if (value instanceof BooleanValue) { + boolean v = ((PrimitiveValue)value).booleanValue(); + return String.valueOf(v); + } + if (value instanceof CharValue) { + char v = ((PrimitiveValue)value).charValue(); + return String.valueOf(v); + } + if (value instanceof ObjectReference) { + if (value instanceof ArrayReference) { + final StringBuilder builder = new StringBuilder(); + builder.append("["); + for (Iterator iterator = ((ArrayReference)value).getValues().iterator(); iterator.hasNext(); ) { + final Value element = iterator.next(); + builder.append(getValueAsString(evaluationContext, element)); + if (iterator.hasNext()) { + builder.append(","); + } + } + builder.append("]"); + return builder.toString(); + } + + final ObjectReference objRef = (ObjectReference)value; + final DebugProcess debugProcess = evaluationContext.getDebugProcess(); + Method toStringMethod = debugProcess.getUserData(TO_STRING_METHOD_KEY); + if (toStringMethod == null) { + try { + ReferenceType refType = objRef.virtualMachine().classesByName(JavaClassNames.JAVA_LANG_OBJECT).get(0); + toStringMethod = findMethod(refType, "toString", "()Ljava/lang/String;"); + debugProcess.putUserData(TO_STRING_METHOD_KEY, toStringMethod); + } + catch (Exception ignored) { + throw EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message( + "evaluation.error.cannot.evaluate.tostring", + objRef.referenceType().name() + )); + } + } + if (toStringMethod == null) { + throw EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message( + "evaluation.error.cannot.evaluate.tostring", + objRef.referenceType().name() + )); + } + // while result must be of com.sun.jdi.StringReference type, it turns out that sometimes (jvm bugs?) + // it is a plain com.sun.tools.jdi.ObjectReferenceImpl + final Value result = + debugProcess.invokeInstanceMethod(evaluationContext, objRef, toStringMethod, Collections.emptyList(), 0); + if (result == null) { + return "null"; + } + return result instanceof StringReference ? ((StringReference)result).value() : result.toString(); + } + throw EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message("evaluation.error.unsupported.expression.type")); + } + catch (ObjectCollectedException ignored) { + throw EvaluateExceptionUtil.OBJECT_WAS_COLLECTED; + } } - Method method = null; - if (methodSignature != null) { - if (refType instanceof ClassType) { - method = ((ClassType) refType).concreteMethodByName(methodName, methodSignature); - } - if (method == null) { - final List methods = refType.methodsByName(methodName, methodSignature); - if (methods.size() > 0) { - method = methods.get(0); - } - } - } else { - List methods = null; - if (refType instanceof ClassType) { - methods = refType.methodsByName(methodName); - } - if (methods != null && methods.size() > 0) { - method = methods.get(0); - } + public static final int MAX_DISPLAY_LABEL_LENGTH = 1024 * 5; + + public static String convertToPresentationString(String str) { + if (str.length() > MAX_DISPLAY_LABEL_LENGTH) { + str = translateStringValue(str.substring(0, MAX_DISPLAY_LABEL_LENGTH)); + StringBuilder buf = new StringBuilder(); + buf.append(str); + if (!str.endsWith("...")) { + buf.append("..."); + } + return buf.toString(); + } + return translateStringValue(str); } - return method; - } - - public static boolean isNumeric(Value value) { - return value != null && (isInteger(value) || - value instanceof FloatValue || - value instanceof DoubleValue); - } - - public static boolean isInteger(Value value) { - return value != null && (value instanceof ByteValue || - value instanceof ShortValue || - value instanceof LongValue || - value instanceof IntegerValue); - } - - public static String translateStringValue(final String str) { - int length = str.length(); - final StringBuilder buffer = new StringBuilder(); - StringUtil.escapeStringCharacters(length, str, buffer); - if (str.length() > length) { - buffer.append("..."); + + @Nullable + public static Method findMethod(@Nonnull ReferenceType refType, @NonNls String methodName, @NonNls String methodSignature) { + if (refType instanceof ArrayType) { + // for array types methodByName() in JDI always returns empty list + final Method method = + findMethod(refType.virtualMachine().classesByName(JavaClassNames.JAVA_LANG_OBJECT).get(0), methodName, methodSignature); + if (method != null) { + return method; + } + } + + Method method = null; + if (methodSignature != null) { + if (refType instanceof ClassType) { + method = ((ClassType)refType).concreteMethodByName(methodName, methodSignature); + } + if (method == null) { + final List methods = refType.methodsByName(methodName, methodSignature); + if (methods.size() > 0) { + method = methods.get(0); + } + } + } + else { + List methods = null; + if (refType instanceof ClassType) { + methods = refType.methodsByName(methodName); + } + if (methods != null && methods.size() > 0) { + method = methods.get(0); + } + } + return method; } - return buffer.toString(); - } - - @Nullable - protected static ArrayClass getArrayClass(@Nonnull String className) { - boolean searchBracket = false; - int dims = 0; - int pos; - - for (pos = className.lastIndexOf(']'); pos >= 0; pos--) { - char c = className.charAt(pos); - - if (searchBracket) { - if (c == '[') { - dims++; - searchBracket = false; - } else if (!Character.isWhitespace(c)) { - break; - } - } else { - if (c == ']') { - searchBracket = true; - } else if (!Character.isWhitespace(c)) { - break; - } - } + + public static boolean isNumeric(Value value) { + return value != null && (isInteger(value) || + value instanceof FloatValue || + value instanceof DoubleValue); } - if (searchBracket) { - return null; + public static boolean isInteger(Value value) { + return value != null && (value instanceof ByteValue || + value instanceof ShortValue || + value instanceof LongValue || + value instanceof IntegerValue); } - if (dims == 0) { - return null; + public static String translateStringValue(final String str) { + int length = str.length(); + final StringBuilder buffer = new StringBuilder(); + StringUtil.escapeStringCharacters(length, str, buffer); + if (str.length() > length) { + buffer.append("..."); + } + return buffer.toString(); } - return new ArrayClass(className.substring(0, pos + 1), dims); - } + @Nullable + protected static ArrayClass getArrayClass(@Nonnull String className) { + boolean searchBracket = false; + int dims = 0; + int pos; + + for (pos = className.lastIndexOf(']'); pos >= 0; pos--) { + char c = className.charAt(pos); + + if (searchBracket) { + if (c == '[') { + dims++; + searchBracket = false; + } + else if (!Character.isWhitespace(c)) { + break; + } + } + else { + if (c == ']') { + searchBracket = true; + } + else if (!Character.isWhitespace(c)) { + break; + } + } + } - public static boolean instanceOf(@Nonnull String subType, @Nonnull String superType, @Nullable Project project) { - if (project == null) { - return subType.equals(superType); - } + if (searchBracket) { + return null; + } - ArrayClass nodeClass = getArrayClass(subType); - ArrayClass rendererClass = getArrayClass(superType); - if (nodeClass == null || rendererClass == null) { - return false; - } + if (dims == 0) { + return null; + } - if (nodeClass.dims == rendererClass.dims) { - GlobalSearchScope scope = GlobalSearchScope.allScope(project); - PsiClass psiNodeClass = JavaPsiFacade.getInstance(project).findClass(nodeClass.className, scope); - PsiClass psiRendererClass = JavaPsiFacade.getInstance(project).findClass(rendererClass.className, scope); - return InheritanceUtil.isInheritorOrSelf(psiNodeClass, psiRendererClass, true); - } else if (nodeClass.dims > rendererClass.dims) { - return rendererClass.className.equals(JavaClassNames.JAVA_LANG_OBJECT); + return new ArrayClass(className.substring(0, pos + 1), dims); } - return false; - } - @Nullable - public static Type getSuperType(@Nullable Type subType, @Nonnull String superType) { - if (subType == null) { - return null; - } + public static boolean instanceOf(@Nonnull String subType, @Nonnull String superType, @Nullable Project project) { + if (project == null) { + return subType.equals(superType); + } - if (JavaClassNames.JAVA_LANG_OBJECT.equals(superType)) { - List list = subType.virtualMachine().classesByName(JavaClassNames.JAVA_LANG_OBJECT); - if (list.size() > 0) { - return (ReferenceType) list.get(0); - } - return null; + ArrayClass nodeClass = getArrayClass(subType); + ArrayClass rendererClass = getArrayClass(superType); + if (nodeClass == null || rendererClass == null) { + return false; + } + + if (nodeClass.dims == rendererClass.dims) { + GlobalSearchScope scope = GlobalSearchScope.allScope(project); + PsiClass psiNodeClass = JavaPsiFacade.getInstance(project).findClass(nodeClass.className, scope); + PsiClass psiRendererClass = JavaPsiFacade.getInstance(project).findClass(rendererClass.className, scope); + return InheritanceUtil.isInheritorOrSelf(psiNodeClass, psiRendererClass, true); + } + else if (nodeClass.dims > rendererClass.dims) { + return rendererClass.className.equals(JavaClassNames.JAVA_LANG_OBJECT); + } + return false; } - return getSuperTypeInt(subType, superType); - } + @Nullable + public static Type getSuperType(@Nullable Type subType, @Nonnull String superType) { + if (subType == null) { + return null; + } - private static boolean typeEquals(@Nonnull Type type, @Nonnull String typeName) { - int genericPos = typeName.indexOf('<'); - if (genericPos > -1) { - typeName = typeName.substring(0, genericPos); + if (JavaClassNames.JAVA_LANG_OBJECT.equals(superType)) { + List list = subType.virtualMachine().classesByName(JavaClassNames.JAVA_LANG_OBJECT); + if (list.size() > 0) { + return (ReferenceType)list.get(0); + } + return null; + } + + return getSuperTypeInt(subType, superType); } - return type.name().replace('$', '.').equals(typeName.replace('$', '.')); - } - private static Type getSuperTypeInt(@Nonnull Type subType, @Nonnull String superType) { - if (typeEquals(subType, superType)) { - return subType; + private static boolean typeEquals(@Nonnull Type type, @Nonnull String typeName) { + int genericPos = typeName.indexOf('<'); + if (genericPos > -1) { + typeName = typeName.substring(0, genericPos); + } + return type.name().replace('$', '.').equals(typeName.replace('$', '.')); } - Type result; - if (subType instanceof ClassType) { - try { - ClassType clsType = (ClassType) subType; - result = getSuperType(clsType.superclass(), superType); - if (result != null) { - return result; + private static Type getSuperTypeInt(@Nonnull Type subType, @Nonnull String superType) { + if (typeEquals(subType, superType)) { + return subType; } - for (InterfaceType iface : clsType.allInterfaces()) { - if (typeEquals(iface, superType)) { - return iface; - } + Type result; + if (subType instanceof ClassType) { + try { + ClassType clsType = (ClassType)subType; + result = getSuperType(clsType.superclass(), superType); + if (result != null) { + return result; + } + + for (InterfaceType iface : clsType.allInterfaces()) { + if (typeEquals(iface, superType)) { + return iface; + } + } + } + catch (ClassNotPreparedException e) { + LOG.info(e); + } + return null; } - } catch (ClassNotPreparedException e) { - LOG.info(e); - } - return null; - } - if (subType instanceof InterfaceType) { - try { - for (InterfaceType iface : ((InterfaceType) subType).superinterfaces()) { - result = getSuperType(iface, superType); - if (result != null) { - return result; - } - } - } catch (ClassNotPreparedException e) { - LOG.info(e); - } - } else if (subType instanceof ArrayType) { - if (superType.endsWith("[]")) { - try { - String superTypeItem = superType.substring(0, superType.length() - 2); - Type subTypeItem = ((ArrayType) subType).componentType(); - return instanceOf(subTypeItem, superTypeItem) ? subType : null; - } catch (ClassNotLoadedException e) { - LOG.info(e); - } - } - } else if (subType instanceof PrimitiveType) { - //noinspection HardCodedStringLiteral - if (superType.equals("java.lang.Primitive")) { - return subType; - } + if (subType instanceof InterfaceType) { + try { + for (InterfaceType iface : ((InterfaceType)subType).superinterfaces()) { + result = getSuperType(iface, superType); + if (result != null) { + return result; + } + } + } + catch (ClassNotPreparedException e) { + LOG.info(e); + } + } + else if (subType instanceof ArrayType) { + if (superType.endsWith("[]")) { + try { + String superTypeItem = superType.substring(0, superType.length() - 2); + Type subTypeItem = ((ArrayType)subType).componentType(); + return instanceOf(subTypeItem, superTypeItem) ? subType : null; + } + catch (ClassNotLoadedException e) { + LOG.info(e); + } + } + } + else if (subType instanceof PrimitiveType) { + //noinspection HardCodedStringLiteral + if (superType.equals("java.lang.Primitive")) { + return subType; + } + } + + //only for interfaces and arrays + if (JavaClassNames.JAVA_LANG_OBJECT.equals(superType)) { + List list = subType.virtualMachine().classesByName(JavaClassNames.JAVA_LANG_OBJECT); + if (list.size() > 0) { + return (ReferenceType)list.get(0); + } + } + return null; } - //only for interfaces and arrays - if (JavaClassNames.JAVA_LANG_OBJECT.equals(superType)) { - List list = subType.virtualMachine().classesByName(JavaClassNames.JAVA_LANG_OBJECT); - if (list.size() > 0) { - return (ReferenceType) list.get(0); - } + public static boolean instanceOf(@Nullable Type subType, @Nonnull String superType) { + return getSuperType(subType, superType) != null; } - return null; - } - - public static boolean instanceOf(@Nullable Type subType, @Nonnull String superType) { - return getSuperType(subType, superType) != null; - } - - @Nullable - public static PsiClass findClass(@Nonnull final String className, @Nonnull Project project, final GlobalSearchScope scope) { - ApplicationManager.getApplication().assertReadAccessAllowed(); - try { - if (getArrayClass(className) != null) { - return JavaPsiFacade.getInstance(project).getElementFactory().getArrayClass(LanguageLevel.HIGHEST); - } - if (project.isDefault()) { - return null; - } - PsiManager psiManager = PsiManager.getInstance(project); - PsiClass psiClass = ClassUtil.findPsiClass(psiManager, className, null, true, scope); - if (psiClass == null) { - GlobalSearchScope globalScope = GlobalSearchScope.allScope(project); - if (!globalScope.equals(scope)) { - psiClass = ClassUtil.findPsiClass(psiManager, className, null, true, globalScope); - } - } + @Nullable + public static PsiClass findClass(@Nonnull final String className, @Nonnull Project project, final GlobalSearchScope scope) { + ApplicationManager.getApplication().assertReadAccessAllowed(); + try { + if (getArrayClass(className) != null) { + return JavaPsiFacade.getInstance(project).getElementFactory().getArrayClass(LanguageLevel.HIGHEST); + } + if (project.isDefault()) { + return null; + } + + PsiManager psiManager = PsiManager.getInstance(project); + PsiClass psiClass = ClassUtil.findPsiClass(psiManager, className, null, true, scope); + if (psiClass == null) { + GlobalSearchScope globalScope = GlobalSearchScope.allScope(project); + if (!globalScope.equals(scope)) { + psiClass = ClassUtil.findPsiClass(psiManager, className, null, true, globalScope); + } + } - return psiClass; - } catch (IndexNotReadyException ignored) { - return null; + return psiClass; + } + catch (IndexNotReadyException ignored) { + return null; + } } - } - - @Nullable - public static PsiType getType(@Nonnull String className, @Nonnull Project project) { - ApplicationManager.getApplication().assertReadAccessAllowed(); - - final PsiManager psiManager = PsiManager.getInstance(project); - try { - if (getArrayClass(className) != null) { - return JavaPsiFacade.getInstance(psiManager.getProject()).getElementFactory().createTypeFromText(className, null); - } - if (project.isDefault()) { + + @Nullable + public static PsiType getType(@Nonnull String className, @Nonnull Project project) { + ApplicationManager.getApplication().assertReadAccessAllowed(); + + final PsiManager psiManager = PsiManager.getInstance(project); + try { + if (getArrayClass(className) != null) { + return JavaPsiFacade.getInstance(psiManager.getProject()).getElementFactory().createTypeFromText(className, null); + } + if (project.isDefault()) { + return null; + } + final PsiClass aClass = JavaPsiFacade.getInstance(psiManager.getProject()) + .findClass(className.replace('$', '.'), GlobalSearchScope.allScope(project)); + if (aClass != null) { + return JavaPsiFacade.getInstance(psiManager.getProject()).getElementFactory().createType(aClass); + } + } + catch (IncorrectOperationException e) { + LOG.error(e); + } return null; - } - final PsiClass aClass = JavaPsiFacade.getInstance(psiManager.getProject()).findClass(className.replace('$', '.'), GlobalSearchScope.allScope(project)); - if (aClass != null) { - return JavaPsiFacade.getInstance(psiManager.getProject()).getElementFactory().createType(aClass); - } - } catch (IncorrectOperationException e) { - LOG.error(e); } - return null; - } - public static void checkSyntax(PsiCodeFragment codeFragment) throws EvaluateException { - PsiElement[] children = codeFragment.getChildren(); + public static void checkSyntax(PsiCodeFragment codeFragment) throws EvaluateException { + PsiElement[] children = codeFragment.getChildren(); - if (children.length == 0) { - throw EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message("evaluation.error.empty.code.fragment")); + if (children.length == 0) { + throw EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message("evaluation.error.empty.code.fragment")); + } + for (PsiElement child : children) { + if (child instanceof PsiErrorElement) { + throw EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message( + "evaluation.error.invalid.expression", + child.getText() + )); + } + } } - for (PsiElement child : children) { - if (child instanceof PsiErrorElement) { - throw EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message("evaluation.error.invalid.expression", child.getText())); - } + + public static boolean hasSideEffects(PsiElement element) { + return hasSideEffectsOrReferencesMissingVars(element, null); } - } - - public static boolean hasSideEffects(PsiElement element) { - return hasSideEffectsOrReferencesMissingVars(element, null); - } - - public static boolean hasSideEffectsOrReferencesMissingVars(PsiElement element, @Nullable final Set visibleLocalVariables) { - final Ref rv = new Ref(Boolean.FALSE); - element.accept(new JavaRecursiveElementWalkingVisitor() { - @Override - public void visitPostfixExpression(final PsiPostfixExpression expression) { - rv.set(Boolean.TRUE); - } - - @Override - public void visitReferenceExpression(final PsiReferenceExpression expression) { - final PsiElement psiElement = expression.resolve(); - if (psiElement instanceof PsiLocalVariable) { - if (visibleLocalVariables != null) { - if (!visibleLocalVariables.contains(((PsiLocalVariable) psiElement).getName())) { - rv.set(Boolean.TRUE); - } - } - } else if (psiElement instanceof PsiMethod) { - rv.set(Boolean.TRUE); - //final PsiMethod method = (PsiMethod)psiElement; - //if (!isSimpleGetter(method)) { - // rv.set(Boolean.TRUE); - //} - } - if (!rv.get().booleanValue()) { - super.visitReferenceExpression(expression); - } - } - - @Override - public void visitPrefixExpression(final PsiPrefixExpression expression) { - final IElementType op = expression.getOperationTokenType(); - if (JavaTokenType.PLUSPLUS.equals(op) || JavaTokenType.MINUSMINUS.equals(op)) { - rv.set(Boolean.TRUE); - } else { - super.visitPrefixExpression(expression); - } - } - - @Override - public void visitAssignmentExpression(final PsiAssignmentExpression expression) { - rv.set(Boolean.TRUE); - } - - @Override - public void visitCallExpression(final PsiCallExpression callExpression) { - rv.set(Boolean.TRUE); - //final PsiMethod method = callExpression.resolveMethod(); - //if (method == null || !isSimpleGetter(method)) { - // rv.set(Boolean.TRUE); - //} - //else { - // super.visitCallExpression(callExpression); - //} - } - }); - return rv.get().booleanValue(); - } - - @Nonnull - public abstract TransportService.ListenKey findAvailableDebugAddress(int type) throws ExecutionException; - - public static boolean isSynthetic(TypeComponent typeComponent) { - if (typeComponent == null) { - return false; + + public static boolean hasSideEffectsOrReferencesMissingVars(PsiElement element, @Nullable final Set visibleLocalVariables) { + final Ref rv = new Ref(Boolean.FALSE); + element.accept(new JavaRecursiveElementWalkingVisitor() { + @Override + public void visitPostfixExpression(final PsiPostfixExpression expression) { + rv.set(Boolean.TRUE); + } + + @Override + public void visitReferenceExpression(final PsiReferenceExpression expression) { + final PsiElement psiElement = expression.resolve(); + if (psiElement instanceof PsiLocalVariable) { + if (visibleLocalVariables != null) { + if (!visibleLocalVariables.contains(((PsiLocalVariable)psiElement).getName())) { + rv.set(Boolean.TRUE); + } + } + } + else if (psiElement instanceof PsiMethod) { + rv.set(Boolean.TRUE); + //final PsiMethod method = (PsiMethod)psiElement; + //if (!isSimpleGetter(method)) { + // rv.set(Boolean.TRUE); + //} + } + if (!rv.get().booleanValue()) { + super.visitReferenceExpression(expression); + } + } + + @Override + public void visitPrefixExpression(final PsiPrefixExpression expression) { + final IElementType op = expression.getOperationTokenType(); + if (JavaTokenType.PLUSPLUS.equals(op) || JavaTokenType.MINUSMINUS.equals(op)) { + rv.set(Boolean.TRUE); + } + else { + super.visitPrefixExpression(expression); + } + } + + @Override + public void visitAssignmentExpression(final PsiAssignmentExpression expression) { + rv.set(Boolean.TRUE); + } + + @Override + public void visitCallExpression(final PsiCallExpression callExpression) { + rv.set(Boolean.TRUE); + //final PsiMethod method = callExpression.resolveMethod(); + //if (method == null || !isSimpleGetter(method)) { + // rv.set(Boolean.TRUE); + //} + //else { + // super.visitCallExpression(callExpression); + //} + } + }); + return rv.get().booleanValue(); } - for (SyntheticTypeComponentProvider syntheticTypeComponentProvider : Application.get().getExtensionList(SyntheticTypeComponentProvider.class)) { - if (syntheticTypeComponentProvider.isSynthetic(typeComponent)) { - return true; - } + @Nonnull + public abstract TransportService.ListenKey findAvailableDebugAddress(int type) throws ExecutionException; + + public static boolean isSynthetic(TypeComponent typeComponent) { + if (typeComponent == null) { + return false; + } + + for (SyntheticTypeComponentProvider syntheticTypeComponentProvider + : Application.get().getExtensionList(SyntheticTypeComponentProvider.class)) { + if (syntheticTypeComponentProvider.isSynthetic(typeComponent)) { + return true; + } + } + return false; } - return false; - } - - public static boolean isInsideSimpleGetter(@Nonnull PsiElement contextElement) { - for (SimplePropertyGetterProvider provider : SimplePropertyGetterProvider.EP_NAME.getExtensionList()) { - if (provider.isInsideSimpleGetter(contextElement)) { - return true; - } + + public static boolean isInsideSimpleGetter(@Nonnull PsiElement contextElement) { + for (SimplePropertyGetterProvider provider : SimplePropertyGetterProvider.EP_NAME.getExtensionList()) { + if (provider.isInsideSimpleGetter(contextElement)) { + return true; + } + } + return false; } - return false; - } - public static boolean isPrimitiveType(final String typeName) { - return ourPrimitiveTypeNames.contains(typeName); - } + public static boolean isPrimitiveType(final String typeName) { + return ourPrimitiveTypeNames.contains(typeName); + } - protected static class ArrayClass { - public String className; - public int dims; + protected static class ArrayClass { + public String className; + public int dims; - public ArrayClass(String className, int dims) { - this.className = className; - this.dims = dims; + public ArrayClass(String className, int dims) { + this.className = className; + this.dims = dims; + } } - } - public static DebuggerUtils getInstance() { - return ServiceManager.getService(DebuggerUtils.class); - } + public static DebuggerUtils getInstance() { + return ServiceManager.getService(DebuggerUtils.class); + } - public abstract PsiExpression substituteThis(PsiExpression expressionWithThis, PsiExpression howToEvaluateThis, Value howToEvaluateThisValue, StackFrameContext context) throws EvaluateException; + public abstract PsiExpression substituteThis( + PsiExpression expressionWithThis, + PsiExpression howToEvaluateThis, + Value howToEvaluateThisValue, + StackFrameContext context + ) throws EvaluateException; - public abstract DebuggerContext getDebuggerContext(DataContext context); + public abstract DebuggerContext getDebuggerContext(DataContext context); - public abstract Element writeTextWithImports(TextWithImports text); + public abstract Element writeTextWithImports(TextWithImports text); - public abstract TextWithImports readTextWithImports(Element element); + public abstract TextWithImports readTextWithImports(Element element); - public abstract void writeTextWithImports(Element root, @NonNls String name, TextWithImports value); + public abstract void writeTextWithImports(Element root, @NonNls String name, TextWithImports value); - public abstract TextWithImports readTextWithImports(Element root, @NonNls String name); + public abstract TextWithImports readTextWithImports(Element root, @NonNls String name); - public abstract TextWithImports createExpressionWithImports(@NonNls String expression); + public abstract TextWithImports createExpressionWithImports(@NonNls String expression); - public abstract PsiElement getContextElement(final StackFrameContext context); + public abstract PsiElement getContextElement(final StackFrameContext context); - public abstract PsiClass chooseClassDialog(String title, Project project); + public abstract PsiClass chooseClassDialog(String title, Project project); } diff --git a/java-debugger-api/src/main/java/com/intellij/java/debugger/engine/SimplePropertyGetterProvider.java b/java-debugger-api/src/main/java/com/intellij/java/debugger/engine/SimplePropertyGetterProvider.java index 9e9a639a7a..bb03d1e4b4 100644 --- a/java-debugger-api/src/main/java/com/intellij/java/debugger/engine/SimplePropertyGetterProvider.java +++ b/java-debugger-api/src/main/java/com/intellij/java/debugger/engine/SimplePropertyGetterProvider.java @@ -24,8 +24,7 @@ @ExtensionAPI(ComponentScope.APPLICATION) public interface SimplePropertyGetterProvider { - ExtensionPointName EP_NAME = - ExtensionPointName.create(SimplePropertyGetterProvider.class); + ExtensionPointName EP_NAME = ExtensionPointName.create(SimplePropertyGetterProvider.class); - boolean isInsideSimpleGetter(@Nonnull PsiElement element); + boolean isInsideSimpleGetter(@Nonnull PsiElement element); } diff --git a/java-debugger-api/src/main/java/com/intellij/java/debugger/engine/SourcePositionHighlighter.java b/java-debugger-api/src/main/java/com/intellij/java/debugger/engine/SourcePositionHighlighter.java index 06c983aea8..827ca8739f 100644 --- a/java-debugger-api/src/main/java/com/intellij/java/debugger/engine/SourcePositionHighlighter.java +++ b/java-debugger-api/src/main/java/com/intellij/java/debugger/engine/SourcePositionHighlighter.java @@ -32,21 +32,20 @@ */ @ExtensionAPI(ComponentScope.APPLICATION) public abstract class SourcePositionHighlighter { - public static final ExtensionPointName EP_NAME = - ExtensionPointName.create(SourcePositionHighlighter.class); + public static final ExtensionPointName EP_NAME = ExtensionPointName.create(SourcePositionHighlighter.class); - @RequiredReadAction - public abstract TextRange getHighlightRange(SourcePosition sourcePosition); + @RequiredReadAction + public abstract TextRange getHighlightRange(SourcePosition sourcePosition); - @Nullable - public static TextRange getHighlightRangeFor(SourcePosition sourcePosition) { - DumbService dumbService = DumbService.getInstance(sourcePosition.getFile().getProject()); - for (SourcePositionHighlighter provider : dumbService.filterByDumbAwareness(EP_NAME.getExtensionList())) { - TextRange range = provider.getHighlightRange(sourcePosition); - if (range != null) { - return range; - } + @Nullable + public static TextRange getHighlightRangeFor(SourcePosition sourcePosition) { + DumbService dumbService = DumbService.getInstance(sourcePosition.getFile().getProject()); + for (SourcePositionHighlighter provider : dumbService.filterByDumbAwareness(EP_NAME.getExtensionList())) { + TextRange range = provider.getHighlightRange(sourcePosition); + if (range != null) { + return range; + } + } + return null; } - return null; - } } diff --git a/java-debugger-api/src/main/java/com/intellij/java/debugger/ui/classFilter/DebuggerClassFilterProvider.java b/java-debugger-api/src/main/java/com/intellij/java/debugger/ui/classFilter/DebuggerClassFilterProvider.java index 69bd4ffb17..921ed89766 100644 --- a/java-debugger-api/src/main/java/com/intellij/java/debugger/ui/classFilter/DebuggerClassFilterProvider.java +++ b/java-debugger-api/src/main/java/com/intellij/java/debugger/ui/classFilter/DebuggerClassFilterProvider.java @@ -23,11 +23,11 @@ /** * @author Eugene Zhuravlev - * Date: Oct 22, 2008 + * Date: Oct 22, 2008 */ @ExtensionAPI(ComponentScope.APPLICATION) public interface DebuggerClassFilterProvider { - ExtensionPointName EP_NAME = ExtensionPointName.create(DebuggerClassFilterProvider.class); + ExtensionPointName EP_NAME = ExtensionPointName.create(DebuggerClassFilterProvider.class); - List getFilters(); + List getFilters(); } diff --git a/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/DebuggerSession.java b/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/DebuggerSession.java index 8cca3708c8..a5c92c315e 100644 --- a/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/DebuggerSession.java +++ b/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/DebuggerSession.java @@ -73,913 +73,802 @@ import java.util.List; import java.util.concurrent.atomic.AtomicReference; -public class DebuggerSession implements AbstractDebuggerSession -{ - private static final Logger LOG = Logger.getInstance(DebuggerSession.class); - // flags - private final MyDebuggerStateManager myContextManager; - - public enum State - { - STOPPED, - RUNNING, - WAITING_ATTACH, - PAUSED, - WAIT_EVALUATION, - DISPOSED - } - - public enum Event - { - ATTACHED, - DETACHED, - RESUME, - STEP, - PAUSE, - REFRESH, - CONTEXT, - START_WAIT_ATTACH, - DISPOSE, - REFRESH_WITH_STACK, - THREADS_REFRESH - } - - private volatile boolean myIsEvaluating; - private volatile int myIgnoreFiltersFrameCountThreshold = 0; - - private DebuggerSessionState myState = null; - - private final String mySessionName; - private final DebugProcessImpl myDebugProcess; - private final GlobalSearchScope mySearchScope; - private Sdk myAlternativeJre; - private Sdk myRunJre; - - private final DebuggerContextImpl SESSION_EMPTY_CONTEXT; - //Thread, user is currently stepping through - private final AtomicReference mySteppingThroughThread = new AtomicReference<>(); - protected final Alarm myUpdateAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD); - - private boolean myModifiedClassesScanRequired = false; - - public boolean isSteppingThrough(ThreadReferenceProxyImpl threadProxy) - { - return Comparing.equal(mySteppingThroughThread.get(), threadProxy); - } - - public void setSteppingThrough(ThreadReferenceProxyImpl threadProxy) - { - mySteppingThroughThread.set(threadProxy); - } - - public void clearSteppingThrough() - { - mySteppingThroughThread.set(null); - resetIgnoreStepFiltersFlag(); - } - - @Nonnull - public GlobalSearchScope getSearchScope() - { - return mySearchScope; - } - - public Sdk getAlternativeJre() - { - return myAlternativeJre; - } - - public void setAlternativeJre(Sdk sdk) - { - myAlternativeJre = sdk; - PsiElementFinder.EP_NAME.findExtensionOrFail(getProject(), AlternativeJreClassFinder.class).clearCache(); - } - - public Sdk getRunJre() - { - return myRunJre; - } - - public boolean isModifiedClassesScanRequired() - { - return myModifiedClassesScanRequired; - } - - public void setModifiedClassesScanRequired(boolean modifiedClassesScanRequired) - { - myModifiedClassesScanRequired = modifiedClassesScanRequired; - } - - private class MyDebuggerStateManager extends DebuggerStateManager - { - private DebuggerContextImpl myDebuggerContext; - - MyDebuggerStateManager() - { - myDebuggerContext = SESSION_EMPTY_CONTEXT; - } - - @Nonnull - @Override - public DebuggerContextImpl getContext() - { - return myDebuggerContext; - } - - /** - * actually state changes not in the same sequence as you call setState - * the 'resuming' setState with context.getSuspendContext() == null may be set prior to - * the setState for the context with context.getSuspendContext() - *

- * in this case we assume that the latter setState is ignored - * since the thread was resumed - */ - @Override - @RequiredUIAccess - public void setState(@Nonnull final DebuggerContextImpl context, final State state, final Event event, final String description) - { - Application.get().assertIsDispatchThread(); - final DebuggerSession session = context.getDebuggerSession(); - LOG.assertTrue(session == DebuggerSession.this || session == null); - final Runnable setStateRunnable = () -> - { - LOG.assertTrue(myDebuggerContext.isInitialised()); - myDebuggerContext = context; - if (LOG.isDebugEnabled()) - { - LOG.debug("DebuggerSession state = " + state + ", event = " + event); - } - - myIsEvaluating = false; - - myState = new DebuggerSessionState(state, description); - fireStateChanged(context, event); - }; - - if (context.getSuspendContext() == null) - { - setStateRunnable.run(); - } - else - { - getProcess().getManagerThread().schedule(new SuspendContextCommandImpl(context.getSuspendContext()) - { - @Override - public PrioritizedTask.Priority getPriority() - { - return PrioritizedTask.Priority.HIGH; - } - - @Override - public void contextAction() throws Exception - { - context.initCaches(); - DebuggerInvocationUtil.swingInvokeLater(getProject(), setStateRunnable); - } - }); - } - } - } - - static DebuggerSession create(String sessionName, @Nonnull final DebugProcessImpl debugProcess, DebugEnvironment environment) throws ExecutionException - { - DebuggerSession session = new DebuggerSession(sessionName, debugProcess, environment); - try - { - session.attach(environment); - } - catch (ExecutionException e) - { - session.dispose(); - throw e; - } - return session; - } - - private DebuggerSession(String sessionName, @Nonnull final DebugProcessImpl debugProcess, DebugEnvironment environment) - { - mySessionName = sessionName; - myDebugProcess = debugProcess; - SESSION_EMPTY_CONTEXT = DebuggerContextImpl.createDebuggerContext(this, null, null, null); - myContextManager = new MyDebuggerStateManager(); - myState = new DebuggerSessionState(State.STOPPED, null); - myDebugProcess.addDebugProcessListener(new MyDebugProcessListener(debugProcess)); - myDebugProcess.addEvaluationListener(new MyEvaluationListener()); - ValueLookupManager.getInstance(getProject()).startListening(); - mySearchScope = environment.getSearchScope(); - myAlternativeJre = environment.getAlternativeJre(); - myRunJre = environment.getRunJre(); - } - - @Nonnull - public DebuggerStateManager getContextManager() - { - return myContextManager; - } - - public Project getProject() - { - return getProcess().getProject(); - } - - public String getSessionName() - { - return mySessionName; - } - - @Nonnull - public DebugProcessImpl getProcess() - { - return myDebugProcess; - } - - private static class DebuggerSessionState - { - final State myState; - final String myDescription; - - public DebuggerSessionState(State state, String description) - { - myState = state; - myDescription = description; - } - } - - public State getState() - { - return myState.myState; - } - - public String getStateDescription() - { - if (myState.myDescription != null) - { - return myState.myDescription; - } - - switch(myState.myState) - { - case STOPPED: - return DebuggerBundle.message("status.debug.stopped"); - case RUNNING: - return DebuggerBundle.message("status.app.running"); - case WAITING_ATTACH: - RemoteConnection connection = getProcess().getConnection(); - final String addressDisplayName = DebuggerBundle.getAddressDisplayName(connection); - final String transportName = DebuggerBundle.getTransportName(connection); - return connection.isServerMode() - ? DebuggerBundle.message("status.listening", addressDisplayName, transportName) - : DebuggerBundle.message("status.connecting", addressDisplayName, transportName); - case PAUSED: - return DebuggerBundle.message("status.paused"); - case WAIT_EVALUATION: - return DebuggerBundle.message("status.waiting.evaluation.result"); - case DISPOSED: - return DebuggerBundle.message("status.debug.stopped"); - } - return null; - } - - /* Stepping */ - private void resumeAction(final DebugProcessImpl.ResumeCommand command, Event event) - { - getContextManager().setState(SESSION_EMPTY_CONTEXT, State.WAIT_EVALUATION, event, null); - myDebugProcess.getManagerThread().schedule(command); - } - - @RequiredUIAccess - public void stepOut(int stepSize) - { - SuspendContextImpl suspendContext = getSuspendContext(); - DebugProcessImpl.ResumeCommand cmd = null; - for (JvmSteppingCommandProvider handler : JvmSteppingCommandProvider.EP_NAME.getExtensions()) - { - cmd = handler.getStepOutCommand(suspendContext, stepSize); - if (cmd != null) - { - break; - } - } - if (cmd == null) - { - cmd = myDebugProcess.createStepOutCommand(suspendContext, stepSize); - } - setSteppingThrough(cmd.getContextThread()); - resumeAction(cmd, Event.STEP); - } - - @RequiredUIAccess - public void stepOut() - { - stepOut(StepRequest.STEP_LINE); - } - - @RequiredUIAccess - public void stepOver(boolean ignoreBreakpoints, int stepSize) - { - SuspendContextImpl suspendContext = getSuspendContext(); - DebugProcessImpl.ResumeCommand cmd = null; - for (JvmSteppingCommandProvider handler : JvmSteppingCommandProvider.EP_NAME.getExtensions()) - { - cmd = handler.getStepOverCommand(suspendContext, ignoreBreakpoints, stepSize); - if (cmd != null) - { - break; - } - } - if (cmd == null) - { - cmd = myDebugProcess.createStepOverCommand(suspendContext, ignoreBreakpoints, stepSize); - } - setSteppingThrough(cmd.getContextThread()); - resumeAction(cmd, Event.STEP); - } - - @RequiredUIAccess - public void stepOver(boolean ignoreBreakpoints) - { - stepOver(ignoreBreakpoints, StepRequest.STEP_LINE); - } - - @RequiredUIAccess - public void stepInto(final boolean ignoreFilters, final @Nullable MethodFilter smartStepFilter, int stepSize) - { - final SuspendContextImpl suspendContext = getSuspendContext(); - DebugProcessImpl.ResumeCommand cmd = null; - for (JvmSteppingCommandProvider handler : JvmSteppingCommandProvider.EP_NAME.getExtensions()) - { - cmd = handler.getStepIntoCommand(suspendContext, ignoreFilters, smartStepFilter, stepSize); - if (cmd != null) - { - break; - } - } - if (cmd == null) - { - cmd = myDebugProcess.createStepIntoCommand(suspendContext, ignoreFilters, smartStepFilter, stepSize); - } - setSteppingThrough(cmd.getContextThread()); - resumeAction(cmd, Event.STEP); - } - - @RequiredUIAccess - public void stepInto(final boolean ignoreFilters, final @Nullable MethodFilter smartStepFilter) - { - stepInto(ignoreFilters, smartStepFilter, StepRequest.STEP_LINE); - } - - @RequiredUIAccess - public void runToCursor(@Nonnull XSourcePosition position, final boolean ignoreBreakpoints) - { - try - { - DebugProcessImpl.ResumeCommand runToCursorCommand = - myDebugProcess.createRunToCursorCommand(getSuspendContext(), position, ignoreBreakpoints); - setSteppingThrough(runToCursorCommand.getContextThread()); - resumeAction(runToCursorCommand, Event.STEP); - } - catch (EvaluateException e) - { - Messages.showErrorDialog(e.getMessage(), ActionLocalize.actionRuntocursorText().map(Presentation.NO_MNEMONIC).get()); - } - } - - @RequiredUIAccess - public void resume() - { - final SuspendContextImpl suspendContext = getSuspendContext(); - if (suspendContext != null) - { - clearSteppingThrough(); - resumeAction(myDebugProcess.createResumeCommand(suspendContext), Event.RESUME); - } - } - - public void resetIgnoreStepFiltersFlag() - { - myIgnoreFiltersFrameCountThreshold = 0; - } - - public void setIgnoreStepFiltersFlag(int currentStackFrameCount) - { - myIgnoreFiltersFrameCountThreshold = myIgnoreFiltersFrameCountThreshold <= 0 - ? currentStackFrameCount : Math.min(myIgnoreFiltersFrameCountThreshold, currentStackFrameCount); - } - - public boolean shouldIgnoreSteppingFilters() - { - return myIgnoreFiltersFrameCountThreshold > 0; - } - - public void pause() - { - myDebugProcess.getManagerThread().schedule(myDebugProcess.createPauseCommand()); - } - - /*Presentation*/ - - @RequiredUIAccess - public void showExecutionPoint() - { - getContextManager() - .setState(DebuggerContextUtil.createDebuggerContext(this, getSuspendContext()), State.PAUSED, Event.REFRESH, null); - } - - @RequiredUIAccess - public void refresh(final boolean refreshWithStack) - { - final State state = getState(); - DebuggerContextImpl context = myContextManager.getContext(); - DebuggerContextImpl newContext = - DebuggerContextImpl.createDebuggerContext(this, context.getSuspendContext(), context.getThreadProxy(), context.getFrameProxy()); - myContextManager.setState(newContext, state, refreshWithStack ? Event.REFRESH_WITH_STACK : Event.REFRESH, null); - } - - public void dispose() - { - getProcess().dispose(); - Disposer.dispose(myUpdateAlarm); - DebuggerInvocationUtil.swingInvokeLater(getProject(), () -> - { - myContextManager.setState(SESSION_EMPTY_CONTEXT, State.DISPOSED, Event.DISPOSE, null); - myContextManager.dispose(); - }); - } - - // ManagerCommands - @Override - public boolean isStopped() - { - return getState() == State.STOPPED; - } - - public boolean isAttached() - { - return !isStopped() && getState() != State.WAITING_ATTACH; - } - - @Override - public boolean isPaused() - { - return getState() == State.PAUSED; - } - - public boolean isConnecting() - { - return getState() == State.WAITING_ATTACH; - } - - public boolean isEvaluating() - { - return myIsEvaluating; - } - - public boolean isRunning() - { - return getState() == State.RUNNING && !getProcess().getProcessHandler().isProcessTerminated(); - } - - @RequiredUIAccess - private SuspendContextImpl getSuspendContext() - { - Application.get().assertIsDispatchThread(); - return getContextManager().getContext().getSuspendContext(); - } - - @Nullable - private ExecutionResult attach(DebugEnvironment environment) throws ExecutionException - { - RemoteConnection remoteConnection = environment.getRemoteConnection(); - final String addressDisplayName = DebuggerBundle.getAddressDisplayName(remoteConnection); - final String transportName = DebuggerBundle.getTransportName(remoteConnection); - final ExecutionResult executionResult = myDebugProcess.attachVirtualMachine(environment, this); - getContextManager().setState( - SESSION_EMPTY_CONTEXT, - State.WAITING_ATTACH, - Event.START_WAIT_ATTACH, - DebuggerBundle.message("status.waiting.attach", addressDisplayName, transportName) - ); - return executionResult; - } - - private class MyDebugProcessListener extends DebugProcessAdapterImpl - { - private final DebugProcessImpl myDebugProcess; - - public MyDebugProcessListener(final DebugProcessImpl debugProcess) - { - myDebugProcess = debugProcess; - } - - //executed in manager thread - @Override - public void connectorIsReady() - { - DebuggerInvocationUtil.invokeLater(getProject(), () -> - { - RemoteConnection connection = myDebugProcess.getConnection(); - final String addressDisplayName = DebuggerBundle.getAddressDisplayName(connection); - final String transportName = DebuggerBundle.getTransportName(connection); - final String connectionStatusMessage = connection.isServerMode() - ? DebuggerBundle.message("status.listening", addressDisplayName, transportName) - : DebuggerBundle.message("status.connecting", addressDisplayName, transportName); - getContextManager().setState(SESSION_EMPTY_CONTEXT, State.WAITING_ATTACH, Event.START_WAIT_ATTACH, connectionStatusMessage); - }); - } - - @Override - public void paused(final SuspendContextImpl suspendContext) - { - LOG.debug("paused"); - - ThreadReferenceProxyImpl currentThread = suspendContext.getThread(); - - if (!shouldSetAsActiveContext(suspendContext)) - { - DebuggerInvocationUtil.invokeLater(getProject(), () -> getContextManager().fireStateChanged(getContextManager().getContext(), Event.THREADS_REFRESH)); - ThreadReferenceProxyImpl thread = suspendContext.getThread(); - if (thread != null) - { - List> descriptors = DebuggerUtilsEx.getEventDescriptors(suspendContext); - if (!descriptors.isEmpty()) - { - XDebuggerUIConstants.NOTIFICATION_GROUP.createNotification( - DebuggerBundle.message("status.breakpoint.reached.in.thread", thread.name()), - DebuggerBundle.message("status.breakpoint.reached.in.thread.switch"), - NotificationType.INFORMATION, - (notification, event) -> { - if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) - { - notification.expire(); - getProcess().getManagerThread().schedule(new SuspendContextCommandImpl(suspendContext) - { - @Override - public void contextAction() throws Exception - { - final DebuggerContextImpl debuggerContext = - DebuggerContextUtil.createDebuggerContext(DebuggerSession.this, suspendContext); - - DebuggerInvocationUtil.invokeLater(getProject(), () -> getContextManager() - .setState(debuggerContext, State.PAUSED, Event.PAUSE, null)); - } - }); - } - } - ).notify(getProject()); - } - } - if (((SuspendManagerImpl) myDebugProcess.getSuspendManager()).getPausedContexts().size() > 1) - { - return; - } - else - { - currentThread = mySteppingThroughThread.get(); - } - } - else - { - setSteppingThrough(currentThread); - } - - final StackFrameContext positionContext; - - if (currentThread == null) - { - //Pause pressed - LOG.assertTrue(suspendContext.getSuspendPolicy() == EventRequest.SUSPEND_ALL); - SuspendContextImpl oldContext = getProcess().getSuspendManager().getPausedContext(); - - if (oldContext != null) - { - currentThread = oldContext.getThread(); - } - - if (currentThread == null) - { - final Collection allThreads = getProcess().getVirtualMachineProxy().allThreads(); - // heuristics: try to pre-select EventDispatchThread - for (final ThreadReferenceProxyImpl thread : allThreads) - { - if (ThreadState.isEDT(thread.name())) - { - currentThread = thread; - break; - } - } - if (currentThread == null) - { - // heuristics: display the first thread with RUNNABLE status - for (final ThreadReferenceProxyImpl thread : allThreads) - { - currentThread = thread; - if (currentThread.status() == ThreadReference.THREAD_STATUS_RUNNING) - { - break; - } - } - } - } - - StackFrameProxyImpl proxy = null; - if (currentThread != null) - { - try - { - while (!currentThread.isSuspended()) - { - // wait until thread is considered suspended. Querying data from a thread immediately after VM.suspend() - // may result in IncompatibleThreadStateException, most likely some time after suspend() VM erroneously thinks that thread is still running - TimeoutUtil.sleep(10); - } - proxy = (currentThread.frameCount() > 0) ? currentThread.frame(0) : null; - } - catch (ObjectCollectedException ignored) - { - proxy = null; - } - catch (EvaluateException e) - { - proxy = null; - LOG.error(e); - } - } - positionContext = new SimpleStackFrameContext(proxy, myDebugProcess); - } - else - { - positionContext = suspendContext; - } - - if (currentThread != null) - { - try - { - final int frameCount = currentThread.frameCount(); - if (frameCount == 0 || (frameCount <= myIgnoreFiltersFrameCountThreshold)) - { - resetIgnoreStepFiltersFlag(); - } - } - catch (EvaluateException e) - { - LOG.info(e); - resetIgnoreStepFiltersFlag(); - } - } - - SourcePosition position = ContextUtil.getSourcePosition(positionContext); - - if (position != null) - { - final List> eventDescriptors = DebuggerUtilsEx.getEventDescriptors(suspendContext); - final RequestManagerImpl requestsManager = suspendContext.getDebugProcess().getRequestsManager(); - final PsiFile foundFile = position.getFile(); - final boolean sourceMissing = foundFile instanceof PsiCompiledElement; - for (Pair eventDescriptor : eventDescriptors) - { - Breakpoint breakpoint = eventDescriptor.getFirst(); - if (breakpoint instanceof LineBreakpoint) - { - final SourcePosition breakpointPosition = ((BreakpointWithHighlighter) breakpoint).getSourcePosition(); - if (breakpointPosition == null || (!sourceMissing && breakpointPosition.getLine() != position.getLine())) - { - requestsManager.deleteRequest(breakpoint); - requestsManager.setInvalid(breakpoint, DebuggerBundle.message("error.invalid.breakpoint.source.changed")); - breakpoint.updateUI(); - } - else if (sourceMissing) - { - // adjust position to be position of the breakpoint in order to show the real originator of the event - position = breakpointPosition; - final StackFrameProxy frameProxy = positionContext.getFrameProxy(); - String className; - try - { - className = frameProxy != null ? frameProxy.location().declaringType().name() : ""; - } - catch (EvaluateException ignored) - { - className = ""; - } - requestsManager.setInvalid(breakpoint, DebuggerBundle.message("error.invalid.breakpoint.source.not.found", className)); - breakpoint.updateUI(); - } - } - } - } - - final DebuggerContextImpl debuggerContext = - DebuggerContextImpl.createDebuggerContext(DebuggerSession.this, suspendContext, currentThread, null); - if (suspendContext.getThread() == currentThread) - { - debuggerContext.setPositionCache(position); - } - - DebuggerInvocationUtil.invokeLater( - getProject(), - () -> getContextManager().setState(debuggerContext, State.PAUSED, Event.PAUSE, getDescription(debuggerContext)) - ); - } - - private boolean shouldSetAsActiveContext(final SuspendContextImpl suspendContext) - { - final ThreadReferenceProxyImpl newThread = suspendContext.getThread(); - if (newThread == null || suspendContext.getSuspendPolicy() == EventRequest.SUSPEND_ALL || isSteppingThrough(newThread)) - { - return true; - } - final SuspendContextImpl currentSuspendContext = getContextManager().getContext().getSuspendContext(); - if (currentSuspendContext == null) - { - return mySteppingThroughThread.get() == null; - } - if (enableBreakpointsDuringEvaluation()) - { - final ThreadReferenceProxyImpl currentThread = currentSuspendContext.getThread(); - return currentThread == null || Comparing.equal(currentThread.getThreadReference(), newThread.getThreadReference()); - } - return false; - } - - - @Override - public void resumed(SuspendContextImpl suspendContext) - { - SuspendContextImpl context = getProcess().getSuspendManager().getPausedContext(); - ThreadReferenceProxyImpl steppingThread = null; - // single thread stepping - if (context != null && suspendContext != null && suspendContext.getSuspendPolicy() == EventRequest.SUSPEND_EVENT_THREAD && isSteppingThrough(suspendContext.getThread())) - { - steppingThread = suspendContext.getThread(); - } - final DebuggerContextImpl debuggerContext = context != null ? DebuggerContextImpl.createDebuggerContext(DebuggerSession.this, context, steppingThread != null ? steppingThread : context - .getThread(), null) : null; - - DebuggerInvocationUtil.invokeLater(getProject(), () -> - { - if (debuggerContext != null) - { - getContextManager().setState(debuggerContext, State.PAUSED, Event.CONTEXT, getDescription(debuggerContext)); - } - else - { - getContextManager().setState(SESSION_EMPTY_CONTEXT, State.RUNNING, Event.CONTEXT, null); - } - }); - } - - @Override - public void processAttached(final DebugProcessImpl process) - { - final RemoteConnection connection = getProcess().getConnection(); - final String addressDisplayName = DebuggerBundle.getAddressDisplayName(connection); - final String transportName = DebuggerBundle.getTransportName(connection); - final String message = DebuggerBundle.message("status.connected", addressDisplayName, transportName); - - process.printToConsole(message + "\n"); - DebuggerInvocationUtil.invokeLater(getProject(), () -> getContextManager().setState(SESSION_EMPTY_CONTEXT, State.RUNNING, Event.ATTACHED, message)); - } - - @Override - public void attachException(final RunProfileState state, final ExecutionException exception, final RemoteConnection remoteConnection) - { - DebuggerInvocationUtil.invokeLater(getProject(), () -> - { - String message = ""; - if (state instanceof RemoteState) - { - message = DebuggerBundle.message( - "status.connect.failed", - DebuggerBundle.getAddressDisplayName(remoteConnection), - DebuggerBundle.getTransportName(remoteConnection) - ); - } - message += exception.getMessage(); - getContextManager().setState(SESSION_EMPTY_CONTEXT, State.STOPPED, Event.DETACHED, message); - }); - } - - @Override - public void processDetached(final DebugProcessImpl debugProcess, boolean closedByUser) - { - if (!closedByUser) - { - ProcessHandler processHandler = debugProcess.getProcessHandler(); - if (processHandler != null) - { - final RemoteConnection connection = getProcess().getConnection(); - final String addressDisplayName = DebuggerBundle.getAddressDisplayName(connection); - final String transportName = DebuggerBundle.getTransportName(connection); - processHandler.notifyTextAvailable( - DebuggerBundle.message("status.disconnected", addressDisplayName, transportName) + "\n", - ProcessOutputTypes.SYSTEM - ); - } - } - DebuggerInvocationUtil.invokeLater(getProject(), () -> - { - final RemoteConnection connection = getProcess().getConnection(); - final String addressDisplayName = DebuggerBundle.getAddressDisplayName(connection); - final String transportName = DebuggerBundle.getTransportName(connection); - getContextManager().setState( - SESSION_EMPTY_CONTEXT, - State.STOPPED, - Event.DETACHED, - DebuggerBundle.message("status.disconnected", addressDisplayName, transportName) - ); - }); - clearSteppingThrough(); - } - - @Override - public void threadStarted(DebugProcess proc, ThreadReference thread) - { - notifyThreadsRefresh(); - } - - @Override - public void threadStopped(DebugProcess proc, ThreadReference thread) - { - notifyThreadsRefresh(); - ThreadReferenceProxyImpl steppingThread = mySteppingThroughThread.get(); - if (steppingThread != null && steppingThread.getThreadReference() == thread) - { - clearSteppingThrough(); - } - DebugProcessImpl debugProcess = (DebugProcessImpl) proc; - if (debugProcess.getRequestsManager().getFilterThread() == thread) - { - DebuggerManagerEx.getInstanceEx(proc.getProject()).getBreakpointManager().applyThreadFilter(debugProcess, null); - } - } - - private void notifyThreadsRefresh() - { - if (!myUpdateAlarm.isDisposed()) - { - myUpdateAlarm.cancelAllRequests(); - myUpdateAlarm.addRequest(() -> - { - final DebuggerStateManager contextManager = getContextManager(); - contextManager.fireStateChanged(contextManager.getContext(), Event.THREADS_REFRESH); - }, 100, Application.get().getNoneModalityState()); - } - } - } - - private static String getDescription(DebuggerContextImpl debuggerContext) - { - SuspendContextImpl suspendContext = debuggerContext.getSuspendContext(); - if (suspendContext != null && debuggerContext.getThreadProxy() != suspendContext.getThread()) - { - return DebuggerBundle.message("status.paused.in.another.thread"); - } - return null; - } - - private class MyEvaluationListener implements EvaluationListener - { - @Override - public void evaluationStarted(SuspendContextImpl context) - { - myIsEvaluating = true; - } - - @Override - public void evaluationFinished(final SuspendContextImpl context) - { - myIsEvaluating = false; - // seems to be not required after move to xdebugger - //DebuggerInvocationUtil.invokeLater(getProject(), new Runnable() { - // @Override - // public void run() { - // if (context != getSuspendContext()) { - // getContextManager().setState(DebuggerContextUtil.createDebuggerContext(DebuggerSession.this, context), STATE_PAUSED, REFRESH, null); - // } - // } - //}); - } - } - - public static boolean enableBreakpointsDuringEvaluation() - { - return false; - } - - public void sessionResumed() - { - XDebugSession session = getXDebugSession(); - if (session != null) - { - session.sessionResumed(); - } - } - - @Nullable - public XDebugSession getXDebugSession() - { - JavaDebugProcess process = myDebugProcess.getXdebugProcess(); - return process != null ? process.getSession() : null; - } +public class DebuggerSession implements AbstractDebuggerSession { + private static final Logger LOG = Logger.getInstance(DebuggerSession.class); + // flags + private final MyDebuggerStateManager myContextManager; + + public enum State { + STOPPED, + RUNNING, + WAITING_ATTACH, + PAUSED, + WAIT_EVALUATION, + DISPOSED + } + + public enum Event { + ATTACHED, + DETACHED, + RESUME, + STEP, + PAUSE, + REFRESH, + CONTEXT, + START_WAIT_ATTACH, + DISPOSE, + REFRESH_WITH_STACK, + THREADS_REFRESH + } + + private volatile boolean myIsEvaluating; + private volatile int myIgnoreFiltersFrameCountThreshold = 0; + + private DebuggerSessionState myState = null; + + private final String mySessionName; + private final DebugProcessImpl myDebugProcess; + private final GlobalSearchScope mySearchScope; + private Sdk myAlternativeJre; + private Sdk myRunJre; + + private final DebuggerContextImpl SESSION_EMPTY_CONTEXT; + //Thread, user is currently stepping through + private final AtomicReference mySteppingThroughThread = new AtomicReference<>(); + protected final Alarm myUpdateAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD); + + private boolean myModifiedClassesScanRequired = false; + + public boolean isSteppingThrough(ThreadReferenceProxyImpl threadProxy) { + return Comparing.equal(mySteppingThroughThread.get(), threadProxy); + } + + public void setSteppingThrough(ThreadReferenceProxyImpl threadProxy) { + mySteppingThroughThread.set(threadProxy); + } + + public void clearSteppingThrough() { + mySteppingThroughThread.set(null); + resetIgnoreStepFiltersFlag(); + } + + @Nonnull + public GlobalSearchScope getSearchScope() { + return mySearchScope; + } + + public Sdk getAlternativeJre() { + return myAlternativeJre; + } + + public void setAlternativeJre(Sdk sdk) { + myAlternativeJre = sdk; + PsiElementFinder.EP_NAME.findExtensionOrFail(getProject(), AlternativeJreClassFinder.class).clearCache(); + } + + public Sdk getRunJre() { + return myRunJre; + } + + public boolean isModifiedClassesScanRequired() { + return myModifiedClassesScanRequired; + } + + public void setModifiedClassesScanRequired(boolean modifiedClassesScanRequired) { + myModifiedClassesScanRequired = modifiedClassesScanRequired; + } + + private class MyDebuggerStateManager extends DebuggerStateManager { + private DebuggerContextImpl myDebuggerContext; + + MyDebuggerStateManager() { + myDebuggerContext = SESSION_EMPTY_CONTEXT; + } + + @Nonnull + @Override + public DebuggerContextImpl getContext() { + return myDebuggerContext; + } + + /** + * actually state changes not in the same sequence as you call setState + * the 'resuming' setState with context.getSuspendContext() == null may be set prior to + * the setState for the context with context.getSuspendContext() + *

+ * in this case we assume that the latter setState is ignored + * since the thread was resumed + */ + @Override + @RequiredUIAccess + public void setState(@Nonnull final DebuggerContextImpl context, final State state, final Event event, final String description) { + Application.get().assertIsDispatchThread(); + final DebuggerSession session = context.getDebuggerSession(); + LOG.assertTrue(session == DebuggerSession.this || session == null); + final Runnable setStateRunnable = () -> + { + LOG.assertTrue(myDebuggerContext.isInitialised()); + myDebuggerContext = context; + if (LOG.isDebugEnabled()) { + LOG.debug("DebuggerSession state = " + state + ", event = " + event); + } + + myIsEvaluating = false; + + myState = new DebuggerSessionState(state, description); + fireStateChanged(context, event); + }; + + if (context.getSuspendContext() == null) { + setStateRunnable.run(); + } + else { + getProcess().getManagerThread().schedule(new SuspendContextCommandImpl(context.getSuspendContext()) { + @Override + public PrioritizedTask.Priority getPriority() { + return PrioritizedTask.Priority.HIGH; + } + + @Override + public void contextAction() throws Exception { + context.initCaches(); + DebuggerInvocationUtil.swingInvokeLater(getProject(), setStateRunnable); + } + }); + } + } + } + + static DebuggerSession create( + String sessionName, + @Nonnull final DebugProcessImpl debugProcess, + DebugEnvironment environment + ) throws ExecutionException { + DebuggerSession session = new DebuggerSession(sessionName, debugProcess, environment); + try { + session.attach(environment); + } + catch (ExecutionException e) { + session.dispose(); + throw e; + } + return session; + } + + private DebuggerSession(String sessionName, @Nonnull final DebugProcessImpl debugProcess, DebugEnvironment environment) { + mySessionName = sessionName; + myDebugProcess = debugProcess; + SESSION_EMPTY_CONTEXT = DebuggerContextImpl.createDebuggerContext(this, null, null, null); + myContextManager = new MyDebuggerStateManager(); + myState = new DebuggerSessionState(State.STOPPED, null); + myDebugProcess.addDebugProcessListener(new MyDebugProcessListener(debugProcess)); + myDebugProcess.addEvaluationListener(new MyEvaluationListener()); + ValueLookupManager.getInstance(getProject()).startListening(); + mySearchScope = environment.getSearchScope(); + myAlternativeJre = environment.getAlternativeJre(); + myRunJre = environment.getRunJre(); + } + + @Nonnull + public DebuggerStateManager getContextManager() { + return myContextManager; + } + + public Project getProject() { + return getProcess().getProject(); + } + + public String getSessionName() { + return mySessionName; + } + + @Nonnull + public DebugProcessImpl getProcess() { + return myDebugProcess; + } + + private static class DebuggerSessionState { + final State myState; + final String myDescription; + + public DebuggerSessionState(State state, String description) { + myState = state; + myDescription = description; + } + } + + public State getState() { + return myState.myState; + } + + public String getStateDescription() { + if (myState.myDescription != null) { + return myState.myDescription; + } + + switch (myState.myState) { + case STOPPED: + return DebuggerBundle.message("status.debug.stopped"); + case RUNNING: + return DebuggerBundle.message("status.app.running"); + case WAITING_ATTACH: + RemoteConnection connection = getProcess().getConnection(); + final String addressDisplayName = DebuggerBundle.getAddressDisplayName(connection); + final String transportName = DebuggerBundle.getTransportName(connection); + return connection.isServerMode() + ? DebuggerBundle.message("status.listening", addressDisplayName, transportName) + : DebuggerBundle.message("status.connecting", addressDisplayName, transportName); + case PAUSED: + return DebuggerBundle.message("status.paused"); + case WAIT_EVALUATION: + return DebuggerBundle.message("status.waiting.evaluation.result"); + case DISPOSED: + return DebuggerBundle.message("status.debug.stopped"); + } + return null; + } + + /* Stepping */ + private void resumeAction(final DebugProcessImpl.ResumeCommand command, Event event) { + getContextManager().setState(SESSION_EMPTY_CONTEXT, State.WAIT_EVALUATION, event, null); + myDebugProcess.getManagerThread().schedule(command); + } + + @RequiredUIAccess + public void stepOut(int stepSize) { + SuspendContextImpl suspendContext = getSuspendContext(); + DebugProcessImpl.ResumeCommand cmd = null; + for (JvmSteppingCommandProvider handler : JvmSteppingCommandProvider.EP_NAME.getExtensions()) { + cmd = handler.getStepOutCommand(suspendContext, stepSize); + if (cmd != null) { + break; + } + } + if (cmd == null) { + cmd = myDebugProcess.createStepOutCommand(suspendContext, stepSize); + } + setSteppingThrough(cmd.getContextThread()); + resumeAction(cmd, Event.STEP); + } + + @RequiredUIAccess + public void stepOut() { + stepOut(StepRequest.STEP_LINE); + } + + @RequiredUIAccess + public void stepOver(boolean ignoreBreakpoints, int stepSize) { + SuspendContextImpl suspendContext = getSuspendContext(); + DebugProcessImpl.ResumeCommand cmd = null; + for (JvmSteppingCommandProvider handler : JvmSteppingCommandProvider.EP_NAME.getExtensions()) { + cmd = handler.getStepOverCommand(suspendContext, ignoreBreakpoints, stepSize); + if (cmd != null) { + break; + } + } + if (cmd == null) { + cmd = myDebugProcess.createStepOverCommand(suspendContext, ignoreBreakpoints, stepSize); + } + setSteppingThrough(cmd.getContextThread()); + resumeAction(cmd, Event.STEP); + } + + @RequiredUIAccess + public void stepOver(boolean ignoreBreakpoints) { + stepOver(ignoreBreakpoints, StepRequest.STEP_LINE); + } + + @RequiredUIAccess + public void stepInto(final boolean ignoreFilters, final @Nullable MethodFilter smartStepFilter, int stepSize) { + final SuspendContextImpl suspendContext = getSuspendContext(); + DebugProcessImpl.ResumeCommand cmd = null; + for (JvmSteppingCommandProvider handler : JvmSteppingCommandProvider.EP_NAME.getExtensions()) { + cmd = handler.getStepIntoCommand(suspendContext, ignoreFilters, smartStepFilter, stepSize); + if (cmd != null) { + break; + } + } + if (cmd == null) { + cmd = myDebugProcess.createStepIntoCommand(suspendContext, ignoreFilters, smartStepFilter, stepSize); + } + setSteppingThrough(cmd.getContextThread()); + resumeAction(cmd, Event.STEP); + } + + @RequiredUIAccess + public void stepInto(final boolean ignoreFilters, final @Nullable MethodFilter smartStepFilter) { + stepInto(ignoreFilters, smartStepFilter, StepRequest.STEP_LINE); + } + + @RequiredUIAccess + public void runToCursor(@Nonnull XSourcePosition position, final boolean ignoreBreakpoints) { + try { + DebugProcessImpl.ResumeCommand runToCursorCommand = + myDebugProcess.createRunToCursorCommand(getSuspendContext(), position, ignoreBreakpoints); + setSteppingThrough(runToCursorCommand.getContextThread()); + resumeAction(runToCursorCommand, Event.STEP); + } + catch (EvaluateException e) { + Messages.showErrorDialog(e.getMessage(), ActionLocalize.actionRuntocursorText().map(Presentation.NO_MNEMONIC).get()); + } + } + + @RequiredUIAccess + public void resume() { + final SuspendContextImpl suspendContext = getSuspendContext(); + if (suspendContext != null) { + clearSteppingThrough(); + resumeAction(myDebugProcess.createResumeCommand(suspendContext), Event.RESUME); + } + } + + public void resetIgnoreStepFiltersFlag() { + myIgnoreFiltersFrameCountThreshold = 0; + } + + public void setIgnoreStepFiltersFlag(int currentStackFrameCount) { + myIgnoreFiltersFrameCountThreshold = myIgnoreFiltersFrameCountThreshold <= 0 + ? currentStackFrameCount : Math.min(myIgnoreFiltersFrameCountThreshold, currentStackFrameCount); + } + + public boolean shouldIgnoreSteppingFilters() { + return myIgnoreFiltersFrameCountThreshold > 0; + } + + public void pause() { + myDebugProcess.getManagerThread().schedule(myDebugProcess.createPauseCommand()); + } + + /*Presentation*/ + + @RequiredUIAccess + public void showExecutionPoint() { + getContextManager() + .setState(DebuggerContextUtil.createDebuggerContext(this, getSuspendContext()), State.PAUSED, Event.REFRESH, null); + } + + @RequiredUIAccess + public void refresh(final boolean refreshWithStack) { + final State state = getState(); + DebuggerContextImpl context = myContextManager.getContext(); + DebuggerContextImpl newContext = + DebuggerContextImpl.createDebuggerContext(this, context.getSuspendContext(), context.getThreadProxy(), context.getFrameProxy()); + myContextManager.setState(newContext, state, refreshWithStack ? Event.REFRESH_WITH_STACK : Event.REFRESH, null); + } + + public void dispose() { + getProcess().dispose(); + Disposer.dispose(myUpdateAlarm); + DebuggerInvocationUtil.swingInvokeLater(getProject(), () -> + { + myContextManager.setState(SESSION_EMPTY_CONTEXT, State.DISPOSED, Event.DISPOSE, null); + myContextManager.dispose(); + }); + } + + // ManagerCommands + @Override + public boolean isStopped() { + return getState() == State.STOPPED; + } + + public boolean isAttached() { + return !isStopped() && getState() != State.WAITING_ATTACH; + } + + @Override + public boolean isPaused() { + return getState() == State.PAUSED; + } + + public boolean isConnecting() { + return getState() == State.WAITING_ATTACH; + } + + public boolean isEvaluating() { + return myIsEvaluating; + } + + public boolean isRunning() { + return getState() == State.RUNNING && !getProcess().getProcessHandler().isProcessTerminated(); + } + + @RequiredUIAccess + private SuspendContextImpl getSuspendContext() { + Application.get().assertIsDispatchThread(); + return getContextManager().getContext().getSuspendContext(); + } + + @Nullable + private ExecutionResult attach(DebugEnvironment environment) throws ExecutionException { + RemoteConnection remoteConnection = environment.getRemoteConnection(); + final String addressDisplayName = DebuggerBundle.getAddressDisplayName(remoteConnection); + final String transportName = DebuggerBundle.getTransportName(remoteConnection); + final ExecutionResult executionResult = myDebugProcess.attachVirtualMachine(environment, this); + getContextManager().setState( + SESSION_EMPTY_CONTEXT, + State.WAITING_ATTACH, + Event.START_WAIT_ATTACH, + DebuggerBundle.message("status.waiting.attach", addressDisplayName, transportName) + ); + return executionResult; + } + + private class MyDebugProcessListener extends DebugProcessAdapterImpl { + private final DebugProcessImpl myDebugProcess; + + public MyDebugProcessListener(final DebugProcessImpl debugProcess) { + myDebugProcess = debugProcess; + } + + //executed in manager thread + @Override + public void connectorIsReady() { + DebuggerInvocationUtil.invokeLater(getProject(), () -> + { + RemoteConnection connection = myDebugProcess.getConnection(); + final String addressDisplayName = DebuggerBundle.getAddressDisplayName(connection); + final String transportName = DebuggerBundle.getTransportName(connection); + final String connectionStatusMessage = connection.isServerMode() + ? DebuggerBundle.message("status.listening", addressDisplayName, transportName) + : DebuggerBundle.message("status.connecting", addressDisplayName, transportName); + getContextManager().setState(SESSION_EMPTY_CONTEXT, State.WAITING_ATTACH, Event.START_WAIT_ATTACH, connectionStatusMessage); + }); + } + + @Override + public void paused(final SuspendContextImpl suspendContext) { + LOG.debug("paused"); + + ThreadReferenceProxyImpl currentThread = suspendContext.getThread(); + + if (!shouldSetAsActiveContext(suspendContext)) { + DebuggerInvocationUtil.invokeLater( + getProject(), + () -> getContextManager().fireStateChanged(getContextManager().getContext(), Event.THREADS_REFRESH) + ); + ThreadReferenceProxyImpl thread = suspendContext.getThread(); + if (thread != null) { + List> descriptors = + DebuggerUtilsEx.getEventDescriptors(suspendContext); + if (!descriptors.isEmpty()) { + XDebuggerUIConstants.NOTIFICATION_GROUP.createNotification( + DebuggerBundle.message("status.breakpoint.reached.in.thread", thread.name()), + DebuggerBundle.message("status.breakpoint.reached.in.thread.switch"), + NotificationType.INFORMATION, + (notification, event) -> { + if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + notification.expire(); + getProcess().getManagerThread().schedule(new SuspendContextCommandImpl(suspendContext) { + @Override + public void contextAction() throws Exception { + final DebuggerContextImpl debuggerContext = + DebuggerContextUtil.createDebuggerContext(DebuggerSession.this, suspendContext); + + DebuggerInvocationUtil.invokeLater(getProject(), () -> getContextManager() + .setState(debuggerContext, State.PAUSED, Event.PAUSE, null)); + } + }); + } + } + ).notify(getProject()); + } + } + if (((SuspendManagerImpl)myDebugProcess.getSuspendManager()).getPausedContexts().size() > 1) { + return; + } + else { + currentThread = mySteppingThroughThread.get(); + } + } + else { + setSteppingThrough(currentThread); + } + + final StackFrameContext positionContext; + + if (currentThread == null) { + //Pause pressed + LOG.assertTrue(suspendContext.getSuspendPolicy() == EventRequest.SUSPEND_ALL); + SuspendContextImpl oldContext = getProcess().getSuspendManager().getPausedContext(); + + if (oldContext != null) { + currentThread = oldContext.getThread(); + } + + if (currentThread == null) { + final Collection allThreads = getProcess().getVirtualMachineProxy().allThreads(); + // heuristics: try to pre-select EventDispatchThread + for (final ThreadReferenceProxyImpl thread : allThreads) { + if (ThreadState.isEDT(thread.name())) { + currentThread = thread; + break; + } + } + if (currentThread == null) { + // heuristics: display the first thread with RUNNABLE status + for (final ThreadReferenceProxyImpl thread : allThreads) { + currentThread = thread; + if (currentThread.status() == ThreadReference.THREAD_STATUS_RUNNING) { + break; + } + } + } + } + + StackFrameProxyImpl proxy = null; + if (currentThread != null) { + try { + while (!currentThread.isSuspended()) { + // wait until thread is considered suspended. Querying data from a thread immediately after VM.suspend() + // may result in IncompatibleThreadStateException, most likely some time after suspend() VM erroneously thinks that thread is still running + TimeoutUtil.sleep(10); + } + proxy = (currentThread.frameCount() > 0) ? currentThread.frame(0) : null; + } + catch (ObjectCollectedException ignored) { + proxy = null; + } + catch (EvaluateException e) { + proxy = null; + LOG.error(e); + } + } + positionContext = new SimpleStackFrameContext(proxy, myDebugProcess); + } + else { + positionContext = suspendContext; + } + + if (currentThread != null) { + try { + final int frameCount = currentThread.frameCount(); + if (frameCount == 0 || (frameCount <= myIgnoreFiltersFrameCountThreshold)) { + resetIgnoreStepFiltersFlag(); + } + } + catch (EvaluateException e) { + LOG.info(e); + resetIgnoreStepFiltersFlag(); + } + } + + SourcePosition position = ContextUtil.getSourcePosition(positionContext); + + if (position != null) { + final List> eventDescriptors = + DebuggerUtilsEx.getEventDescriptors(suspendContext); + final RequestManagerImpl requestsManager = suspendContext.getDebugProcess().getRequestsManager(); + final PsiFile foundFile = position.getFile(); + final boolean sourceMissing = foundFile instanceof PsiCompiledElement; + for (Pair eventDescriptor : eventDescriptors) { + Breakpoint breakpoint = eventDescriptor.getFirst(); + if (breakpoint instanceof LineBreakpoint) { + final SourcePosition breakpointPosition = ((BreakpointWithHighlighter)breakpoint).getSourcePosition(); + if (breakpointPosition == null || (!sourceMissing && breakpointPosition.getLine() != position.getLine())) { + requestsManager.deleteRequest(breakpoint); + requestsManager.setInvalid(breakpoint, DebuggerBundle.message("error.invalid.breakpoint.source.changed")); + breakpoint.updateUI(); + } + else if (sourceMissing) { + // adjust position to be position of the breakpoint in order to show the real originator of the event + position = breakpointPosition; + final StackFrameProxy frameProxy = positionContext.getFrameProxy(); + String className; + try { + className = frameProxy != null ? frameProxy.location().declaringType().name() : ""; + } + catch (EvaluateException ignored) { + className = ""; + } + requestsManager.setInvalid( + breakpoint, + DebuggerBundle.message("error.invalid.breakpoint.source.not.found", className) + ); + breakpoint.updateUI(); + } + } + } + } + + final DebuggerContextImpl debuggerContext = + DebuggerContextImpl.createDebuggerContext(DebuggerSession.this, suspendContext, currentThread, null); + if (suspendContext.getThread() == currentThread) { + debuggerContext.setPositionCache(position); + } + + DebuggerInvocationUtil.invokeLater( + getProject(), + () -> getContextManager().setState(debuggerContext, State.PAUSED, Event.PAUSE, getDescription(debuggerContext)) + ); + } + + private boolean shouldSetAsActiveContext(final SuspendContextImpl suspendContext) { + final ThreadReferenceProxyImpl newThread = suspendContext.getThread(); + if (newThread == null || suspendContext.getSuspendPolicy() == EventRequest.SUSPEND_ALL || isSteppingThrough(newThread)) { + return true; + } + final SuspendContextImpl currentSuspendContext = getContextManager().getContext().getSuspendContext(); + if (currentSuspendContext == null) { + return mySteppingThroughThread.get() == null; + } + if (enableBreakpointsDuringEvaluation()) { + final ThreadReferenceProxyImpl currentThread = currentSuspendContext.getThread(); + return currentThread == null || Comparing.equal(currentThread.getThreadReference(), newThread.getThreadReference()); + } + return false; + } + + + @Override + public void resumed(SuspendContextImpl suspendContext) { + SuspendContextImpl context = getProcess().getSuspendManager().getPausedContext(); + ThreadReferenceProxyImpl steppingThread = null; + // single thread stepping + if (context != null && suspendContext != null && suspendContext.getSuspendPolicy() == EventRequest.SUSPEND_EVENT_THREAD && isSteppingThrough( + suspendContext.getThread())) { + steppingThread = suspendContext.getThread(); + } + final DebuggerContextImpl debuggerContext = context != null + ? DebuggerContextImpl.createDebuggerContext( + DebuggerSession.this, + context, + steppingThread != null ? steppingThread : context.getThread(), + null + ) + : null; + + DebuggerInvocationUtil.invokeLater( + getProject(), + () -> { + if (debuggerContext != null) { + getContextManager().setState(debuggerContext, State.PAUSED, Event.CONTEXT, getDescription(debuggerContext)); + } + else { + getContextManager().setState(SESSION_EMPTY_CONTEXT, State.RUNNING, Event.CONTEXT, null); + } + } + ); + } + + @Override + public void processAttached(final DebugProcessImpl process) { + final RemoteConnection connection = getProcess().getConnection(); + final String addressDisplayName = DebuggerBundle.getAddressDisplayName(connection); + final String transportName = DebuggerBundle.getTransportName(connection); + final String message = DebuggerBundle.message("status.connected", addressDisplayName, transportName); + + process.printToConsole(message + "\n"); + DebuggerInvocationUtil.invokeLater( + getProject(), + () -> getContextManager().setState(SESSION_EMPTY_CONTEXT, State.RUNNING, Event.ATTACHED, message) + ); + } + + @Override + public void attachException(final RunProfileState state, final ExecutionException exception, final RemoteConnection remoteConnection) { + DebuggerInvocationUtil.invokeLater( + getProject(), + () -> { + String message = ""; + if (state instanceof RemoteState) { + message = DebuggerBundle.message( + "status.connect.failed", + DebuggerBundle.getAddressDisplayName(remoteConnection), + DebuggerBundle.getTransportName(remoteConnection) + ); + } + message += exception.getMessage(); + getContextManager().setState(SESSION_EMPTY_CONTEXT, State.STOPPED, Event.DETACHED, message); + } + ); + } + + @Override + public void processDetached(final DebugProcessImpl debugProcess, boolean closedByUser) { + if (!closedByUser) { + ProcessHandler processHandler = debugProcess.getProcessHandler(); + if (processHandler != null) { + final RemoteConnection connection = getProcess().getConnection(); + final String addressDisplayName = DebuggerBundle.getAddressDisplayName(connection); + final String transportName = DebuggerBundle.getTransportName(connection); + processHandler.notifyTextAvailable( + DebuggerBundle.message("status.disconnected", addressDisplayName, transportName) + "\n", + ProcessOutputTypes.SYSTEM + ); + } + } + DebuggerInvocationUtil.invokeLater(getProject(), () -> + { + final RemoteConnection connection = getProcess().getConnection(); + final String addressDisplayName = DebuggerBundle.getAddressDisplayName(connection); + final String transportName = DebuggerBundle.getTransportName(connection); + getContextManager().setState( + SESSION_EMPTY_CONTEXT, + State.STOPPED, + Event.DETACHED, + DebuggerBundle.message("status.disconnected", addressDisplayName, transportName) + ); + }); + clearSteppingThrough(); + } + + @Override + public void threadStarted(DebugProcess proc, ThreadReference thread) { + notifyThreadsRefresh(); + } + + @Override + public void threadStopped(DebugProcess proc, ThreadReference thread) { + notifyThreadsRefresh(); + ThreadReferenceProxyImpl steppingThread = mySteppingThroughThread.get(); + if (steppingThread != null && steppingThread.getThreadReference() == thread) { + clearSteppingThrough(); + } + DebugProcessImpl debugProcess = (DebugProcessImpl)proc; + if (debugProcess.getRequestsManager().getFilterThread() == thread) { + DebuggerManagerEx.getInstanceEx(proc.getProject()).getBreakpointManager().applyThreadFilter(debugProcess, null); + } + } + + private void notifyThreadsRefresh() { + if (!myUpdateAlarm.isDisposed()) { + myUpdateAlarm.cancelAllRequests(); + myUpdateAlarm.addRequest(() -> + { + final DebuggerStateManager contextManager = getContextManager(); + contextManager.fireStateChanged(contextManager.getContext(), Event.THREADS_REFRESH); + }, 100, Application.get().getNoneModalityState()); + } + } + } + + private static String getDescription(DebuggerContextImpl debuggerContext) { + SuspendContextImpl suspendContext = debuggerContext.getSuspendContext(); + if (suspendContext != null && debuggerContext.getThreadProxy() != suspendContext.getThread()) { + return DebuggerBundle.message("status.paused.in.another.thread"); + } + return null; + } + + private class MyEvaluationListener implements EvaluationListener { + @Override + public void evaluationStarted(SuspendContextImpl context) { + myIsEvaluating = true; + } + + @Override + public void evaluationFinished(final SuspendContextImpl context) { + myIsEvaluating = false; + //seems to be not required after move to xdebugger + //DebuggerInvocationUtil.invokeLater( + // getProject(), + // new Runnable() { + // @Override + // public void run() { + // if (context != getSuspendContext()) { + // getContextManager().setState(DebuggerContextUtil.createDebuggerContext(DebuggerSession.this, context), STATE_PAUSED, REFRESH, null); + // } + // } + // } + //); + } + } + + public static boolean enableBreakpointsDuringEvaluation() { + return false; + } + + public void sessionResumed() { + XDebugSession session = getXDebugSession(); + if (session != null) { + session.sessionResumed(); + } + } + + @Nullable + public XDebugSession getXDebugSession() { + JavaDebugProcess process = myDebugProcess.getXdebugProcess(); + return process != null ? process.getSession() : null; + } } diff --git a/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/JvmSteppingCommandProvider.java b/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/JvmSteppingCommandProvider.java index bad309bee6..c6a41e772f 100644 --- a/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/JvmSteppingCommandProvider.java +++ b/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/JvmSteppingCommandProvider.java @@ -27,30 +27,32 @@ */ @ExtensionAPI(ComponentScope.APPLICATION) public abstract class JvmSteppingCommandProvider { - public static final ExtensionPointName EP_NAME = - ExtensionPointName.create(JvmSteppingCommandProvider.class); + public static final ExtensionPointName EP_NAME = + ExtensionPointName.create(JvmSteppingCommandProvider.class); - /** - * @return null if can not handle - */ - public DebugProcessImpl.ResumeCommand getStepIntoCommand(SuspendContextImpl suspendContext, - boolean ignoreFilters, - final MethodFilter smartStepFilter, - int stepSize) { - return null; - } + /** + * @return null if can not handle + */ + public DebugProcessImpl.ResumeCommand getStepIntoCommand( + SuspendContextImpl suspendContext, + boolean ignoreFilters, + final MethodFilter smartStepFilter, + int stepSize + ) { + return null; + } - /** - * @return null if can not handle - */ - public DebugProcessImpl.ResumeCommand getStepOutCommand(SuspendContextImpl suspendContext, int stepSize) { - return null; - } + /** + * @return null if can not handle + */ + public DebugProcessImpl.ResumeCommand getStepOutCommand(SuspendContextImpl suspendContext, int stepSize) { + return null; + } - /** - * @return null if can not handle - */ - public DebugProcessImpl.ResumeCommand getStepOverCommand(SuspendContextImpl suspendContext, boolean ignoreBreakpoints, int stepSize) { - return null; - } + /** + * @return null if can not handle + */ + public DebugProcessImpl.ResumeCommand getStepOverCommand(SuspendContextImpl suspendContext, boolean ignoreBreakpoints, int stepSize) { + return null; + } } diff --git a/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/engine/DebugProcessImpl.java b/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/engine/DebugProcessImpl.java index 361739ad26..061dd4e5dc 100644 --- a/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/engine/DebugProcessImpl.java +++ b/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/engine/DebugProcessImpl.java @@ -114,1870 +114,1876 @@ import java.util.stream.Collectors; public abstract class DebugProcessImpl extends UserDataHolderBase implements DebugProcess { - private static final Logger LOG = Logger.getInstance(DebugProcessImpl.class); - - @NonNls - private static final String SOCKET_ATTACHING_CONNECTOR_NAME = "consulo.internal.com.sun.jdi.SocketAttach"; - @NonNls - private static final String SHMEM_ATTACHING_CONNECTOR_NAME = "consulo.internal.com.sun.jdi.SharedMemoryAttach"; - @NonNls - private static final String SOCKET_LISTENING_CONNECTOR_NAME = "consulo.internal.com.sun.jdi.SocketListen"; - @NonNls - private static final String SHMEM_LISTENING_CONNECTOR_NAME = "consulo.internal.com.sun.jdi.SharedMemoryListen"; - - private final Project myProject; - private final RequestManagerImpl myRequestManager; - - private volatile VirtualMachineProxyImpl myVirtualMachineProxy = null; - protected final EventDispatcher myDebugProcessDispatcher = EventDispatcher.create(DebugProcessListener.class); - protected final EventDispatcher myEvaluationDispatcher = EventDispatcher.create(EvaluationListener.class); - - private final List myProcessListeners = Lists.newLockFreeCopyOnWriteList(); - - enum State { - INITIAL, - ATTACHED, - DETACHING, - DETACHED - } - - protected final AtomicReference myState = new AtomicReference<>(State.INITIAL); - - private volatile ExecutionResult myExecutionResult; - private RemoteConnection myConnection; - private JavaDebugProcess myXDebugProcess; - - private volatile Map myArguments; - - private final List myRenderers = new ArrayList<>(); - - // we use null key here - private final Map myNodeRenderersMap = new HashMap<>(); - - private final SuspendManagerImpl mySuspendManager = new SuspendManagerImpl(this); - protected CompoundPositionManager myPositionManager = CompoundPositionManager.EMPTY; - private final DebuggerManagerThreadImpl myDebuggerManagerThread; - - private final Semaphore myWaitFor = new Semaphore(); - private final AtomicBoolean myIsFailed = new AtomicBoolean(false); - protected DebuggerSession mySession; - @Nullable - protected MethodReturnValueWatcher myReturnValueWatcher; - protected final Disposable myDisposable = Disposable.newDisposable(); - private final Alarm myStatusUpdateAlarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, myDisposable); - - private final ThreadBlockedMonitor myThreadBlockedMonitor = new ThreadBlockedMonitor(this, myDisposable); - - protected DebugProcessImpl(Project project) { - myProject = project; - myDebuggerManagerThread = new DebuggerManagerThreadImpl(myDisposable, myProject); - myRequestManager = new RequestManagerImpl(this); - NodeRendererSettings.getInstance().addListener(this::reloadRenderers, myDisposable); - reloadRenderers(); - myDebugProcessDispatcher.addListener(new DebugProcessListener() { - @Override - public void paused(SuspendContext suspendContext) { - myThreadBlockedMonitor.stopWatching( - suspendContext.getSuspendPolicy() != EventRequest.SUSPEND_ALL ? suspendContext.getThread() : null - ); - } - }); - } + private static final Logger LOG = Logger.getInstance(DebugProcessImpl.class); - private void reloadRenderers() { - getManagerThread().invoke(new DebuggerCommandImpl() { - @Override - public Priority getPriority() { - return Priority.HIGH; - } + @NonNls + private static final String SOCKET_ATTACHING_CONNECTOR_NAME = "consulo.internal.com.sun.jdi.SocketAttach"; + @NonNls + private static final String SHMEM_ATTACHING_CONNECTOR_NAME = "consulo.internal.com.sun.jdi.SharedMemoryAttach"; + @NonNls + private static final String SOCKET_LISTENING_CONNECTOR_NAME = "consulo.internal.com.sun.jdi.SocketListen"; + @NonNls + private static final String SHMEM_LISTENING_CONNECTOR_NAME = "consulo.internal.com.sun.jdi.SharedMemoryListen"; - @Override - protected void action() throws Exception { - myNodeRenderersMap.clear(); - myRenderers.clear(); - try { - NodeRendererSettings.getInstance().getAllRenderers().stream().filter(NodeRenderer::isEnabled).forEachOrdered(myRenderers::add); - } - finally { - DebuggerInvocationUtil.swingInvokeLater( - myProject, - () -> - { - final DebuggerSession session = mySession; - if (session != null && session.isAttached()) { - DebuggerAction.refreshViews(mySession.getXDebugSession()); - } - } - ); - } - } - }); - } + private final Project myProject; + private final RequestManagerImpl myRequestManager; - @Nullable - public Pair getLastExecutedMethod() { - final MethodReturnValueWatcher watcher = myReturnValueWatcher; - if (watcher == null) { - return null; - } - final Method method = watcher.getLastExecutedMethod(); - if (method == null) { - return null; - } - return Pair.create(method, watcher.getLastMethodReturnValue()); - } + private volatile VirtualMachineProxyImpl myVirtualMachineProxy = null; + protected final EventDispatcher myDebugProcessDispatcher = EventDispatcher.create(DebugProcessListener.class); + protected final EventDispatcher myEvaluationDispatcher = EventDispatcher.create(EvaluationListener.class); + + private final List myProcessListeners = Lists.newLockFreeCopyOnWriteList(); - public void setWatchMethodReturnValuesEnabled(boolean enabled) { - final MethodReturnValueWatcher watcher = myReturnValueWatcher; - if (watcher != null) { - watcher.setFeatureEnabled(enabled); + enum State { + INITIAL, + ATTACHED, + DETACHING, + DETACHED } - } - public boolean isWatchMethodReturnValuesEnabled() { - final MethodReturnValueWatcher watcher = myReturnValueWatcher; - return watcher != null && watcher.isFeatureEnabled(); - } + protected final AtomicReference myState = new AtomicReference<>(State.INITIAL); - public boolean canGetMethodReturnValue() { - return myReturnValueWatcher != null; - } + private volatile ExecutionResult myExecutionResult; + private RemoteConnection myConnection; + private JavaDebugProcess myXDebugProcess; - public NodeRenderer getAutoRenderer(ValueDescriptor descriptor) { - DebuggerManagerThreadImpl.assertIsManagerThread(); - Type type = descriptor.getType(); + private volatile Map myArguments; - // in case evaluation is not possible, force default renderer - if (!DebuggerManagerEx.getInstanceEx(getProject()).getContext().isEvaluationPossible()) { - return getDefaultRenderer(type); - } + private final List myRenderers = new ArrayList<>(); - try { - return myNodeRenderersMap.computeIfAbsent( - type, - t -> myRenderers.stream() - .filter(r -> DebuggerUtilsImpl.suppressExceptions( - () -> r.isApplicable(type), - false, - true, - ClassNotPreparedException.class - )) - .findFirst() - .orElseGet(() -> getDefaultRenderer(type))); - } - catch (ClassNotPreparedException e) { - LOG.info(e); - // use default, but do not cache - return getDefaultRenderer(type); - } - } + // we use null key here + private final Map myNodeRenderersMap = new HashMap<>(); + + private final SuspendManagerImpl mySuspendManager = new SuspendManagerImpl(this); + protected CompoundPositionManager myPositionManager = CompoundPositionManager.EMPTY; + private final DebuggerManagerThreadImpl myDebuggerManagerThread; - @Nonnull - public static NodeRenderer getDefaultRenderer(Value value) { - return getDefaultRenderer(value != null ? value.type() : null); - } + private final Semaphore myWaitFor = new Semaphore(); + private final AtomicBoolean myIsFailed = new AtomicBoolean(false); + protected DebuggerSession mySession; + @Nullable + protected MethodReturnValueWatcher myReturnValueWatcher; + protected final Disposable myDisposable = Disposable.newDisposable(); + private final Alarm myStatusUpdateAlarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, myDisposable); + + private final ThreadBlockedMonitor myThreadBlockedMonitor = new ThreadBlockedMonitor(this, myDisposable); + + protected DebugProcessImpl(Project project) { + myProject = project; + myDebuggerManagerThread = new DebuggerManagerThreadImpl(myDisposable, myProject); + myRequestManager = new RequestManagerImpl(this); + NodeRendererSettings.getInstance().addListener(this::reloadRenderers, myDisposable); + reloadRenderers(); + myDebugProcessDispatcher.addListener(new DebugProcessListener() { + @Override + public void paused(SuspendContext suspendContext) { + myThreadBlockedMonitor.stopWatching( + suspendContext.getSuspendPolicy() != EventRequest.SUSPEND_ALL ? suspendContext.getThread() : null + ); + } + }); + } - @Nonnull - public static NodeRenderer getDefaultRenderer(Type type) { - final NodeRendererSettings settings = NodeRendererSettings.getInstance(); + private void reloadRenderers() { + getManagerThread().invoke(new DebuggerCommandImpl() { + @Override + public Priority getPriority() { + return Priority.HIGH; + } - final PrimitiveRenderer primitiveRenderer = settings.getPrimitiveRenderer(); - if (primitiveRenderer.isApplicable(type)) { - return primitiveRenderer; + @Override + protected void action() throws Exception { + myNodeRenderersMap.clear(); + myRenderers.clear(); + try { + NodeRendererSettings.getInstance() + .getAllRenderers() + .stream() + .filter(NodeRenderer::isEnabled) + .forEachOrdered(myRenderers::add); + } + finally { + DebuggerInvocationUtil.swingInvokeLater( + myProject, + () -> + { + final DebuggerSession session = mySession; + if (session != null && session.isAttached()) { + DebuggerAction.refreshViews(mySession.getXDebugSession()); + } + } + ); + } + } + }); } - final ArrayRenderer arrayRenderer = settings.getArrayRenderer(); - if (arrayRenderer.isApplicable(type)) { - return arrayRenderer; + @Nullable + public Pair getLastExecutedMethod() { + final MethodReturnValueWatcher watcher = myReturnValueWatcher; + if (watcher == null) { + return null; + } + final Method method = watcher.getLastExecutedMethod(); + if (method == null) { + return null; + } + return Pair.create(method, watcher.getLastMethodReturnValue()); } - final ClassRenderer classRenderer = settings.getClassRenderer(); - LOG.assertTrue(classRenderer.isApplicable(type), type.name()); - return classRenderer; - } + public void setWatchMethodReturnValuesEnabled(boolean enabled) { + final MethodReturnValueWatcher watcher = myReturnValueWatcher; + if (watcher != null) { + watcher.setFeatureEnabled(enabled); + } + } - private static final String ourTrace = Platform.current().jvm().getRuntimeProperty("idea.debugger.trace"); + public boolean isWatchMethodReturnValuesEnabled() { + final MethodReturnValueWatcher watcher = myReturnValueWatcher; + return watcher != null && watcher.isFeatureEnabled(); + } - @SuppressWarnings({"HardCodedStringLiteral"}) - protected void commitVM(VirtualMachine vm) { - if (!isInInitialState()) { - LOG.error("State is invalid " + myState.get()); + public boolean canGetMethodReturnValue() { + return myReturnValueWatcher != null; } - DebuggerManagerThreadImpl.assertIsManagerThread(); - myPositionManager = new CompoundPositionManager(new PositionManagerImpl(this)); - LOG.debug("*******************VM attached******************"); - checkVirtualMachineVersion(vm); - myVirtualMachineProxy = new VirtualMachineProxyImpl(this, vm); + public NodeRenderer getAutoRenderer(ValueDescriptor descriptor) { + DebuggerManagerThreadImpl.assertIsManagerThread(); + Type type = descriptor.getType(); - if (!StringUtil.isEmpty(ourTrace)) { - int mask = 0; - StringTokenizer tokenizer = new StringTokenizer(ourTrace); - while (tokenizer.hasMoreTokens()) { - String token = tokenizer.nextToken(); - if ("SENDS".compareToIgnoreCase(token) == 0) { - mask |= VirtualMachine.TRACE_SENDS; - } - else if ("RAW_SENDS".compareToIgnoreCase(token) == 0) { - mask |= 0x01000000; - } - else if ("RECEIVES".compareToIgnoreCase(token) == 0) { - mask |= VirtualMachine.TRACE_RECEIVES; - } - else if ("RAW_RECEIVES".compareToIgnoreCase(token) == 0) { - mask |= 0x02000000; + // in case evaluation is not possible, force default renderer + if (!DebuggerManagerEx.getInstanceEx(getProject()).getContext().isEvaluationPossible()) { + return getDefaultRenderer(type); } - else if ("EVENTS".compareToIgnoreCase(token) == 0) { - mask |= VirtualMachine.TRACE_EVENTS; - } - else if ("REFTYPES".compareToIgnoreCase(token) == 0) { - mask |= VirtualMachine.TRACE_REFTYPES; - } - else if ("OBJREFS".compareToIgnoreCase(token) == 0) { - mask |= VirtualMachine.TRACE_OBJREFS; - } - else if ("ALL".compareToIgnoreCase(token) == 0) { - mask |= VirtualMachine.TRACE_ALL; + + try { + return myNodeRenderersMap.computeIfAbsent( + type, + t -> myRenderers.stream() + .filter(r -> DebuggerUtilsImpl.suppressExceptions( + () -> r.isApplicable(type), + false, + true, + ClassNotPreparedException.class + )) + .findFirst() + .orElseGet(() -> getDefaultRenderer(type)) + ); + } + catch (ClassNotPreparedException e) { + LOG.info(e); + // use default, but do not cache + return getDefaultRenderer(type); } - } + } - vm.setDebugTraceMode(mask); + @Nonnull + public static NodeRenderer getDefaultRenderer(Value value) { + return getDefaultRenderer(value != null ? value.type() : null); } - } - private void stopConnecting() { - Map arguments = myArguments; - try { - if (arguments == null) { - return; - } - if (myConnection.isServerMode()) { - ListeningConnector connector = (ListeningConnector)findConnector(SOCKET_LISTENING_CONNECTOR_NAME); - if (connector == null) { - LOG.error("Cannot find connector: " + SOCKET_LISTENING_CONNECTOR_NAME); + @Nonnull + public static NodeRenderer getDefaultRenderer(Type type) { + final NodeRendererSettings settings = NodeRendererSettings.getInstance(); + + final PrimitiveRenderer primitiveRenderer = settings.getPrimitiveRenderer(); + if (primitiveRenderer.isApplicable(type)) { + return primitiveRenderer; } - else { - connector.stopListening(arguments); + + final ArrayRenderer arrayRenderer = settings.getArrayRenderer(); + if (arrayRenderer.isApplicable(type)) { + return arrayRenderer; } - } + + final ClassRenderer classRenderer = settings.getClassRenderer(); + LOG.assertTrue(classRenderer.isApplicable(type), type.name()); + return classRenderer; } - catch (IOException | IllegalConnectorArgumentsException e) { - LOG.debug(e); - } - catch (ExecutionException e) { - LOG.error(e); - } - } - - @Override - public void printToConsole(final String text) { - myExecutionResult.getProcessHandler().notifyTextAvailable(text, ProcessOutputTypes.SYSTEM); - } - - @Override - public ProcessHandler getProcessHandler() { - return myExecutionResult != null ? myExecutionResult.getProcessHandler() : null; - } - - /** - * @param suspendContext - * @param stepThread - * @param size the step size. One of {@link StepRequest#STEP_LINE} or {@link StepRequest#STEP_MIN} - * @param depth - * @param hint may be null - */ - protected void doStep( - final SuspendContextImpl suspendContext, - final ThreadReferenceProxyImpl stepThread, - int size, - int depth, - RequestHint hint - ) { - if (stepThread == null) { - return; - } - try { - final ThreadReference stepThreadReference = stepThread.getThreadReference(); - if (LOG.isDebugEnabled()) { - LOG.debug("DO_STEP: creating step request for " + stepThreadReference); - } - deleteStepRequests(stepThreadReference); - EventRequestManager requestManager = getVirtualMachineProxy().eventRequestManager(); - StepRequest stepRequest = requestManager.createStepRequest(stepThreadReference, size, depth); - if (!(hint != null && hint.isIgnoreFilters()) /*&& depth == StepRequest.STEP_INTO*/) { - checkPositionNotFiltered(stepThread, filters -> filters.forEach(f -> stepRequest.addClassExclusionFilter(f.getPattern()))); - } - // suspend policy to match the suspend policy of the context: - // if all threads were suspended, then during stepping all the threads must be suspended - // if only event thread were suspended, then only this particular thread must be suspended during stepping - stepRequest.setSuspendPolicy( - suspendContext.getSuspendPolicy() == EventRequest.SUSPEND_EVENT_THREAD - ? EventRequest.SUSPEND_EVENT_THREAD : EventRequest.SUSPEND_ALL - ); + private static final String ourTrace = Platform.current().jvm().getRuntimeProperty("idea.debugger.trace"); - stepRequest.addCountFilter(1); + @SuppressWarnings({"HardCodedStringLiteral"}) + protected void commitVM(VirtualMachine vm) { + if (!isInInitialState()) { + LOG.error("State is invalid " + myState.get()); + } + DebuggerManagerThreadImpl.assertIsManagerThread(); + myPositionManager = new CompoundPositionManager(new PositionManagerImpl(this)); + LOG.debug("*******************VM attached******************"); + checkVirtualMachineVersion(vm); - if (hint != null) { - //noinspection HardCodedStringLiteral - stepRequest.putProperty("hint", hint); - } - stepRequest.enable(); - } - catch (ObjectCollectedException ignored) { - } - } + myVirtualMachineProxy = new VirtualMachineProxyImpl(this, vm); - public void checkPositionNotFiltered(ThreadReferenceProxyImpl thread, Consumer> action) { - List activeFilters = getActiveFilters(); - if (!activeFilters.isEmpty()) { - String currentClassName = getCurrentClassName(thread); - if (currentClassName == null || !DebuggerUtilsEx.isFiltered(currentClassName, activeFilters)) { - action.accept(activeFilters); - } + if (!StringUtil.isEmpty(ourTrace)) { + int mask = 0; + StringTokenizer tokenizer = new StringTokenizer(ourTrace); + while (tokenizer.hasMoreTokens()) { + String token = tokenizer.nextToken(); + if ("SENDS".compareToIgnoreCase(token) == 0) { + mask |= VirtualMachine.TRACE_SENDS; + } + else if ("RAW_SENDS".compareToIgnoreCase(token) == 0) { + mask |= 0x01000000; + } + else if ("RECEIVES".compareToIgnoreCase(token) == 0) { + mask |= VirtualMachine.TRACE_RECEIVES; + } + else if ("RAW_RECEIVES".compareToIgnoreCase(token) == 0) { + mask |= 0x02000000; + } + else if ("EVENTS".compareToIgnoreCase(token) == 0) { + mask |= VirtualMachine.TRACE_EVENTS; + } + else if ("REFTYPES".compareToIgnoreCase(token) == 0) { + mask |= VirtualMachine.TRACE_REFTYPES; + } + else if ("OBJREFS".compareToIgnoreCase(token) == 0) { + mask |= VirtualMachine.TRACE_OBJREFS; + } + else if ("ALL".compareToIgnoreCase(token) == 0) { + mask |= VirtualMachine.TRACE_ALL; + } + } + + vm.setDebugTraceMode(mask); + } } - } - - @Nonnull - private static List getActiveFilters() { - DebuggerSettings settings = DebuggerSettings.getInstance(); - List classFilters = new ArrayList<>(); - for (DebuggerClassFilterProvider provider : DebuggerClassFilterProvider.EP_NAME.getExtensionList()) { - classFilters.addAll(provider.getFilters()); - } - if (settings.TRACING_FILTERS_ENABLED) { - ContainerUtil.prepend(classFilters, settings.getSteppingFilters()); - } - return classFilters.stream().filter(ClassFilter::isEnabled).collect(Collectors.toList()); - } - - void deleteStepRequests(@Nullable final ThreadReference stepThread) { - EventRequestManager requestManager = getVirtualMachineProxy().eventRequestManager(); - List stepRequests = requestManager.stepRequests(); - if (!stepRequests.isEmpty()) { - final List toDelete = new ArrayList<>(stepRequests.size()); - for (final StepRequest request : stepRequests) { - ThreadReference threadReference = request.thread(); - // [jeka] on attempt to delete a request assigned to a thread with unknown status, a JDWP error occurs + + private void stopConnecting() { + Map arguments = myArguments; try { - if (threadReference.status() != ThreadReference.THREAD_STATUS_UNKNOWN - && (stepThread == null || stepThread.equals(threadReference))) { - toDelete.add(request); - } - } - catch (IllegalThreadStateException e) { - LOG.info(e); // undocumented by JDI: may be thrown when querying thread status + if (arguments == null) { + return; + } + if (myConnection.isServerMode()) { + ListeningConnector connector = (ListeningConnector)findConnector(SOCKET_LISTENING_CONNECTOR_NAME); + if (connector == null) { + LOG.error("Cannot find connector: " + SOCKET_LISTENING_CONNECTOR_NAME); + } + else { + connector.stopListening(arguments); + } + } } - catch (ObjectCollectedException ignored) { + catch (IOException | IllegalConnectorArgumentsException e) { + LOG.debug(e); } - } - requestManager.deleteEventRequests(toDelete); - } - } - - @Nullable - static String getCurrentClassName(ThreadReferenceProxyImpl thread) { - try { - if (thread != null && thread.frameCount() > 0) { - StackFrameProxyImpl stackFrame = thread.frame(0); - if (stackFrame != null) { - Location location = stackFrame.location(); - ReferenceType referenceType = location == null ? null : location.declaringType(); - if (referenceType != null) { - return referenceType.name(); - } + catch (ExecutionException e) { + LOG.error(e); } - } } - catch (EvaluateException ignored) { + + @Override + public void printToConsole(final String text) { + myExecutionResult.getProcessHandler().notifyTextAvailable(text, ProcessOutputTypes.SYSTEM); } - return null; - } - private VirtualMachine createVirtualMachineInt() throws ExecutionException { - try { - if (myArguments != null) { - throw new IOException(DebuggerBundle.message("error.debugger.already.listening")); - } + @Override + public ProcessHandler getProcessHandler() { + return myExecutionResult != null ? myExecutionResult.getProcessHandler() : null; + } + + /** + * @param suspendContext + * @param stepThread + * @param size the step size. One of {@link StepRequest#STEP_LINE} or {@link StepRequest#STEP_MIN} + * @param depth + * @param hint may be null + */ + protected void doStep( + final SuspendContextImpl suspendContext, + final ThreadReferenceProxyImpl stepThread, + int size, + int depth, + RequestHint hint + ) { + if (stepThread == null) { + return; + } + try { + final ThreadReference stepThreadReference = stepThread.getThreadReference(); + if (LOG.isDebugEnabled()) { + LOG.debug("DO_STEP: creating step request for " + stepThreadReference); + } + deleteStepRequests(stepThreadReference); + EventRequestManager requestManager = getVirtualMachineProxy().eventRequestManager(); + StepRequest stepRequest = requestManager.createStepRequest(stepThreadReference, size, depth); + if (!(hint != null && hint.isIgnoreFilters()) /*&& depth == StepRequest.STEP_INTO*/) { + checkPositionNotFiltered(stepThread, filters -> filters.forEach(f -> stepRequest.addClassExclusionFilter(f.getPattern()))); + } + + // suspend policy to match the suspend policy of the context: + // if all threads were suspended, then during stepping all the threads must be suspended + // if only event thread were suspended, then only this particular thread must be suspended during stepping + stepRequest.setSuspendPolicy( + suspendContext.getSuspendPolicy() == EventRequest.SUSPEND_EVENT_THREAD + ? EventRequest.SUSPEND_EVENT_THREAD : EventRequest.SUSPEND_ALL + ); - final String address = myConnection.getAddress(); - if (myConnection.isServerMode()) { - ListeningConnector connector = - (ListeningConnector)findConnector(myConnection.isUseSockets() ? SOCKET_LISTENING_CONNECTOR_NAME : SHMEM_LISTENING_CONNECTOR_NAME); - if (connector == null) { - throw new CantRunException(DebuggerBundle.message( - "error.debug.connector.not.found", - DebuggerBundle.getTransportName(myConnection) - )); + stepRequest.addCountFilter(1); + + if (hint != null) { + //noinspection HardCodedStringLiteral + stepRequest.putProperty("hint", hint); + } + stepRequest.enable(); } - myArguments = connector.defaultArguments(); - if (myArguments == null) { - throw new CantRunException(DebuggerBundle.message("error.no.debug.listen.port")); + catch (ObjectCollectedException ignored) { } + } - if (address == null) { - throw new CantRunException(DebuggerBundle.message("error.no.debug.listen.port")); + public void checkPositionNotFiltered(ThreadReferenceProxyImpl thread, Consumer> action) { + List activeFilters = getActiveFilters(); + if (!activeFilters.isEmpty()) { + String currentClassName = getCurrentClassName(thread); + if (currentClassName == null || !DebuggerUtilsEx.isFiltered(currentClassName, activeFilters)) { + action.accept(activeFilters); + } } - // zero port number means the caller leaves to debugger to decide at which port to listen - //noinspection HardCodedStringLiteral - final Connector.Argument portArg = myConnection.isUseSockets() ? myArguments.get("port") : myArguments.get("name"); - if (portArg != null) { - portArg.setValue(address); + } + + @Nonnull + private static List getActiveFilters() { + DebuggerSettings settings = DebuggerSettings.getInstance(); + List classFilters = new ArrayList<>(); + for (DebuggerClassFilterProvider provider : DebuggerClassFilterProvider.EP_NAME.getExtensionList()) { + classFilters.addAll(provider.getFilters()); + } + if (settings.TRACING_FILTERS_ENABLED) { + ContainerUtil.prepend(classFilters, settings.getSteppingFilters()); + } + return classFilters.stream().filter(ClassFilter::isEnabled).collect(Collectors.toList()); + } + + void deleteStepRequests(@Nullable final ThreadReference stepThread) { + EventRequestManager requestManager = getVirtualMachineProxy().eventRequestManager(); + List stepRequests = requestManager.stepRequests(); + if (!stepRequests.isEmpty()) { + final List toDelete = new ArrayList<>(stepRequests.size()); + for (final StepRequest request : stepRequests) { + ThreadReference threadReference = request.thread(); + // [jeka] on attempt to delete a request assigned to a thread with unknown status, a JDWP error occurs + try { + if (threadReference.status() != ThreadReference.THREAD_STATUS_UNKNOWN + && (stepThread == null || stepThread.equals(threadReference))) { + toDelete.add(request); + } + } + catch (IllegalThreadStateException e) { + LOG.info(e); // undocumented by JDI: may be thrown when querying thread status + } + catch (ObjectCollectedException ignored) { + } + } + requestManager.deleteEventRequests(toDelete); } - //noinspection HardCodedStringLiteral - final Connector.Argument timeoutArg = myArguments.get("timeout"); - if (timeoutArg != null) { - timeoutArg.setValue("0"); // wait forever + } + + @Nullable + static String getCurrentClassName(ThreadReferenceProxyImpl thread) { + try { + if (thread != null && thread.frameCount() > 0) { + StackFrameProxyImpl stackFrame = thread.frame(0); + if (stackFrame != null) { + Location location = stackFrame.location(); + ReferenceType referenceType = location == null ? null : location.declaringType(); + if (referenceType != null) { + return referenceType.name(); + } + } + } } - String listeningAddress = connector.startListening(myArguments); - String port = StringUtil.substringAfter(listeningAddress, ":"); - if (port != null) { - listeningAddress = port; + catch (EvaluateException ignored) { } - myConnection.setAddress(listeningAddress); + return null; + } - myDebugProcessDispatcher.getMulticaster().connectorIsReady(); + private VirtualMachine createVirtualMachineInt() throws ExecutionException { try { - return connector.accept(myArguments); - } - finally { - if (myArguments != null) { - try { - connector.stopListening(myArguments); + if (myArguments != null) { + throw new IOException(DebuggerBundle.message("error.debugger.already.listening")); + } + + final String address = myConnection.getAddress(); + if (myConnection.isServerMode()) { + ListeningConnector connector = + (ListeningConnector)findConnector(myConnection.isUseSockets() ? SOCKET_LISTENING_CONNECTOR_NAME : SHMEM_LISTENING_CONNECTOR_NAME); + if (connector == null) { + throw new CantRunException(DebuggerBundle.message( + "error.debug.connector.not.found", + DebuggerBundle.getTransportName(myConnection) + )); + } + myArguments = connector.defaultArguments(); + if (myArguments == null) { + throw new CantRunException(DebuggerBundle.message("error.no.debug.listen.port")); + } + + if (address == null) { + throw new CantRunException(DebuggerBundle.message("error.no.debug.listen.port")); + } + // zero port number means the caller leaves to debugger to decide at which port to listen + //noinspection HardCodedStringLiteral + final Connector.Argument portArg = myConnection.isUseSockets() ? myArguments.get("port") : myArguments.get("name"); + if (portArg != null) { + portArg.setValue(address); + } + //noinspection HardCodedStringLiteral + final Connector.Argument timeoutArg = myArguments.get("timeout"); + if (timeoutArg != null) { + timeoutArg.setValue("0"); // wait forever + } + String listeningAddress = connector.startListening(myArguments); + String port = StringUtil.substringAfter(listeningAddress, ":"); + if (port != null) { + listeningAddress = port; + } + myConnection.setAddress(listeningAddress); + + myDebugProcessDispatcher.getMulticaster().connectorIsReady(); + try { + return connector.accept(myArguments); + } + finally { + if (myArguments != null) { + try { + connector.stopListening(myArguments); + } + catch (IllegalArgumentException | IllegalConnectorArgumentsException ignored) { + // ignored + } + } + } } - catch (IllegalArgumentException | IllegalConnectorArgumentsException ignored) { - // ignored + else { // is client mode, should attach to already running process + AttachingConnector connector = + (AttachingConnector)findConnector(myConnection.isUseSockets() ? SOCKET_ATTACHING_CONNECTOR_NAME : SHMEM_ATTACHING_CONNECTOR_NAME); + + if (connector == null) { + throw new CantRunException(DebuggerBundle.message( + "error.debug.connector.not.found", + DebuggerBundle.getTransportName(myConnection) + )); + } + myArguments = connector.defaultArguments(); + if (myConnection.isUseSockets()) { + //noinspection HardCodedStringLiteral + final Connector.Argument hostnameArg = myArguments.get("hostname"); + if (hostnameArg != null && myConnection.getHostName() != null) { + hostnameArg.setValue(myConnection.getHostName()); + } + if (address == null) { + throw new CantRunException(DebuggerBundle.message("error.no.debug.attach.port")); + } + //noinspection HardCodedStringLiteral + final Connector.Argument portArg = myArguments.get("port"); + if (portArg != null) { + portArg.setValue(address); + } + } + else { + if (address == null) { + throw new CantRunException(DebuggerBundle.message("error.no.shmem.address")); + } + //noinspection HardCodedStringLiteral + final Connector.Argument nameArg = myArguments.get("name"); + if (nameArg != null) { + nameArg.setValue(address); + } + } + //noinspection HardCodedStringLiteral + final Connector.Argument timeoutArg = myArguments.get("timeout"); + if (timeoutArg != null) { + timeoutArg.setValue("0"); // wait forever + } + + myDebugProcessDispatcher.getMulticaster().connectorIsReady(); + try { + return connector.attach(myArguments); + } + catch (IllegalArgumentException e) { + throw new CantRunException(e.getLocalizedMessage()); + } } - } } - } - else { // is client mode, should attach to already running process - AttachingConnector connector = - (AttachingConnector)findConnector(myConnection.isUseSockets() ? SOCKET_ATTACHING_CONNECTOR_NAME : SHMEM_ATTACHING_CONNECTOR_NAME); - - if (connector == null) { - throw new CantRunException(DebuggerBundle.message( - "error.debug.connector.not.found", - DebuggerBundle.getTransportName(myConnection) - )); - } - myArguments = connector.defaultArguments(); - if (myConnection.isUseSockets()) { - //noinspection HardCodedStringLiteral - final Connector.Argument hostnameArg = myArguments.get("hostname"); - if (hostnameArg != null && myConnection.getHostName() != null) { - hostnameArg.setValue(myConnection.getHostName()); - } - if (address == null) { - throw new CantRunException(DebuggerBundle.message("error.no.debug.attach.port")); - } - //noinspection HardCodedStringLiteral - final Connector.Argument portArg = myArguments.get("port"); - if (portArg != null) { - portArg.setValue(address); - } + catch (IOException e) { + throw new ExecutionException(processIOException(e, DebuggerBundle.getAddressDisplayName(myConnection)), e); } - else { - if (address == null) { - throw new CantRunException(DebuggerBundle.message("error.no.shmem.address")); - } - //noinspection HardCodedStringLiteral - final Connector.Argument nameArg = myArguments.get("name"); - if (nameArg != null) { - nameArg.setValue(address); - } + catch (IllegalConnectorArgumentsException e) { + throw new ExecutionException(processError(e), e); + } + finally { + myArguments = null; } - //noinspection HardCodedStringLiteral - final Connector.Argument timeoutArg = myArguments.get("timeout"); - if (timeoutArg != null) { - timeoutArg.setValue("0"); // wait forever + } + + public void showStatusText(final String text) { + if (!myStatusUpdateAlarm.isDisposed()) { + myStatusUpdateAlarm.cancelAllRequests(); + myStatusUpdateAlarm.addRequest(() -> StatusBarUtil.setStatusBarInfo(myProject, text), 50); } + } - myDebugProcessDispatcher.getMulticaster().connectorIsReady(); + static Connector findConnector(String connectorName) throws ExecutionException { + VirtualMachineManager virtualMachineManager; try { - return connector.attach(myArguments); + virtualMachineManager = Bootstrap.virtualMachineManager(); } - catch (IllegalArgumentException e) { - throw new CantRunException(e.getLocalizedMessage()); + catch (Error e) { + final String error = e.getClass().getName() + " : " + e.getLocalizedMessage(); + throw new ExecutionException(DebuggerBundle.message("debugger.jdi.bootstrap.error", error)); + } + List connectors; + if (SOCKET_ATTACHING_CONNECTOR_NAME.equals(connectorName) || SHMEM_ATTACHING_CONNECTOR_NAME.equals(connectorName)) { + connectors = virtualMachineManager.attachingConnectors(); + } + else if (SOCKET_LISTENING_CONNECTOR_NAME.equals(connectorName) || SHMEM_LISTENING_CONNECTOR_NAME.equals(connectorName)) { + connectors = virtualMachineManager.listeningConnectors(); + } + else { + return null; + } + for (Object connector1 : connectors) { + Connector connector = (Connector)connector1; + if (connectorName.equals(connector.name())) { + return connector; + } + } + return null; + } + + private void checkVirtualMachineVersion(VirtualMachine vm) { + final String version = vm.version(); + if ("1.4.0".equals(version)) { + DebuggerInvocationUtil.swingInvokeLater( + myProject, + () -> Messages.showMessageDialog( + myProject, + DebuggerBundle.message("warning.jdk140.unstable"), + DebuggerBundle.message("title.jdk140.unstable"), + UIUtil.getWarningIcon() + ) + ); + } + + if (getSession().getAlternativeJre() == null) { + Sdk runjre = getSession().getRunJre(); + if ((runjre == null || runjre.getSdkType() instanceof JavaSdkType) && !versionMatch(runjre, version)) { + JavaSdkTypeUtil.getAllJavaSdks().stream() + .filter(sdk -> versionMatch(sdk, version)) + .findFirst() + .ifPresent(sdk -> + { + XDebuggerUIConstants.NOTIFICATION_GROUP.createNotification( + DebuggerBundle.message( + "message.remote.jre.version.mismatch", + version, + runjre != null ? runjre.getVersionString() : "unknown", + sdk.getName() + ), + NotificationType.INFORMATION + ).notify(myProject); + getSession().setAlternativeJre(sdk); + }); + } } - } - } - catch (IOException e) { - throw new ExecutionException(processIOException(e, DebuggerBundle.getAddressDisplayName(myConnection)), e); - } - catch (IllegalConnectorArgumentsException e) { - throw new ExecutionException(processError(e), e); - } - finally { - myArguments = null; } - } - public void showStatusText(final String text) { - if (!myStatusUpdateAlarm.isDisposed()) { - myStatusUpdateAlarm.cancelAllRequests(); - myStatusUpdateAlarm.addRequest(() -> StatusBarUtil.setStatusBarInfo(myProject, text), 50); + private static boolean versionMatch(@Nullable Sdk sdk, String version) { + if (sdk != null && sdk.getSdkType() instanceof JavaSdkType) { + String versionString = sdk.getVersionString(); + return versionString != null && versionString.contains(version); + } + return false; } - } - static Connector findConnector(String connectorName) throws ExecutionException { - VirtualMachineManager virtualMachineManager; - try { - virtualMachineManager = Bootstrap.virtualMachineManager(); - } - catch (Error e) { - final String error = e.getClass().getName() + " : " + e.getLocalizedMessage(); - throw new ExecutionException(DebuggerBundle.message("debugger.jdi.bootstrap.error", error)); + /*Event dispatching*/ + public void addEvaluationListener(EvaluationListener evaluationListener) { + myEvaluationDispatcher.addListener(evaluationListener); } - List connectors; - if (SOCKET_ATTACHING_CONNECTOR_NAME.equals(connectorName) || SHMEM_ATTACHING_CONNECTOR_NAME.equals(connectorName)) { - connectors = virtualMachineManager.attachingConnectors(); + + public void removeEvaluationListener(EvaluationListener evaluationListener) { + myEvaluationDispatcher.removeListener(evaluationListener); } - else if (SOCKET_LISTENING_CONNECTOR_NAME.equals(connectorName) || SHMEM_LISTENING_CONNECTOR_NAME.equals(connectorName)) { - connectors = virtualMachineManager.listeningConnectors(); + + @Override + public void addDebugProcessListener(DebugProcessListener listener) { + myDebugProcessDispatcher.addListener(listener); } - else { - return null; + + @Override + public void removeDebugProcessListener(DebugProcessListener listener) { + myDebugProcessDispatcher.removeListener(listener); } - for (Object connector1 : connectors) { - Connector connector = (Connector)connector1; - if (connectorName.equals(connector.name())) { - return connector; - } + + public void addProcessListener(ProcessListener processListener) { + synchronized (myProcessListeners) { + if (getProcessHandler() != null) { + getProcessHandler().addProcessListener(processListener); + } + else { + myProcessListeners.add(processListener); + } + } } - return null; - } - - private void checkVirtualMachineVersion(VirtualMachine vm) { - final String version = vm.version(); - if ("1.4.0".equals(version)) { - DebuggerInvocationUtil.swingInvokeLater( - myProject, - () -> Messages.showMessageDialog( - myProject, - DebuggerBundle.message("warning.jdk140.unstable"), - DebuggerBundle.message("title.jdk140.unstable"), - UIUtil.getWarningIcon() - ) - ); - } - - if (getSession().getAlternativeJre() == null) { - Sdk runjre = getSession().getRunJre(); - if ((runjre == null || runjre.getSdkType() instanceof JavaSdkType) && !versionMatch(runjre, version)) { - JavaSdkTypeUtil.getAllJavaSdks().stream() - .filter(sdk -> versionMatch(sdk, version)) - .findFirst() - .ifPresent(sdk -> - { - XDebuggerUIConstants.NOTIFICATION_GROUP.createNotification( - DebuggerBundle.message( - "message.remote.jre.version.mismatch", - version, - runjre != null ? runjre.getVersionString() : "unknown", - sdk.getName()), - NotificationType.INFORMATION - ).notify(myProject); - getSession().setAlternativeJre(sdk); - }); - } + + public void removeProcessListener(ProcessListener processListener) { + synchronized (myProcessListeners) { + if (getProcessHandler() != null) { + getProcessHandler().removeProcessListener(processListener); + } + else { + myProcessListeners.remove(processListener); + } + } } - } - private static boolean versionMatch(@Nullable Sdk sdk, String version) { - if (sdk != null && sdk.getSdkType() instanceof JavaSdkType) { - String versionString = sdk.getVersionString(); - return versionString != null && versionString.contains(version); + /* getters */ + public RemoteConnection getConnection() { + return myConnection; } - return false; - } - /*Event dispatching*/ - public void addEvaluationListener(EvaluationListener evaluationListener) { - myEvaluationDispatcher.addListener(evaluationListener); - } + @Override + public ExecutionResult getExecutionResult() { + return myExecutionResult; + } - public void removeEvaluationListener(EvaluationListener evaluationListener) { - myEvaluationDispatcher.removeListener(evaluationListener); - } + @Nonnull + @Override + public Project getProject() { + return myProject; + } - @Override - public void addDebugProcessListener(DebugProcessListener listener) { - myDebugProcessDispatcher.addListener(listener); - } + public boolean canRedefineClasses() { + final VirtualMachineProxyImpl vm = myVirtualMachineProxy; + return vm != null && vm.canRedefineClasses(); + } - @Override - public void removeDebugProcessListener(DebugProcessListener listener) { - myDebugProcessDispatcher.removeListener(listener); - } + public boolean canWatchFieldModification() { + final VirtualMachineProxyImpl vm = myVirtualMachineProxy; + return vm != null && vm.canWatchFieldModification(); + } - public void addProcessListener(ProcessListener processListener) { - synchronized (myProcessListeners) { - if (getProcessHandler() != null) { - getProcessHandler().addProcessListener(processListener); - } - else { - myProcessListeners.add(processListener); - } + public boolean isInInitialState() { + return myState.get() == State.INITIAL; } - } - public void removeProcessListener(ProcessListener processListener) { - synchronized (myProcessListeners) { - if (getProcessHandler() != null) { - getProcessHandler().removeProcessListener(processListener); - } - else { - myProcessListeners.remove(processListener); - } + @Override + public boolean isAttached() { + return myState.get() == State.ATTACHED; } - } - - /* getters */ - public RemoteConnection getConnection() { - return myConnection; - } - - @Override - public ExecutionResult getExecutionResult() { - return myExecutionResult; - } - - @Nonnull - @Override - public Project getProject() { - return myProject; - } - - public boolean canRedefineClasses() { - final VirtualMachineProxyImpl vm = myVirtualMachineProxy; - return vm != null && vm.canRedefineClasses(); - } - - public boolean canWatchFieldModification() { - final VirtualMachineProxyImpl vm = myVirtualMachineProxy; - return vm != null && vm.canWatchFieldModification(); - } - - public boolean isInInitialState() { - return myState.get() == State.INITIAL; - } - - @Override - public boolean isAttached() { - return myState.get() == State.ATTACHED; - } - - @Override - public boolean isDetached() { - return myState.get() == State.DETACHED; - } - - @Override - public boolean isDetaching() { - return myState.get() == State.DETACHING; - } - - @Override - public RequestManagerImpl getRequestsManager() { - return myRequestManager; - } - - @Nonnull - @Override - public VirtualMachineProxyImpl getVirtualMachineProxy() { - DebuggerManagerThreadImpl.assertIsManagerThread(); - final VirtualMachineProxyImpl vm = myVirtualMachineProxy; - if (vm == null) { - if (isInInitialState()) { - throw new IllegalStateException("Virtual machine is not initialized yet"); - } - else { - throw new VMDisconnectedException(); - } + + @Override + public boolean isDetached() { + return myState.get() == State.DETACHED; } - return vm; - } - - @Override - public void appendPositionManager(final PositionManager positionManager) { - DebuggerManagerThreadImpl.assertIsManagerThread(); - myPositionManager.appendPositionManager(positionManager); - } - - private volatile RunToCursorBreakpoint myRunToCursorBreakpoint; - - public void setRunToCursorBreakpoint(@Nullable RunToCursorBreakpoint breakpoint) { - myRunToCursorBreakpoint = breakpoint; - } - - public void cancelRunToCursorBreakpoint() { - DebuggerManagerThreadImpl.assertIsManagerThread(); - final RunToCursorBreakpoint runToCursorBreakpoint = myRunToCursorBreakpoint; - if (runToCursorBreakpoint != null) { - setRunToCursorBreakpoint(null); - getRequestsManager().deleteRequest(runToCursorBreakpoint); - if (runToCursorBreakpoint.isRestoreBreakpoints()) { - DebuggerManagerEx.getInstanceEx(getProject()).getBreakpointManager().enableBreakpoints(this); - } + + @Override + public boolean isDetaching() { + return myState.get() == State.DETACHING; } - } - protected void closeProcess(boolean closedByUser) { - DebuggerManagerThreadImpl.assertIsManagerThread(); + @Override + public RequestManagerImpl getRequestsManager() { + return myRequestManager; + } - if (myState.compareAndSet(State.INITIAL, State.DETACHING) || myState.compareAndSet(State.ATTACHED, State.DETACHING)) { - try { - getManagerThread().close(); - } - finally { + @Nonnull + @Override + public VirtualMachineProxyImpl getVirtualMachineProxy() { + DebuggerManagerThreadImpl.assertIsManagerThread(); final VirtualMachineProxyImpl vm = myVirtualMachineProxy; - myVirtualMachineProxy = null; - myPositionManager = CompoundPositionManager.EMPTY; - myReturnValueWatcher = null; - myNodeRenderersMap.clear(); - myRenderers.clear(); - DebuggerUtils.cleanupAfterProcessFinish(this); - myState.compareAndSet(State.DETACHING, State.DETACHED); - try { - myDebugProcessDispatcher.getMulticaster().processDetached(this, closedByUser); - } - finally { - //if (DebuggerSettings.getInstance().UNMUTE_ON_STOP) { - // XDebugSession session = mySession.getXDebugSession(); - // if (session != null) { - // session.setBreakpointMuted(false); - // } - //} - if (vm != null) { - try { - vm.dispose(); // to be on the safe side ensure that VM mirror, if present, is disposed and invalidated + if (vm == null) { + if (isInInitialState()) { + throw new IllegalStateException("Virtual machine is not initialized yet"); } - catch (Throwable ignored) { + else { + throw new VMDisconnectedException(); } - } - myWaitFor.up(); } - } + return vm; } - } - private static String formatMessage(String message) { - final int lineLength = 90; - StringBuilder buf = new StringBuilder(message.length()); - int index = 0; - while (index < message.length()) { - buf.append(message.substring(index, Math.min(index + lineLength, message.length()))).append('\n'); - index += lineLength; + @Override + public void appendPositionManager(final PositionManager positionManager) { + DebuggerManagerThreadImpl.assertIsManagerThread(); + myPositionManager.appendPositionManager(positionManager); } - return buf.toString(); - } - public static String processError(Exception e) { - String message; + private volatile RunToCursorBreakpoint myRunToCursorBreakpoint; - if (e instanceof VMStartException e1) { - message = e1.getLocalizedMessage(); - } - else if (e instanceof IllegalConnectorArgumentsException e1) { - final List invalidArgumentNames = e1.argumentNames(); - message = formatMessage( - DebuggerBundle.message("error.invalid.argument", invalidArgumentNames.size()) + ": " + e1.getLocalizedMessage() - ) + invalidArgumentNames; - LOG.debug(e1); - } - else if (e instanceof CantRunException) { - message = e.getLocalizedMessage(); - } - else if (e instanceof VMDisconnectedException) { - message = DebuggerBundle.message("error.vm.disconnected"); - } - else if (e instanceof IOException e1) { - message = processIOException(e1, null); - } - else if (e instanceof ExecutionException) { - message = e.getLocalizedMessage(); + public void setRunToCursorBreakpoint(@Nullable RunToCursorBreakpoint breakpoint) { + myRunToCursorBreakpoint = breakpoint; } - else { - message = DebuggerBundle.message("error.exception.while.connecting", e.getClass().getName(), e.getLocalizedMessage()); - LOG.debug(e); - } - return message; - } - @Nonnull - public static String processIOException(@Nonnull IOException e, @Nullable String address) { - if (e instanceof UnknownHostException) { - return DebuggerBundle.message("error.unknown.host") + (address != null ? " (" + address + ")" : "") + ":\n" + e.getLocalizedMessage(); + public void cancelRunToCursorBreakpoint() { + DebuggerManagerThreadImpl.assertIsManagerThread(); + final RunToCursorBreakpoint runToCursorBreakpoint = myRunToCursorBreakpoint; + if (runToCursorBreakpoint != null) { + setRunToCursorBreakpoint(null); + getRequestsManager().deleteRequest(runToCursorBreakpoint); + if (runToCursorBreakpoint.isRestoreBreakpoints()) { + DebuggerManagerEx.getInstanceEx(getProject()).getBreakpointManager().enableBreakpoints(this); + } + } } - String message; - final StringBuilder buf = new StringBuilder(); - buf.append(DebuggerBundle.message("error.cannot.open.debugger.port")); - if (address != null) { - buf.append(" (").append(address).append(")"); - } - buf.append(": "); - buf.append(e.getClass().getName()).append(" "); - final String localizedMessage = e.getLocalizedMessage(); - if (!StringUtil.isEmpty(localizedMessage)) { - buf.append('"').append(localizedMessage).append('"'); + protected void closeProcess(boolean closedByUser) { + DebuggerManagerThreadImpl.assertIsManagerThread(); + + if (myState.compareAndSet(State.INITIAL, State.DETACHING) || myState.compareAndSet(State.ATTACHED, State.DETACHING)) { + try { + getManagerThread().close(); + } + finally { + final VirtualMachineProxyImpl vm = myVirtualMachineProxy; + myVirtualMachineProxy = null; + myPositionManager = CompoundPositionManager.EMPTY; + myReturnValueWatcher = null; + myNodeRenderersMap.clear(); + myRenderers.clear(); + DebuggerUtils.cleanupAfterProcessFinish(this); + myState.compareAndSet(State.DETACHING, State.DETACHED); + try { + myDebugProcessDispatcher.getMulticaster().processDetached(this, closedByUser); + } + finally { + //if (DebuggerSettings.getInstance().UNMUTE_ON_STOP) { + // XDebugSession session = mySession.getXDebugSession(); + // if (session != null) { + // session.setBreakpointMuted(false); + // } + //} + if (vm != null) { + try { + vm.dispose(); // to be on the safe side ensure that VM mirror, if present, is disposed and invalidated + } + catch (Throwable ignored) { + } + } + myWaitFor.up(); + } + } + } } - LOG.debug(e); - message = buf.toString(); - return message; - } - public void dispose() { - Disposer.dispose(myDisposable); - myRequestManager.setFilterThread(null); - } + private static String formatMessage(String message) { + final int lineLength = 90; + StringBuilder buf = new StringBuilder(message.length()); + int index = 0; + while (index < message.length()) { + buf.append(message.substring(index, Math.min(index + lineLength, message.length()))).append('\n'); + index += lineLength; + } + return buf.toString(); + } - @Override - public DebuggerManagerThreadImpl getManagerThread() { - return myDebuggerManagerThread; - } + public static String processError(Exception e) { + String message; - private static int getInvokePolicy(SuspendContext suspendContext) { - if (suspendContext.getSuspendPolicy() == EventRequest.SUSPEND_EVENT_THREAD || isResumeOnlyCurrentThread()) { - return ObjectReference.INVOKE_SINGLE_THREADED; + if (e instanceof VMStartException e1) { + message = e1.getLocalizedMessage(); + } + else if (e instanceof IllegalConnectorArgumentsException e1) { + final List invalidArgumentNames = e1.argumentNames(); + message = formatMessage( + DebuggerBundle.message("error.invalid.argument", invalidArgumentNames.size()) + ": " + e1.getLocalizedMessage() + ) + invalidArgumentNames; + LOG.debug(e1); + } + else if (e instanceof CantRunException) { + message = e.getLocalizedMessage(); + } + else if (e instanceof VMDisconnectedException) { + message = DebuggerBundle.message("error.vm.disconnected"); + } + else if (e instanceof IOException e1) { + message = processIOException(e1, null); + } + else if (e instanceof ExecutionException) { + message = e.getLocalizedMessage(); + } + else { + message = DebuggerBundle.message("error.exception.while.connecting", e.getClass().getName(), e.getLocalizedMessage()); + LOG.debug(e); + } + return message; } - return 0; - } - @Override - public void waitFor() { - LOG.assertTrue(!DebuggerManagerThreadImpl.isManagerThread()); - myWaitFor.waitFor(); - } - - @Override - public void waitFor(long timeout) { - LOG.assertTrue(!DebuggerManagerThreadImpl.isManagerThread()); - myWaitFor.waitFor(timeout); - } + @Nonnull + public static String processIOException(@Nonnull IOException e, @Nullable String address) { + if (e instanceof UnknownHostException) { + return DebuggerBundle.message("error.unknown.host") + (address != null ? " (" + address + ")" : "") + ":\n" + e.getLocalizedMessage(); + } - private abstract class InvokeCommand { - private final Method myMethod; - private final List myArgs; + String message; + final StringBuilder buf = new StringBuilder(); + buf.append(DebuggerBundle.message("error.cannot.open.debugger.port")); + if (address != null) { + buf.append(" (").append(address).append(")"); + } + buf.append(": "); + buf.append(e.getClass().getName()).append(" "); + final String localizedMessage = e.getLocalizedMessage(); + if (!StringUtil.isEmpty(localizedMessage)) { + buf.append('"').append(localizedMessage).append('"'); + } + LOG.debug(e); + message = buf.toString(); + return message; + } - protected InvokeCommand(@Nonnull Method method, @Nonnull List args) { - myMethod = method; - myArgs = new ArrayList<>(args); + public void dispose() { + Disposer.dispose(myDisposable); + myRequestManager.setFilterThread(null); } - public String toString() { - return "INVOKE: " + super.toString(); + @Override + public DebuggerManagerThreadImpl getManagerThread() { + return myDebuggerManagerThread; } - protected abstract E invokeMethod( - int invokePolicy, - Method method, - List args - ) throws InvocationException, ClassNotLoadedException, IncompatibleThreadStateException, InvalidTypeException; + private static int getInvokePolicy(SuspendContext suspendContext) { + if (suspendContext.getSuspendPolicy() == EventRequest.SUSPEND_EVENT_THREAD || isResumeOnlyCurrentThread()) { + return ObjectReference.INVOKE_SINGLE_THREADED; + } + return 0; + } - - E start(EvaluationContextImpl evaluationContext, boolean internalEvaluate) throws EvaluateException { - while (true) { - try { - return startInternal(evaluationContext, internalEvaluate); - } - catch (ClassNotLoadedException e) { - ReferenceType loadedClass = null; - try { - if (evaluationContext.isAutoLoadClasses()) { - loadedClass = loadClass(evaluationContext, e.className(), evaluationContext.getClassLoader()); - } - } - catch (Exception ignored) { - loadedClass = null; - } - if (loadedClass == null) { - throw EvaluateExceptionUtil.createEvaluateException(e); - } - } - } + @Override + public void waitFor() { + LOG.assertTrue(!DebuggerManagerThreadImpl.isManagerThread()); + myWaitFor.waitFor(); } - E startInternal(EvaluationContextImpl evaluationContext, boolean internalEvaluate) throws EvaluateException, ClassNotLoadedException { - DebuggerManagerThreadImpl.assertIsManagerThread(); - SuspendContextImpl suspendContext = evaluationContext.getSuspendContext(); - SuspendManagerUtil.assertSuspendContext(suspendContext); + @Override + public void waitFor(long timeout) { + LOG.assertTrue(!DebuggerManagerThreadImpl.isManagerThread()); + myWaitFor.waitFor(timeout); + } - ThreadReferenceProxyImpl invokeThread = suspendContext.getThread(); + private abstract class InvokeCommand { + private final Method myMethod; + private final List myArgs; - if (SuspendManagerUtil.isEvaluating(getSuspendManager(), invokeThread)) { - throw EvaluateExceptionUtil.NESTED_EVALUATION_ERROR; - } + protected InvokeCommand(@Nonnull Method method, @Nonnull List args) { + myMethod = method; + myArgs = new ArrayList<>(args); + } - if (!suspendContext.suspends(invokeThread)) { - throw EvaluateExceptionUtil.THREAD_WAS_RESUMED; - } + public String toString() { + return "INVOKE: " + super.toString(); + } - Set suspendingContexts = SuspendManagerUtil.getSuspendingContexts(getSuspendManager(), invokeThread); - final ThreadReference invokeThreadRef = invokeThread.getThreadReference(); + protected abstract E invokeMethod( + int invokePolicy, + Method method, + List args + ) throws InvocationException, ClassNotLoadedException, IncompatibleThreadStateException, InvalidTypeException; - myEvaluationDispatcher.getMulticaster().evaluationStarted(suspendContext); - beforeMethodInvocation(suspendContext, myMethod, internalEvaluate); - Object resumeData = null; - try { - for (SuspendContextImpl suspendingContext : suspendingContexts) { - final ThreadReferenceProxyImpl suspendContextThread = suspendingContext.getThread(); - if (suspendContextThread != invokeThread) { - if (LOG.isDebugEnabled()) { - LOG.debug("Resuming " + invokeThread + " that is paused by " + suspendContextThread); + E start(EvaluationContextImpl evaluationContext, boolean internalEvaluate) throws EvaluateException { + while (true) { + try { + return startInternal(evaluationContext, internalEvaluate); + } + catch (ClassNotLoadedException e) { + ReferenceType loadedClass = null; + try { + if (evaluationContext.isAutoLoadClasses()) { + loadedClass = loadClass(evaluationContext, e.className(), evaluationContext.getClassLoader()); + } + } + catch (Exception ignored) { + loadedClass = null; + } + if (loadedClass == null) { + throw EvaluateExceptionUtil.createEvaluateException(e); + } + } } - LOG.assertTrue(suspendContextThread == null || !invokeThreadRef.equals(suspendContextThread.getThreadReference())); - getSuspendManager().resumeThread(suspendingContext, invokeThread); - } } - resumeData = SuspendManagerUtil.prepareForResume(suspendContext); - suspendContext.setIsEvaluating(evaluationContext); + E startInternal(EvaluationContextImpl evaluationContext, boolean internalEvaluate) + throws EvaluateException, ClassNotLoadedException { + DebuggerManagerThreadImpl.assertIsManagerThread(); + SuspendContextImpl suspendContext = evaluationContext.getSuspendContext(); + SuspendManagerUtil.assertSuspendContext(suspendContext); - getVirtualMachineProxy().clearCaches(); + ThreadReferenceProxyImpl invokeThread = suspendContext.getThread(); - return invokeMethodAndFork(suspendContext); - } - catch (InvocationException | InternalException | UnsupportedOperationException - | ObjectCollectedException | InvalidTypeException | IncompatibleThreadStateException e) { - throw EvaluateExceptionUtil.createEvaluateException(e); - } - finally { - suspendContext.setIsEvaluating(null); - if (resumeData != null) { - SuspendManagerUtil.restoreAfterResume(suspendContext, resumeData); - } - for (SuspendContextImpl suspendingContext : mySuspendManager.getEventContexts()) { - if (suspendingContexts.contains(suspendingContext) - && !suspendingContext.isEvaluating() - && !suspendingContext.suspends(invokeThread)) { - mySuspendManager.suspendThread(suspendingContext, invokeThread); - } - } + if (SuspendManagerUtil.isEvaluating(getSuspendManager(), invokeThread)) { + throw EvaluateExceptionUtil.NESTED_EVALUATION_ERROR; + } - LOG.debug("getVirtualMachine().clearCaches()"); - getVirtualMachineProxy().clearCaches(); - afterMethodInvocation(suspendContext, internalEvaluate); + if (!suspendContext.suspends(invokeThread)) { + throw EvaluateExceptionUtil.THREAD_WAS_RESUMED; + } - myEvaluationDispatcher.getMulticaster().evaluationFinished(suspendContext); - } - } + Set suspendingContexts = SuspendManagerUtil.getSuspendingContexts(getSuspendManager(), invokeThread); + final ThreadReference invokeThreadRef = invokeThread.getThreadReference(); - private E invokeMethodAndFork(final SuspendContextImpl context) - throws InvocationException, ClassNotLoadedException, IncompatibleThreadStateException, InvalidTypeException { - final int invokePolicy = getInvokePolicy(context); - final Exception[] exception = new Exception[1]; - final Value[] result = new Value[1]; - getManagerThread().startLongProcessAndFork(() -> - { - ThreadReferenceProxyImpl thread = context.getThread(); - try { - try { - if (LOG.isDebugEnabled()) { - final VirtualMachineProxyImpl virtualMachineProxy = getVirtualMachineProxy(); - virtualMachineProxy.logThreads(); - LOG.debug("Invoke in " + thread.name()); - assertThreadSuspended(thread, context); - } - - if (myMethod.isVarArgs()) { - // See IDEA-63581 - // if vararg parameter array is of interface type and Object[] is expected, JDI wrap it into another array, - // in this case we have to unroll the array manually and pass its elements to the method instead of array object - int lastIndex = myMethod.argumentTypeNames().size() - 1; - if (lastIndex >= 0 && myArgs.size() > lastIndex) { // at least one varargs param - Object firstVararg = myArgs.get(lastIndex); - if (myArgs.size() == lastIndex + 1) { // only one vararg param - if (firstVararg instanceof ArrayReference arrayRef) { - if (((ArrayType)arrayRef.referenceType()).componentType() instanceof InterfaceType) { - List argTypes = myMethod.argumentTypeNames(); - if (argTypes.size() > lastIndex && argTypes.get(lastIndex).startsWith(JavaClassNames.JAVA_LANG_OBJECT)) { - // unwrap array of interfaces for vararg param - myArgs.remove(lastIndex); - myArgs.addAll(arrayRef.getValues()); - } + myEvaluationDispatcher.getMulticaster().evaluationStarted(suspendContext); + beforeMethodInvocation(suspendContext, myMethod, internalEvaluate); + + Object resumeData = null; + try { + for (SuspendContextImpl suspendingContext : suspendingContexts) { + final ThreadReferenceProxyImpl suspendContextThread = suspendingContext.getThread(); + if (suspendContextThread != invokeThread) { + if (LOG.isDebugEnabled()) { + LOG.debug("Resuming " + invokeThread + " that is paused by " + suspendContextThread); + } + LOG.assertTrue(suspendContextThread == null || !invokeThreadRef.equals(suspendContextThread.getThreadReference())); + getSuspendManager().resumeThread(suspendingContext, invokeThread); } - } } - else if (firstVararg == null) { // more than one vararg params and the first one is null - // this is a workaround for a bug in jdi, see IDEA-157321 - int argCount = myArgs.size(); - List paramTypes = myMethod.argumentTypes(); - int paramCount = paramTypes.size(); - ArrayType lastParamType = (ArrayType)paramTypes.get(paramTypes.size() - 1); - int count = argCount - paramCount + 1; - ArrayReference argArray = lastParamType.newInstance(count); - argArray.setValues(0, myArgs, paramCount - 1, count); - myArgs.set(paramCount - 1, argArray); - myArgs.subList(paramCount, argCount).clear(); + resumeData = SuspendManagerUtil.prepareForResume(suspendContext); + suspendContext.setIsEvaluating(evaluationContext); + + getVirtualMachineProxy().clearCaches(); + + return invokeMethodAndFork(suspendContext); + } + catch (InvocationException | InternalException | UnsupportedOperationException + | ObjectCollectedException | InvalidTypeException | IncompatibleThreadStateException e) { + throw EvaluateExceptionUtil.createEvaluateException(e); + } + finally { + suspendContext.setIsEvaluating(null); + if (resumeData != null) { + SuspendManagerUtil.restoreAfterResume(suspendContext, resumeData); } - } + for (SuspendContextImpl suspendingContext : mySuspendManager.getEventContexts()) { + if (suspendingContexts.contains(suspendingContext) + && !suspendingContext.isEvaluating() + && !suspendingContext.suspends(invokeThread)) { + mySuspendManager.suspendThread(suspendingContext, invokeThread); + } + } + + LOG.debug("getVirtualMachine().clearCaches()"); + getVirtualMachineProxy().clearCaches(); + afterMethodInvocation(suspendContext, internalEvaluate); + + myEvaluationDispatcher.getMulticaster().evaluationFinished(suspendContext); } + } - // ensure args are not collected - myArgs.stream() - .filter(value -> value instanceof ObjectReference) - .forEach(value -> DebuggerUtilsEx.disableCollection((ObjectReference)value)); + private E invokeMethodAndFork(final SuspendContextImpl context) + throws InvocationException, ClassNotLoadedException, IncompatibleThreadStateException, InvalidTypeException { + final int invokePolicy = getInvokePolicy(context); + final Exception[] exception = new Exception[1]; + final Value[] result = new Value[1]; + getManagerThread().startLongProcessAndFork(() -> + { + ThreadReferenceProxyImpl thread = context.getThread(); + try { + try { + if (LOG.isDebugEnabled()) { + final VirtualMachineProxyImpl virtualMachineProxy = getVirtualMachineProxy(); + virtualMachineProxy.logThreads(); + LOG.debug("Invoke in " + thread.name()); + assertThreadSuspended(thread, context); + } + + if (myMethod.isVarArgs()) { + // See IDEA-63581 + // if vararg parameter array is of interface type and Object[] is expected, JDI wrap it into another array, + // in this case we have to unroll the array manually and pass its elements to the method instead of array object + int lastIndex = myMethod.argumentTypeNames().size() - 1; + if (lastIndex >= 0 && myArgs.size() > lastIndex) { // at least one varargs param + Object firstVararg = myArgs.get(lastIndex); + if (myArgs.size() == lastIndex + 1) { // only one vararg param + if (firstVararg instanceof ArrayReference arrayRef) { + if (((ArrayType)arrayRef.referenceType()).componentType() instanceof InterfaceType) { + List argTypes = myMethod.argumentTypeNames(); + if (argTypes.size() > lastIndex + && argTypes.get(lastIndex).startsWith(JavaClassNames.JAVA_LANG_OBJECT)) { + // unwrap array of interfaces for vararg param + myArgs.remove(lastIndex); + myArgs.addAll(arrayRef.getValues()); + } + } + } + } + else if (firstVararg == null) { // more than one vararg params and the first one is null + // this is a workaround for a bug in jdi, see IDEA-157321 + int argCount = myArgs.size(); + List paramTypes = myMethod.argumentTypes(); + int paramCount = paramTypes.size(); + ArrayType lastParamType = (ArrayType)paramTypes.get(paramTypes.size() - 1); + + int count = argCount - paramCount + 1; + ArrayReference argArray = lastParamType.newInstance(count); + argArray.setValues(0, myArgs, paramCount - 1, count); + myArgs.set(paramCount - 1, argArray); + myArgs.subList(paramCount, argCount).clear(); + } + } + } + + // ensure args are not collected + myArgs.stream() + .filter(value -> value instanceof ObjectReference) + .forEach(value -> DebuggerUtilsEx.disableCollection((ObjectReference)value)); + + // workaround for jdi hang in trace mode + if (!StringUtil.isEmpty(ourTrace)) { + myArgs.forEach(Object::toString); + } + + result[0] = invokeMethod(invokePolicy, myMethod, myArgs); + } + finally { + // assertThreadSuspended(thread, context); + // ensure args are not collected + myArgs.stream() + .filter(value -> value instanceof ObjectReference) + .forEach(value -> DebuggerUtilsEx.enableCollection((ObjectReference)value)); + } + } + catch (Exception e) { + exception[0] = e; + } + }); - // workaround for jdi hang in trace mode - if (!StringUtil.isEmpty(ourTrace)) { - myArgs.forEach(Object::toString); + if (exception[0] != null) { + if (exception[0] instanceof InvocationException invocationException) { + throw invocationException; + } + else if (exception[0] instanceof ClassNotLoadedException classNotLoadedException) { + throw classNotLoadedException; + } + else if (exception[0] instanceof IncompatibleThreadStateException incompatibleThreadStateException) { + throw incompatibleThreadStateException; + } + else if (exception[0] instanceof InvalidTypeException invalidTypeException) { + throw invalidTypeException; + } + else if (exception[0] instanceof RuntimeException runtimeException) { + throw runtimeException; + } + else { + LOG.error("Unexpected exception", new Throwable().initCause(exception[0])); + } } - result[0] = invokeMethod(invokePolicy, myMethod, myArgs); - } - finally { - // assertThreadSuspended(thread, context); - // ensure args are not collected - myArgs.stream() - .filter(value -> value instanceof ObjectReference) - .forEach(value -> DebuggerUtilsEx.enableCollection((ObjectReference)value)); - } - } - catch (Exception e) { - exception[0] = e; + //noinspection unchecked + return (E)result[0]; } - }); - if (exception[0] != null) { - if (exception[0] instanceof InvocationException invocationException) { - throw invocationException; - } - else if (exception[0] instanceof ClassNotLoadedException classNotLoadedException) { - throw classNotLoadedException; - } - else if (exception[0] instanceof IncompatibleThreadStateException incompatibleThreadStateException) { - throw incompatibleThreadStateException; - } - else if (exception[0] instanceof InvalidTypeException invalidTypeException) { - throw invalidTypeException; - } - else if (exception[0] instanceof RuntimeException runtimeException) { - throw runtimeException; - } - else { - LOG.error("Unexpected exception", new Throwable().initCause(exception[0])); + private void assertThreadSuspended(final ThreadReferenceProxyImpl thread, final SuspendContextImpl context) { + LOG.assertTrue(context.isEvaluating()); + try { + final boolean isSuspended = thread.isSuspended(); + LOG.assertTrue(isSuspended, thread); + } + catch (ObjectCollectedException ignored) { + } } - } + } - //noinspection unchecked - return (E)result[0]; + @Override + public Value invokeMethod( + @Nonnull EvaluationContext evaluationContext, + @Nonnull ObjectReference objRef, + @Nonnull Method method, + @Nonnull List args + ) throws EvaluateException { + return invokeInstanceMethod(evaluationContext, objRef, method, args, 0); } - private void assertThreadSuspended(final ThreadReferenceProxyImpl thread, final SuspendContextImpl context) { - LOG.assertTrue(context.isEvaluating()); - try { - final boolean isSuspended = thread.isSuspended(); - LOG.assertTrue(isSuspended, thread); - } - catch (ObjectCollectedException ignored) { - } + @Override + public Value invokeInstanceMethod( + @Nonnull EvaluationContext evaluationContext, + @Nonnull final ObjectReference objRef, + @Nonnull Method method, + @Nonnull List args, + final int invocationOptions + ) throws EvaluateException { + final ThreadReference thread = getEvaluationThread(evaluationContext); + return new InvokeCommand<>(method, args) { + @Override + protected Value invokeMethod( + int invokePolicy, + Method method, + List args + ) throws InvocationException, ClassNotLoadedException, IncompatibleThreadStateException, InvalidTypeException { + if (LOG.isDebugEnabled()) { + LOG.debug("Invoking " + objRef.type().name() + "." + method.name()); + } + return objRef.invokeMethod(thread, method, args, invokePolicy | invocationOptions); + } + }.start((EvaluationContextImpl)evaluationContext, false); } - } - - @Override - public Value invokeMethod( - @Nonnull EvaluationContext evaluationContext, - @Nonnull ObjectReference objRef, - @Nonnull Method method, - @Nonnull List args - ) throws EvaluateException { - return invokeInstanceMethod(evaluationContext, objRef, method, args, 0); - } - - @Override - public Value invokeInstanceMethod( - @Nonnull EvaluationContext evaluationContext, - @Nonnull final ObjectReference objRef, - @Nonnull Method method, - @Nonnull List args, - final int invocationOptions - ) throws EvaluateException { - final ThreadReference thread = getEvaluationThread(evaluationContext); - return new InvokeCommand<>(method, args) { - @Override - protected Value invokeMethod( - int invokePolicy, - Method method, - List args - ) throws InvocationException, ClassNotLoadedException, IncompatibleThreadStateException, InvalidTypeException { - if (LOG.isDebugEnabled()) { - LOG.debug("Invoking " + objRef.type().name() + "." + method.name()); + + private static ThreadReference getEvaluationThread(final EvaluationContext evaluationContext) throws EvaluateException { + ThreadReferenceProxy evaluationThread = evaluationContext.getSuspendContext().getThread(); + if (evaluationThread == null) { + throw EvaluateExceptionUtil.NULL_STACK_FRAME; } - return objRef.invokeMethod(thread, method, args, invokePolicy | invocationOptions); - } - }.start((EvaluationContextImpl)evaluationContext, false); - } - - private static ThreadReference getEvaluationThread(final EvaluationContext evaluationContext) throws EvaluateException { - ThreadReferenceProxy evaluationThread = evaluationContext.getSuspendContext().getThread(); - if (evaluationThread == null) { - throw EvaluateExceptionUtil.NULL_STACK_FRAME; - } - return evaluationThread.getThreadReference(); - } - - @Override - public Value invokeMethod( - EvaluationContext evaluationContext, - ClassType classType, - Method method, - List args - ) throws EvaluateException { - return invokeMethod(evaluationContext, classType, method, args, false); - } - - public Value invokeMethod( - @Nonnull EvaluationContext evaluationContext, - @Nonnull final ClassType classType, - @Nonnull Method method, - @Nonnull List args, - boolean internalEvaluate - ) throws EvaluateException { - final ThreadReference thread = getEvaluationThread(evaluationContext); - return new InvokeCommand<>(method, args) { - @Override - protected Value invokeMethod( - int invokePolicy, + return evaluationThread.getThreadReference(); + } + + @Override + public Value invokeMethod( + EvaluationContext evaluationContext, + ClassType classType, Method method, List args - ) throws InvocationException, ClassNotLoadedException, IncompatibleThreadStateException, InvalidTypeException { - if (LOG.isDebugEnabled()) { - LOG.debug("Invoking " + classType.name() + "." + method.name()); - } - return classType.invokeMethod(thread, method, args, invokePolicy); - } - }.start((EvaluationContextImpl)evaluationContext, internalEvaluate); - } - - public Value invokeMethod( - EvaluationContext evaluationContext, - InterfaceType interfaceType, - Method method, - List args - ) throws EvaluateException { - final ThreadReference thread = getEvaluationThread(evaluationContext); - return new InvokeCommand<>(method, args) { - @Override - protected Value invokeMethod( - int invokePolicy, + ) throws EvaluateException { + return invokeMethod(evaluationContext, classType, method, args, false); + } + + public Value invokeMethod( + @Nonnull EvaluationContext evaluationContext, + @Nonnull final ClassType classType, + @Nonnull Method method, + @Nonnull List args, + boolean internalEvaluate + ) throws EvaluateException { + final ThreadReference thread = getEvaluationThread(evaluationContext); + return new InvokeCommand<>(method, args) { + @Override + protected Value invokeMethod( + int invokePolicy, + Method method, + List args + ) throws InvocationException, ClassNotLoadedException, IncompatibleThreadStateException, InvalidTypeException { + if (LOG.isDebugEnabled()) { + LOG.debug("Invoking " + classType.name() + "." + method.name()); + } + return classType.invokeMethod(thread, method, args, invokePolicy); + } + }.start((EvaluationContextImpl)evaluationContext, internalEvaluate); + } + + public Value invokeMethod( + EvaluationContext evaluationContext, + InterfaceType interfaceType, Method method, List args - ) throws InvocationException, ClassNotLoadedException, IncompatibleThreadStateException, InvalidTypeException { - if (LOG.isDebugEnabled()) { - LOG.debug("Invoking " + interfaceType.name() + "." + method.name()); + ) throws EvaluateException { + final ThreadReference thread = getEvaluationThread(evaluationContext); + return new InvokeCommand<>(method, args) { + @Override + protected Value invokeMethod( + int invokePolicy, + Method method, + List args + ) throws InvocationException, ClassNotLoadedException, IncompatibleThreadStateException, InvalidTypeException { + if (LOG.isDebugEnabled()) { + LOG.debug("Invoking " + interfaceType.name() + "." + method.name()); + } + return interfaceType.invokeMethod(thread, method, args, invokePolicy); + } + }.start((EvaluationContextImpl)evaluationContext, false); + } + + + @Override + public ArrayReference newInstance(final ArrayType arrayType, final int dimension) throws EvaluateException { + try { + return arrayType.newInstance(dimension); } - return interfaceType.invokeMethod(thread, method, args, invokePolicy); - } - }.start((EvaluationContextImpl)evaluationContext, false); - } - - - @Override - public ArrayReference newInstance(final ArrayType arrayType, final int dimension) throws EvaluateException { - try { - return arrayType.newInstance(dimension); - } - catch (Exception e) { - throw EvaluateExceptionUtil.createEvaluateException(e); - } - } - - @Override - public ObjectReference newInstance( - @Nonnull final EvaluationContext evaluationContext, - @Nonnull final ClassType classType, - @Nonnull Method method, - @Nonnull List args - ) throws EvaluateException { - final ThreadReference thread = getEvaluationThread(evaluationContext); - InvokeCommand invokeCommand = new InvokeCommand<>(method, args) { - @Override - protected ObjectReference invokeMethod( - int invokePolicy, - Method method, - List args - ) throws InvocationException, ClassNotLoadedException, IncompatibleThreadStateException, InvalidTypeException { - if (LOG.isDebugEnabled()) { - LOG.debug("New instance " + classType.name() + "." + method.name()); + catch (Exception e) { + throw EvaluateExceptionUtil.createEvaluateException(e); } - return classType.newInstance(thread, method, args, invokePolicy); - } - }; - return invokeCommand.start((EvaluationContextImpl)evaluationContext, false); - } - - public void clearCashes(@MagicConstant(flagsFromClass = EventRequest.class) int suspendPolicy) { - if (!isAttached()) { - return; - } - switch (suspendPolicy) { - case EventRequest.SUSPEND_ALL: - getVirtualMachineProxy().clearCaches(); - break; - case EventRequest.SUSPEND_EVENT_THREAD: - getVirtualMachineProxy().clearCaches(); - //suspendContext.getThread().clearAll(); - break; - } - } - - protected void beforeSuspend(SuspendContextImpl suspendContext) { - clearCashes(suspendContext.getSuspendPolicy()); - } - - private void beforeMethodInvocation(SuspendContextImpl suspendContext, Method method, boolean internalEvaluate) { - if (LOG.isDebugEnabled()) { - LOG.debug( - "before invocation in thread " + suspendContext.getThread().name() + - " method " + (method == null ? "null" : method.name()) - ); - } - - if (!internalEvaluate) { - if (method != null) { - showStatusText(DebuggerBundle.message("progress.evaluating", DebuggerUtilsEx.methodName(method))); - } - else { - showStatusText(DebuggerBundle.message("title.evaluating")); - } } - } - private void afterMethodInvocation(SuspendContextImpl suspendContext, boolean internalEvaluate) { - if (LOG.isDebugEnabled()) { - LOG.debug("after invocation in thread " + suspendContext.getThread().name()); - } - if (!internalEvaluate) { - showStatusText(""); + @Override + public ObjectReference newInstance( + @Nonnull final EvaluationContext evaluationContext, + @Nonnull final ClassType classType, + @Nonnull Method method, + @Nonnull List args + ) throws EvaluateException { + final ThreadReference thread = getEvaluationThread(evaluationContext); + InvokeCommand invokeCommand = new InvokeCommand<>(method, args) { + @Override + protected ObjectReference invokeMethod( + int invokePolicy, + Method method, + List args + ) throws InvocationException, ClassNotLoadedException, IncompatibleThreadStateException, InvalidTypeException { + if (LOG.isDebugEnabled()) { + LOG.debug("New instance " + classType.name() + "." + method.name()); + } + return classType.newInstance(thread, method, args, invokePolicy); + } + }; + return invokeCommand.start((EvaluationContextImpl)evaluationContext, false); } - } - @Override - public ReferenceType findClass(EvaluationContext evaluationContext, - String className, - ClassLoaderReference classLoader) throws EvaluateException { - try { - DebuggerManagerThreadImpl.assertIsManagerThread(); - ReferenceType result = null; - for (final ReferenceType refType : getVirtualMachineProxy().classesByName(className)) { - if (refType.isPrepared() && isVisibleFromClassLoader(classLoader, refType)) { - result = refType; - break; + public void clearCashes(@MagicConstant(flagsFromClass = EventRequest.class) int suspendPolicy) { + if (!isAttached()) { + return; } - } - final EvaluationContextImpl evalContext = (EvaluationContextImpl)evaluationContext; - if (result == null && evalContext.isAutoLoadClasses()) { - return loadClass(evalContext, className, classLoader); - } - return result; - } - catch (InvocationException | InvalidTypeException | IncompatibleThreadStateException | ClassNotLoadedException e) { - throw EvaluateExceptionUtil.createEvaluateException(e); - } - } - - private static boolean isVisibleFromClassLoader(final ClassLoaderReference fromLoader, final ReferenceType refType) { - // IMPORTANT! Even if the refType is already loaded by some parent or bootstrap loader, it may not be visible from the given loader. - // For example because there were no accesses yet from this loader to this class. So the loader is not in the list of "initialing" loaders - // for this refType and the refType is not visible to the loader. - // Attempt to evaluate method with this refType will yield ClassNotLoadedException. - // The only way to say for sure whether the class is _visible_ to the given loader, is to use the following API call - return fromLoader == null || fromLoader.equals(refType.classLoader()) || fromLoader.visibleClasses().contains(refType); - } - - private static String reformatArrayName(String className) { - if (className.indexOf('[') == -1) { - return className; - } - - int dims = 0; - while (className.endsWith("[]")) { - className = className.substring(0, className.length() - 2); - dims++; - } - - StringBuilder buffer = new StringBuilder(); - StringUtil.repeatSymbol(buffer, '[', dims); - String primitiveSignature = JVMNameUtil.getPrimitiveSignature(className); - if (primitiveSignature != null) { - buffer.append(primitiveSignature); - } - else { - buffer.append('L'); - buffer.append(className); - buffer.append(';'); - } - return buffer.toString(); - } - - @SuppressWarnings({ - "HardCodedStringLiteral", - "SpellCheckingInspection" - }) - public ReferenceType loadClass(EvaluationContextImpl evaluationContext, - String qName, - ClassLoaderReference classLoader) throws InvocationException, ClassNotLoadedException, IncompatibleThreadStateException, InvalidTypeException, EvaluateException { - - DebuggerManagerThreadImpl.assertIsManagerThread(); - qName = reformatArrayName(qName); - ReferenceType refType = null; - VirtualMachineProxyImpl virtualMachine = getVirtualMachineProxy(); - ClassType classClassType = (ClassType)ContainerUtil.getFirstItem(virtualMachine.classesByName(JavaClassNames.JAVA_LANG_CLASS)); - if (classClassType != null) { - final Method forNameMethod; - List args = new ArrayList<>(); // do not use unmodifiable lists because the list is modified by JPDA - args.add(virtualMachine.mirrorOf(qName)); - if (classLoader != null) { - //forNameMethod = classClassType.concreteMethodByName("forName", "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;"); - forNameMethod = - DebuggerUtils.findMethod(classClassType, "forName", "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;"); - args.add(virtualMachine.mirrorOf(true)); - args.add(classLoader); - } - else { - //forNameMethod = classClassType.concreteMethodByName("forName", "(Ljava/lang/String;)Ljava/lang/Class;"); - forNameMethod = DebuggerUtils.findMethod(classClassType, "forName", "(Ljava/lang/String;)Ljava/lang/Class;"); - } - Value classReference = invokeMethod(evaluationContext, classClassType, forNameMethod, args); - if (classReference instanceof ClassObjectReference classObjectReference) { - refType = classObjectReference.reflectedType(); - } - } - return refType; - } - - public void logThreads() { - if (LOG.isDebugEnabled()) { - try { - Collection allThreads = getVirtualMachineProxy().allThreads(); - for (ThreadReferenceProxyImpl threadReferenceProxy : allThreads) { - LOG.debug("Thread name=" + threadReferenceProxy.name() + " suspendCount()=" + threadReferenceProxy.getSuspendCount()); + switch (suspendPolicy) { + case EventRequest.SUSPEND_ALL: + getVirtualMachineProxy().clearCaches(); + break; + case EventRequest.SUSPEND_EVENT_THREAD: + getVirtualMachineProxy().clearCaches(); + //suspendContext.getThread().clearAll(); + break; } - } - catch (Exception e) { - LOG.debug(e); - } } - } - - public void onHotSwapFinished() { - getPositionManager().clearCache(); - StackCapturingLineBreakpoint.clearCaches(this); - } - - @Nonnull - public SuspendManager getSuspendManager() { - return mySuspendManager; - } - @Nonnull - @Override - public CompoundPositionManager getPositionManager() { - return myPositionManager; - } - //ManagerCommands - - @Override - public void stop(boolean forceTerminate) { - stopConnecting(); // does this first place in case debugger manager hanged accepting debugger connection (forever) - getManagerThread().terminateAndInvoke( - createStopCommand(forceTerminate), - myProject.getApplication().isUnitTestMode() ? 0 : DebuggerManagerThreadImpl.COMMAND_TIMEOUT - ); - } - - @Nonnull - public StopCommand createStopCommand(boolean forceTerminate) { - return new StopCommand(forceTerminate); - } + protected void beforeSuspend(SuspendContextImpl suspendContext) { + clearCashes(suspendContext.getSuspendPolicy()); + } - protected class StopCommand extends DebuggerCommandImpl { - private final boolean myIsTerminateTargetVM; + private void beforeMethodInvocation(SuspendContextImpl suspendContext, Method method, boolean internalEvaluate) { + if (LOG.isDebugEnabled()) { + LOG.debug( + "before invocation in thread " + suspendContext.getThread().name() + + " method " + (method == null ? "null" : method.name()) + ); + } - public StopCommand(boolean isTerminateTargetVM) { - myIsTerminateTargetVM = isTerminateTargetVM; + if (!internalEvaluate) { + if (method != null) { + showStatusText(DebuggerBundle.message("progress.evaluating", DebuggerUtilsEx.methodName(method))); + } + else { + showStatusText(DebuggerBundle.message("title.evaluating")); + } + } } - @Override - public Priority getPriority() { - return Priority.HIGH; + private void afterMethodInvocation(SuspendContextImpl suspendContext, boolean internalEvaluate) { + if (LOG.isDebugEnabled()) { + LOG.debug("after invocation in thread " + suspendContext.getThread().name()); + } + if (!internalEvaluate) { + showStatusText(""); + } } @Override - protected void action() throws Exception { - if (isAttached()) { - final VirtualMachineProxyImpl virtualMachineProxy = getVirtualMachineProxy(); - if (myIsTerminateTargetVM) { - virtualMachineProxy.exit(-1); - } - else { - // some VMs (like IBM VM 1.4.2 bundled with WebSphere) does not resume threads on dispose() like it should - try { - virtualMachineProxy.resume(); - } - finally { - virtualMachineProxy.dispose(); - } - } - } - else { + public ReferenceType findClass(EvaluationContext evaluationContext, String className, ClassLoaderReference classLoader) + throws EvaluateException { try { - stopConnecting(); + DebuggerManagerThreadImpl.assertIsManagerThread(); + ReferenceType result = null; + for (final ReferenceType refType : getVirtualMachineProxy().classesByName(className)) { + if (refType.isPrepared() && isVisibleFromClassLoader(classLoader, refType)) { + result = refType; + break; + } + } + final EvaluationContextImpl evalContext = (EvaluationContextImpl)evaluationContext; + if (result == null && evalContext.isAutoLoadClasses()) { + return loadClass(evalContext, className, classLoader); + } + return result; } - finally { - closeProcess(true); + catch (InvocationException | InvalidTypeException | IncompatibleThreadStateException | ClassNotLoadedException e) { + throw EvaluateExceptionUtil.createEvaluateException(e); } - } } - } - - private class StepOutCommand extends StepCommand { - private final int myStepSize; - public StepOutCommand(SuspendContextImpl suspendContext, int stepSize) { - super(suspendContext); - myStepSize = stepSize; + private static boolean isVisibleFromClassLoader(final ClassLoaderReference fromLoader, final ReferenceType refType) { + // IMPORTANT! Even if the refType is already loaded by some parent or bootstrap loader, it may not be visible from the given loader. + // For example because there were no accesses yet from this loader to this class. So the loader is not in the list of "initialing" loaders + // for this refType and the refType is not visible to the loader. + // Attempt to evaluate method with this refType will yield ClassNotLoadedException. + // The only way to say for sure whether the class is _visible_ to the given loader, is to use the following API call + return fromLoader == null || fromLoader.equals(refType.classLoader()) || fromLoader.visibleClasses().contains(refType); } - @Override - public void contextAction(@Nonnull SuspendContextImpl suspendContext) { - showStatusText(DebuggerBundle.message("status.step.out")); - final ThreadReferenceProxyImpl thread = getContextThread(); - RequestHint hint = new RequestHint(thread, suspendContext, StepRequest.STEP_OUT); - hint.setIgnoreFilters(mySession.shouldIgnoreSteppingFilters()); - applyThreadFilter(thread); - final MethodReturnValueWatcher rvWatcher = myReturnValueWatcher; - if (rvWatcher != null) { - rvWatcher.enable(thread.getThreadReference()); - } - doStep(suspendContext, thread, myStepSize, StepRequest.STEP_OUT, hint); - super.contextAction(suspendContext); - } - } - - private class StepIntoCommand extends StepCommand { - private final boolean myForcedIgnoreFilters; - private final MethodFilter mySmartStepFilter; - @Nullable - private final StepIntoBreakpoint myBreakpoint; - private final int myStepSize; + private static String reformatArrayName(String className) { + if (className.indexOf('[') == -1) { + return className; + } - public StepIntoCommand(SuspendContextImpl suspendContext, - boolean ignoreFilters, - @Nullable final MethodFilter methodFilter, - int stepSize) { - super(suspendContext); - myForcedIgnoreFilters = ignoreFilters || methodFilter != null; - mySmartStepFilter = methodFilter; - myBreakpoint = methodFilter instanceof BreakpointStepMethodFilter breakpointStepMethodFilter - ? DebuggerManagerEx.getInstanceEx(myProject).getBreakpointManager().addStepIntoBreakpoint(breakpointStepMethodFilter) : null; - myStepSize = stepSize; - } + int dims = 0; + while (className.endsWith("[]")) { + className = className.substring(0, className.length() - 2); + dims++; + } - @Override - public void contextAction(@Nonnull SuspendContextImpl suspendContext) { - showStatusText(DebuggerBundle.message("status.step.into")); - final ThreadReferenceProxyImpl stepThread = getContextThread(); - final RequestHint hint = mySmartStepFilter != null ? new RequestHint(stepThread, suspendContext, mySmartStepFilter) : new RequestHint( - stepThread, - suspendContext, - StepRequest.STEP_INTO); - hint.setResetIgnoreFilters(mySmartStepFilter != null && !mySession.shouldIgnoreSteppingFilters()); - if (myForcedIgnoreFilters) { - try { - mySession.setIgnoreStepFiltersFlag(stepThread.frameCount()); + StringBuilder buffer = new StringBuilder(); + StringUtil.repeatSymbol(buffer, '[', dims); + String primitiveSignature = JVMNameUtil.getPrimitiveSignature(className); + if (primitiveSignature != null) { + buffer.append(primitiveSignature); } - catch (EvaluateException e) { - LOG.info(e); + else { + buffer.append('L'); + buffer.append(className); + buffer.append(';'); + } + return buffer.toString(); + } + + @SuppressWarnings({"HardCodedStringLiteral", "SpellCheckingInspection"}) + public ReferenceType loadClass(EvaluationContextImpl evaluationContext, String qName, ClassLoaderReference classLoader) + throws InvocationException, ClassNotLoadedException, IncompatibleThreadStateException, InvalidTypeException, EvaluateException { + DebuggerManagerThreadImpl.assertIsManagerThread(); + qName = reformatArrayName(qName); + ReferenceType refType = null; + VirtualMachineProxyImpl virtualMachine = getVirtualMachineProxy(); + ClassType classClassType = (ClassType)ContainerUtil.getFirstItem(virtualMachine.classesByName(JavaClassNames.JAVA_LANG_CLASS)); + if (classClassType != null) { + final Method forNameMethod; + List args = new ArrayList<>(); // do not use unmodifiable lists because the list is modified by JPDA + args.add(virtualMachine.mirrorOf(qName)); + if (classLoader != null) { + //forNameMethod = classClassType.concreteMethodByName("forName", "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;"); + forNameMethod = + DebuggerUtils.findMethod(classClassType, "forName", "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;"); + args.add(virtualMachine.mirrorOf(true)); + args.add(classLoader); + } + else { + //forNameMethod = classClassType.concreteMethodByName("forName", "(Ljava/lang/String;)Ljava/lang/Class;"); + forNameMethod = DebuggerUtils.findMethod(classClassType, "forName", "(Ljava/lang/String;)Ljava/lang/Class;"); + } + Value classReference = invokeMethod(evaluationContext, classClassType, forNameMethod, args); + if (classReference instanceof ClassObjectReference classObjectReference) { + refType = classObjectReference.reflectedType(); + } } - } - hint.setIgnoreFilters(myForcedIgnoreFilters || mySession.shouldIgnoreSteppingFilters()); - applyThreadFilter(stepThread); - if (myBreakpoint != null) { - myBreakpoint.setSuspendPolicy(suspendContext.getSuspendPolicy() == EventRequest.SUSPEND_EVENT_THREAD ? DebuggerSettings.SUSPEND_THREAD : DebuggerSettings.SUSPEND_ALL); - myBreakpoint.createRequest(suspendContext.getDebugProcess()); - myBreakpoint.setRequestHint(hint); - setRunToCursorBreakpoint(myBreakpoint); - } - doStep(suspendContext, stepThread, myStepSize, StepRequest.STEP_INTO, hint); - super.contextAction(suspendContext); + return refType; } - } - public class StepOverCommand extends StepCommand { - private final boolean myIsIgnoreBreakpoints; - private final int myStepSize; - - public StepOverCommand(SuspendContextImpl suspendContext, boolean ignoreBreakpoints, int stepSize) { - super(suspendContext); - myIsIgnoreBreakpoints = ignoreBreakpoints; - myStepSize = stepSize; + public void logThreads() { + if (LOG.isDebugEnabled()) { + try { + Collection allThreads = getVirtualMachineProxy().allThreads(); + for (ThreadReferenceProxyImpl threadReferenceProxy : allThreads) { + LOG.debug("Thread name=" + threadReferenceProxy.name() + " suspendCount()=" + threadReferenceProxy.getSuspendCount()); + } + } + catch (Exception e) { + LOG.debug(e); + } + } } - protected int getStepSize() { - return StepRequest.STEP_OVER; + public void onHotSwapFinished() { + getPositionManager().clearCache(); + StackCapturingLineBreakpoint.clearCaches(this); } @Nonnull - protected String getStatusText() { - return DebuggerBundle.message("status.step.over"); + public SuspendManager getSuspendManager() { + return mySuspendManager; } @Nonnull - protected RequestHint getHint(SuspendContextImpl suspendContext, ThreadReferenceProxyImpl stepThread) { - // need this hint while stepping over for JSR45 support: - // several lines of generated java code may correspond to a single line in the source file, - // from which the java code was generated - RequestHint hint = new RequestHint(stepThread, suspendContext, StepRequest.STEP_OVER); - hint.setRestoreBreakpoints(myIsIgnoreBreakpoints); - hint.setIgnoreFilters(myIsIgnoreBreakpoints || mySession.shouldIgnoreSteppingFilters()); - return hint; + @Override + public CompoundPositionManager getPositionManager() { + return myPositionManager; } + //ManagerCommands @Override - public void contextAction(@Nonnull SuspendContextImpl suspendContext) { - showStatusText(getStatusText()); - - ThreadReferenceProxyImpl stepThread = getContextThread(); + public void stop(boolean forceTerminate) { + stopConnecting(); // does this first place in case debugger manager hanged accepting debugger connection (forever) + getManagerThread().terminateAndInvoke( + createStopCommand(forceTerminate), + myProject.getApplication().isUnitTestMode() ? 0 : DebuggerManagerThreadImpl.COMMAND_TIMEOUT + ); + } - RequestHint hint = getHint(suspendContext, stepThread); + @Nonnull + public StopCommand createStopCommand(boolean forceTerminate) { + return new StopCommand(forceTerminate); + } - applyThreadFilter(stepThread); + protected class StopCommand extends DebuggerCommandImpl { + private final boolean myIsTerminateTargetVM; - final MethodReturnValueWatcher rvWatcher = myReturnValueWatcher; - if (rvWatcher != null) { - rvWatcher.enable(stepThread.getThreadReference()); - } + public StopCommand(boolean isTerminateTargetVM) { + myIsTerminateTargetVM = isTerminateTargetVM; + } - doStep(suspendContext, stepThread, myStepSize, getStepSize(), hint); + @Override + public Priority getPriority() { + return Priority.HIGH; + } - if (myIsIgnoreBreakpoints) { - DebuggerManagerEx.getInstanceEx(myProject).getBreakpointManager().disableBreakpoints(DebugProcessImpl.this); - } - super.contextAction(suspendContext); + @Override + protected void action() throws Exception { + if (isAttached()) { + final VirtualMachineProxyImpl virtualMachineProxy = getVirtualMachineProxy(); + if (myIsTerminateTargetVM) { + virtualMachineProxy.exit(-1); + } + else { + // some VMs (like IBM VM 1.4.2 bundled with WebSphere) does not resume threads on dispose() like it should + try { + virtualMachineProxy.resume(); + } + finally { + virtualMachineProxy.dispose(); + } + } + } + else { + try { + stopConnecting(); + } + finally { + closeProcess(true); + } + } + } } - } - private class RunToCursorCommand extends StepCommand { - private final RunToCursorBreakpoint myRunToCursorBreakpoint; - private final boolean myIgnoreBreakpoints; + private class StepOutCommand extends StepCommand { + private final int myStepSize; - private RunToCursorCommand(SuspendContextImpl suspendContext, @Nonnull XSourcePosition position, final boolean ignoreBreakpoints) { - super(suspendContext); - myIgnoreBreakpoints = ignoreBreakpoints; - myRunToCursorBreakpoint = - DebuggerManagerEx.getInstanceEx(myProject).getBreakpointManager().addRunToCursorBreakpoint(position, ignoreBreakpoints); - } + public StepOutCommand(SuspendContextImpl suspendContext, int stepSize) { + super(suspendContext); + myStepSize = stepSize; + } - @Override - public void contextAction(@Nonnull SuspendContextImpl suspendContext) { - showStatusText(DebuggerBundle.message("status.run.to.cursor")); - cancelRunToCursorBreakpoint(); - if (myRunToCursorBreakpoint == null) { - return; - } - if (myIgnoreBreakpoints) { - DebuggerManagerEx.getInstanceEx(myProject).getBreakpointManager().disableBreakpoints(DebugProcessImpl.this); - } - applyThreadFilter(getContextThread()); - final SuspendContextImpl context = getSuspendContext(); - myRunToCursorBreakpoint.setSuspendPolicy( - context.getSuspendPolicy() == EventRequest.SUSPEND_EVENT_THREAD ? DebuggerSettings.SUSPEND_THREAD : DebuggerSettings.SUSPEND_ALL - ); - final DebugProcessImpl debugProcess = context.getDebugProcess(); - myRunToCursorBreakpoint.createRequest(debugProcess); - setRunToCursorBreakpoint(myRunToCursorBreakpoint); - - if (debugProcess.getRequestsManager().getWarning(myRunToCursorBreakpoint) == null) { - super.contextAction(suspendContext); - } - else { - DebuggerInvocationUtil.swingInvokeLater(myProject, () -> - { - Messages.showErrorDialog( - DebuggerBundle.message( - "error.running.to.cursor.no.executable.code", - myRunToCursorBreakpoint.getSourcePosition().getFile().getName(), - myRunToCursorBreakpoint.getLineIndex() + 1 - ), - ActionLocalize.actionRuntocursorText().map(Presentation.NO_MNEMONIC).get() - ); - DebuggerSession session = debugProcess.getSession(); - session.getContextManager().setState( - DebuggerContextUtil.createDebuggerContext(session, context), - DebuggerSession.State.PAUSED, - DebuggerSession.Event.CONTEXT, - null - ); - }); - } + @Override + public void contextAction(@Nonnull SuspendContextImpl suspendContext) { + showStatusText(DebuggerBundle.message("status.step.out")); + final ThreadReferenceProxyImpl thread = getContextThread(); + RequestHint hint = new RequestHint(thread, suspendContext, StepRequest.STEP_OUT); + hint.setIgnoreFilters(mySession.shouldIgnoreSteppingFilters()); + applyThreadFilter(thread); + final MethodReturnValueWatcher rvWatcher = myReturnValueWatcher; + if (rvWatcher != null) { + rvWatcher.enable(thread.getThreadReference()); + } + doStep(suspendContext, thread, myStepSize, StepRequest.STEP_OUT, hint); + super.contextAction(suspendContext); + } + } + + private class StepIntoCommand extends StepCommand { + private final boolean myForcedIgnoreFilters; + private final MethodFilter mySmartStepFilter; + @Nullable + private final StepIntoBreakpoint myBreakpoint; + private final int myStepSize; + + public StepIntoCommand( + SuspendContextImpl suspendContext, + boolean ignoreFilters, + @Nullable final MethodFilter methodFilter, + int stepSize + ) { + super(suspendContext); + myForcedIgnoreFilters = ignoreFilters || methodFilter != null; + mySmartStepFilter = methodFilter; + myBreakpoint = methodFilter instanceof BreakpointStepMethodFilter breakpointStepMethodFilter + ? DebuggerManagerEx.getInstanceEx(myProject) + .getBreakpointManager() + .addStepIntoBreakpoint(breakpointStepMethodFilter) : null; + myStepSize = stepSize; + } + + @Override + public void contextAction(@Nonnull SuspendContextImpl suspendContext) { + showStatusText(DebuggerBundle.message("status.step.into")); + final ThreadReferenceProxyImpl stepThread = getContextThread(); + final RequestHint hint = mySmartStepFilter != null + ? new RequestHint(stepThread, suspendContext, mySmartStepFilter) + : new RequestHint(stepThread, suspendContext, StepRequest.STEP_INTO); + hint.setResetIgnoreFilters(mySmartStepFilter != null && !mySession.shouldIgnoreSteppingFilters()); + if (myForcedIgnoreFilters) { + try { + mySession.setIgnoreStepFiltersFlag(stepThread.frameCount()); + } + catch (EvaluateException e) { + LOG.info(e); + } + } + hint.setIgnoreFilters(myForcedIgnoreFilters || mySession.shouldIgnoreSteppingFilters()); + applyThreadFilter(stepThread); + if (myBreakpoint != null) { + myBreakpoint.setSuspendPolicy(suspendContext.getSuspendPolicy() == EventRequest.SUSPEND_EVENT_THREAD ? DebuggerSettings.SUSPEND_THREAD : DebuggerSettings.SUSPEND_ALL); + myBreakpoint.createRequest(suspendContext.getDebugProcess()); + myBreakpoint.setRequestHint(hint); + setRunToCursorBreakpoint(myBreakpoint); + } + doStep(suspendContext, stepThread, myStepSize, StepRequest.STEP_INTO, hint); + super.contextAction(suspendContext); + } } - } - private abstract class StepCommand extends ResumeCommand { - public StepCommand(SuspendContextImpl suspendContext) { - super(suspendContext); - } + public class StepOverCommand extends StepCommand { + private final boolean myIsIgnoreBreakpoints; + private final int myStepSize; - @Override - protected void resumeAction() { - SuspendContextImpl context = getSuspendContext(); - if (context != null && (context.getSuspendPolicy() == EventRequest.SUSPEND_EVENT_THREAD || isResumeOnlyCurrentThread())) { - myThreadBlockedMonitor.startWatching(myContextThread); - } - if (context != null && isResumeOnlyCurrentThread() && context.getSuspendPolicy() == EventRequest.SUSPEND_ALL && myContextThread != null) { - getSuspendManager().resumeThread(context, myContextThread); - } - else { - super.resumeAction(); - } - } - } + public StepOverCommand(SuspendContextImpl suspendContext, boolean ignoreBreakpoints, int stepSize) { + super(suspendContext); + myIsIgnoreBreakpoints = ignoreBreakpoints; + myStepSize = stepSize; + } - public abstract class ResumeCommand extends SuspendContextCommandImpl { - @Nullable - protected final ThreadReferenceProxyImpl myContextThread; + protected int getStepSize() { + return StepRequest.STEP_OVER; + } - public ResumeCommand(SuspendContextImpl suspendContext) { - super(suspendContext); - final ThreadReferenceProxyImpl contextThread = getDebuggerContext().getThreadProxy(); - myContextThread = contextThread != null ? contextThread : (suspendContext != null ? suspendContext.getThread() : null); - } + @Nonnull + protected String getStatusText() { + return DebuggerBundle.message("status.step.over"); + } - @Override - public Priority getPriority() { - return Priority.HIGH; - } + @Nonnull + protected RequestHint getHint(SuspendContextImpl suspendContext, ThreadReferenceProxyImpl stepThread) { + // need this hint while stepping over for JSR45 support: + // several lines of generated java code may correspond to a single line in the source file, + // from which the java code was generated + RequestHint hint = new RequestHint(stepThread, suspendContext, StepRequest.STEP_OVER); + hint.setRestoreBreakpoints(myIsIgnoreBreakpoints); + hint.setIgnoreFilters(myIsIgnoreBreakpoints || mySession.shouldIgnoreSteppingFilters()); + return hint; + } - @Override - public void contextAction(@Nonnull SuspendContextImpl suspendContext) { - showStatusText(DebuggerBundle.message("status.process.resumed")); - resumeAction(); - myDebugProcessDispatcher.getMulticaster().resumed(getSuspendContext()); - } + @Override + public void contextAction(@Nonnull SuspendContextImpl suspendContext) { + showStatusText(getStatusText()); - protected void resumeAction() { - getSuspendManager().resume(getSuspendContext()); - } + ThreadReferenceProxyImpl stepThread = getContextThread(); - @Nullable - public ThreadReferenceProxyImpl getContextThread() { - return myContextThread; - } + RequestHint hint = getHint(suspendContext, stepThread); - protected void applyThreadFilter(ThreadReferenceProxy thread) { - if (getSuspendContext().getSuspendPolicy() == EventRequest.SUSPEND_ALL) { - // there could be explicit resume as a result of call to voteSuspend() - // e.g. when breakpoint was considered invalid, in that case the filter will be applied _after_ - // resuming and all breakpoints in other threads will be ignored. - // As resume() implicitly cleares the filter, the filter must be always applied _before_ any resume() action happens - final BreakpointManager breakpointManager = DebuggerManagerEx.getInstanceEx(getProject()).getBreakpointManager(); - breakpointManager.applyThreadFilter(DebugProcessImpl.this, thread.getThreadReference()); - } - } - } + applyThreadFilter(stepThread); - private class PauseCommand extends DebuggerCommandImpl { - public PauseCommand() { + final MethodReturnValueWatcher rvWatcher = myReturnValueWatcher; + if (rvWatcher != null) { + rvWatcher.enable(stepThread.getThreadReference()); + } + + doStep(suspendContext, stepThread, myStepSize, getStepSize(), hint); + + if (myIsIgnoreBreakpoints) { + DebuggerManagerEx.getInstanceEx(myProject).getBreakpointManager().disableBreakpoints(DebugProcessImpl.this); + } + super.contextAction(suspendContext); + } } - @Override - public void action() { - if (!isAttached() || getVirtualMachineProxy().isPausePressed()) { - return; - } - logThreads(); - getVirtualMachineProxy().suspend(); - logThreads(); - SuspendContextImpl suspendContext = mySuspendManager.pushSuspendContext(EventRequest.SUSPEND_ALL, 0); - myDebugProcessDispatcher.getMulticaster().paused(suspendContext); + private class RunToCursorCommand extends StepCommand { + private final RunToCursorBreakpoint myRunToCursorBreakpoint; + private final boolean myIgnoreBreakpoints; + + private RunToCursorCommand(SuspendContextImpl suspendContext, @Nonnull XSourcePosition position, final boolean ignoreBreakpoints) { + super(suspendContext); + myIgnoreBreakpoints = ignoreBreakpoints; + myRunToCursorBreakpoint = + DebuggerManagerEx.getInstanceEx(myProject).getBreakpointManager().addRunToCursorBreakpoint(position, ignoreBreakpoints); + } + + @Override + public void contextAction(@Nonnull SuspendContextImpl suspendContext) { + showStatusText(DebuggerBundle.message("status.run.to.cursor")); + cancelRunToCursorBreakpoint(); + if (myRunToCursorBreakpoint == null) { + return; + } + if (myIgnoreBreakpoints) { + DebuggerManagerEx.getInstanceEx(myProject).getBreakpointManager().disableBreakpoints(DebugProcessImpl.this); + } + applyThreadFilter(getContextThread()); + final SuspendContextImpl context = getSuspendContext(); + myRunToCursorBreakpoint.setSuspendPolicy( + context.getSuspendPolicy() == EventRequest.SUSPEND_EVENT_THREAD ? DebuggerSettings.SUSPEND_THREAD : DebuggerSettings.SUSPEND_ALL + ); + final DebugProcessImpl debugProcess = context.getDebugProcess(); + myRunToCursorBreakpoint.createRequest(debugProcess); + setRunToCursorBreakpoint(myRunToCursorBreakpoint); + + if (debugProcess.getRequestsManager().getWarning(myRunToCursorBreakpoint) == null) { + super.contextAction(suspendContext); + } + else { + DebuggerInvocationUtil.swingInvokeLater(myProject, () -> + { + Messages.showErrorDialog( + DebuggerBundle.message( + "error.running.to.cursor.no.executable.code", + myRunToCursorBreakpoint.getSourcePosition().getFile().getName(), + myRunToCursorBreakpoint.getLineIndex() + 1 + ), + ActionLocalize.actionRuntocursorText().map(Presentation.NO_MNEMONIC).get() + ); + DebuggerSession session = debugProcess.getSession(); + session.getContextManager().setState( + DebuggerContextUtil.createDebuggerContext(session, context), + DebuggerSession.State.PAUSED, + DebuggerSession.Event.CONTEXT, + null + ); + }); + } + } } - } - private class ResumeThreadCommand extends SuspendContextCommandImpl { - private final ThreadReferenceProxyImpl myThread; + private abstract class StepCommand extends ResumeCommand { + public StepCommand(SuspendContextImpl suspendContext) { + super(suspendContext); + } - public ResumeThreadCommand(SuspendContextImpl suspendContext, @Nonnull ThreadReferenceProxyImpl thread) { - super(suspendContext); - myThread = thread; + @Override + protected void resumeAction() { + SuspendContextImpl context = getSuspendContext(); + if (context != null && (context.getSuspendPolicy() == EventRequest.SUSPEND_EVENT_THREAD || isResumeOnlyCurrentThread())) { + myThreadBlockedMonitor.startWatching(myContextThread); + } + if (context != null && isResumeOnlyCurrentThread() && context.getSuspendPolicy() == EventRequest.SUSPEND_ALL && myContextThread != null) { + getSuspendManager().resumeThread(context, myContextThread); + } + else { + super.resumeAction(); + } + } } - @Override - public void contextAction() { - // handle unfreeze through the regular context resume - if (false && getSuspendManager().isFrozen(myThread)) { - getSuspendManager().unfreezeThread(myThread); - return; - } + public abstract class ResumeCommand extends SuspendContextCommandImpl { + @Nullable + protected final ThreadReferenceProxyImpl myContextThread; - final Set suspendingContexts = SuspendManagerUtil.getSuspendingContexts(getSuspendManager(), myThread); - for (SuspendContextImpl suspendContext : suspendingContexts) { - if (suspendContext.getSuspendPolicy() == EventRequest.SUSPEND_EVENT_THREAD && suspendContext.getThread() == myThread) { - getSession().getXDebugSession().sessionResumed(); - getManagerThread().invoke(createResumeCommand(suspendContext)); + public ResumeCommand(SuspendContextImpl suspendContext) { + super(suspendContext); + final ThreadReferenceProxyImpl contextThread = getDebuggerContext().getThreadProxy(); + myContextThread = contextThread != null ? contextThread : (suspendContext != null ? suspendContext.getThread() : null); } - else { - getSuspendManager().resumeThread(suspendContext, myThread); + + @Override + public Priority getPriority() { + return Priority.HIGH; + } + + @Override + public void contextAction(@Nonnull SuspendContextImpl suspendContext) { + showStatusText(DebuggerBundle.message("status.process.resumed")); + resumeAction(); + myDebugProcessDispatcher.getMulticaster().resumed(getSuspendContext()); + } + + protected void resumeAction() { + getSuspendManager().resume(getSuspendContext()); + } + + @Nullable + public ThreadReferenceProxyImpl getContextThread() { + return myContextThread; + } + + protected void applyThreadFilter(ThreadReferenceProxy thread) { + if (getSuspendContext().getSuspendPolicy() == EventRequest.SUSPEND_ALL) { + // there could be explicit resume as a result of call to voteSuspend() + // e.g. when breakpoint was considered invalid, in that case the filter will be applied _after_ + // resuming and all breakpoints in other threads will be ignored. + // As resume() implicitly cleares the filter, the filter must be always applied _before_ any resume() action happens + final BreakpointManager breakpointManager = DebuggerManagerEx.getInstanceEx(getProject()).getBreakpointManager(); + breakpointManager.applyThreadFilter(DebugProcessImpl.this, thread.getThreadReference()); + } } - } } - } - private class FreezeThreadCommand extends DebuggerCommandImpl { - private final ThreadReferenceProxyImpl myThread; + private class PauseCommand extends DebuggerCommandImpl { + public PauseCommand() { + } - public FreezeThreadCommand(ThreadReferenceProxyImpl thread) { - myThread = thread; + @Override + public void action() { + if (!isAttached() || getVirtualMachineProxy().isPausePressed()) { + return; + } + logThreads(); + getVirtualMachineProxy().suspend(); + logThreads(); + SuspendContextImpl suspendContext = mySuspendManager.pushSuspendContext(EventRequest.SUSPEND_ALL, 0); + myDebugProcessDispatcher.getMulticaster().paused(suspendContext); + } } - @Override - protected void action() throws Exception { - SuspendManager suspendManager = getSuspendManager(); - if (!suspendManager.isFrozen(myThread)) { - suspendManager.freezeThread(myThread); - SuspendContextImpl suspendContext = mySuspendManager.pushSuspendContext(EventRequest.SUSPEND_EVENT_THREAD, 0); - suspendContext.setThread(myThread.getThreadReference()); - myDebugProcessDispatcher.getMulticaster().paused(suspendContext); - } + private class ResumeThreadCommand extends SuspendContextCommandImpl { + private final ThreadReferenceProxyImpl myThread; + + public ResumeThreadCommand(SuspendContextImpl suspendContext, @Nonnull ThreadReferenceProxyImpl thread) { + super(suspendContext); + myThread = thread; + } + + @Override + public void contextAction() { + // handle unfreeze through the regular context resume + if (false && getSuspendManager().isFrozen(myThread)) { + getSuspendManager().unfreezeThread(myThread); + return; + } + + final Set suspendingContexts = SuspendManagerUtil.getSuspendingContexts(getSuspendManager(), myThread); + for (SuspendContextImpl suspendContext : suspendingContexts) { + if (suspendContext.getSuspendPolicy() == EventRequest.SUSPEND_EVENT_THREAD && suspendContext.getThread() == myThread) { + getSession().getXDebugSession().sessionResumed(); + getManagerThread().invoke(createResumeCommand(suspendContext)); + } + else { + getSuspendManager().resumeThread(suspendContext, myThread); + } + } + } } - } - private class PopFrameCommand extends DebuggerContextCommandImpl { - private final StackFrameProxyImpl myStackFrame; + private class FreezeThreadCommand extends DebuggerCommandImpl { + private final ThreadReferenceProxyImpl myThread; + + public FreezeThreadCommand(ThreadReferenceProxyImpl thread) { + myThread = thread; + } - public PopFrameCommand(DebuggerContextImpl context, StackFrameProxyImpl frameProxy) { - super(context, frameProxy.threadProxy()); - myStackFrame = frameProxy; + @Override + protected void action() throws Exception { + SuspendManager suspendManager = getSuspendManager(); + if (!suspendManager.isFrozen(myThread)) { + suspendManager.freezeThread(myThread); + SuspendContextImpl suspendContext = mySuspendManager.pushSuspendContext(EventRequest.SUSPEND_EVENT_THREAD, 0); + suspendContext.setThread(myThread.getThreadReference()); + myDebugProcessDispatcher.getMulticaster().paused(suspendContext); + } + } } - @Override - public void threadAction(@Nonnull SuspendContextImpl suspendContext) { - final ThreadReferenceProxyImpl thread = myStackFrame.threadProxy(); - try { - if (!getSuspendManager().isSuspended(thread)) { - notifyCancelled(); - return; + private class PopFrameCommand extends DebuggerContextCommandImpl { + private final StackFrameProxyImpl myStackFrame; + + public PopFrameCommand(DebuggerContextImpl context, StackFrameProxyImpl frameProxy) { + super(context, frameProxy.threadProxy()); + myStackFrame = frameProxy; } - } - catch (ObjectCollectedException ignored) { - notifyCancelled(); - return; - } - if (!suspendContext.suspends(thread)) { - suspendContext.postponeCommand(this); - return; - } + @Override + public void threadAction(@Nonnull SuspendContextImpl suspendContext) { + final ThreadReferenceProxyImpl thread = myStackFrame.threadProxy(); + try { + if (!getSuspendManager().isSuspended(thread)) { + notifyCancelled(); + return; + } + } + catch (ObjectCollectedException ignored) { + notifyCancelled(); + return; + } - if (myStackFrame.isBottom()) { - DebuggerInvocationUtil.swingInvokeLater( - myProject, - () -> Messages.showMessageDialog( - myProject, - DebuggerBundle.message("error.pop.bottom.stackframe"), - ActionsBundle.actionText("Debugger.PopFrame"), - UIUtil.getErrorIcon() - ) - ); - return; - } + if (!suspendContext.suspends(thread)) { + suspendContext.postponeCommand(this); + return; + } - try { - thread.popFrames(myStackFrame); - } - catch (final EvaluateException e) { - DebuggerInvocationUtil.swingInvokeLater( - myProject, - () -> Messages.showMessageDialog( - myProject, - DebuggerBundle.message("error.pop.stackframe", e.getLocalizedMessage()), - ActionsBundle.actionText(DebuggerActions.POP_FRAME), - UIUtil.getErrorIcon() - ) - ); - LOG.info(e); - } - finally { - getSuspendManager().popFrame(suspendContext); - } + if (myStackFrame.isBottom()) { + DebuggerInvocationUtil.swingInvokeLater( + myProject, + () -> Messages.showMessageDialog( + myProject, + DebuggerBundle.message("error.pop.bottom.stackframe"), + ActionsBundle.actionText("Debugger.PopFrame"), + UIUtil.getErrorIcon() + ) + ); + return; + } + + try { + thread.popFrames(myStackFrame); + } + catch (final EvaluateException e) { + DebuggerInvocationUtil.swingInvokeLater( + myProject, + () -> Messages.showMessageDialog( + myProject, + DebuggerBundle.message("error.pop.stackframe", e.getLocalizedMessage()), + ActionsBundle.actionText(DebuggerActions.POP_FRAME), + UIUtil.getErrorIcon() + ) + ); + LOG.info(e); + } + finally { + getSuspendManager().popFrame(suspendContext); + } + } } - } - - @Override - @Nonnull - public GlobalSearchScope getSearchScope() { - LOG.assertTrue(mySession != null, "Accessing debug session before its initialization"); - return mySession.getSearchScope(); - } - - public void reattach(final DebugEnvironment environment) throws ExecutionException { - getManagerThread().schedule(new DebuggerCommandImpl() { - @Override - protected void action() throws Exception { - closeProcess(false); - doReattach(); - } - @Override - protected void commandCancelled() { - doReattach(); // if the original process is already finished - } + @Override + @Nonnull + public GlobalSearchScope getSearchScope() { + LOG.assertTrue(mySession != null, "Accessing debug session before its initialization"); + return mySession.getSearchScope(); + } + + public void reattach(final DebugEnvironment environment) throws ExecutionException { + getManagerThread().schedule(new DebuggerCommandImpl() { + @Override + protected void action() throws Exception { + closeProcess(false); + doReattach(); + } + + @Override + protected void commandCancelled() { + doReattach(); // if the original process is already finished + } - private void doReattach() { - DebuggerInvocationUtil.swingInvokeLater(myProject, () -> - { - getXdebugProcess().getSession().resetBreakpoints(); - myState.set(State.INITIAL); - myConnection = environment.getRemoteConnection(); - getManagerThread().restartIfNeeded(); - createVirtualMachine(environment); + private void doReattach() { + DebuggerInvocationUtil.swingInvokeLater(myProject, () -> + { + getXdebugProcess().getSession().resetBreakpoints(); + myState.set(State.INITIAL); + myConnection = environment.getRemoteConnection(); + getManagerThread().restartIfNeeded(); + createVirtualMachine(environment); + }); + } }); - } - }); - } + } - @Nullable - @RequiredUIAccess - public ExecutionResult attachVirtualMachine(final DebugEnvironment environment, final DebuggerSession session) throws ExecutionException { - mySession = session; - myWaitFor.down(); + @Nullable + @RequiredUIAccess + public ExecutionResult attachVirtualMachine(final DebugEnvironment environment, final DebuggerSession session) + throws ExecutionException { + mySession = session; + myWaitFor.down(); - myProject.getApplication().assertIsDispatchThread(); - LOG.assertTrue(isInInitialState()); + myProject.getApplication().assertIsDispatchThread(); + LOG.assertTrue(isInInitialState()); - myConnection = environment.getRemoteConnection(); + myConnection = environment.getRemoteConnection(); - createVirtualMachine(environment); + createVirtualMachine(environment); - ExecutionResult executionResult; - try { - synchronized (myProcessListeners) { - executionResult = environment.createExecutionResult(); - myExecutionResult = executionResult; - if (executionResult == null) { - fail(); - return null; + ExecutionResult executionResult; + try { + synchronized (myProcessListeners) { + executionResult = environment.createExecutionResult(); + myExecutionResult = executionResult; + if (executionResult == null) { + fail(); + return null; + } + for (ProcessListener processListener : myProcessListeners) { + executionResult.getProcessHandler().addProcessListener(processListener); + } + myProcessListeners.clear(); + } } - for (ProcessListener processListener : myProcessListeners) { - executionResult.getProcessHandler().addProcessListener(processListener); + catch (ExecutionException e) { + fail(); + throw e; } - myProcessListeners.clear(); - } - } - catch (ExecutionException e) { - fail(); - throw e; - } - // writing to volatile field ensures the other threads will see the right values in non-volatile fields + // writing to volatile field ensures the other threads will see the right values in non-volatile fields - if (myProject.getApplication().isUnitTestMode()) { - return executionResult; - } + if (myProject.getApplication().isUnitTestMode()) { + return executionResult; + } /* final Alarm debugPortTimeout = new Alarm(Alarm.ThreadToUse.SHARED_THREAD); @@ -2004,294 +2010,297 @@ public void run() { }); */ - return executionResult; - } - - private void fail() { - // need this in order to prevent calling stop() twice - if (myIsFailed.compareAndSet(false, true)) { - stop(false); - } - } - - private void createVirtualMachine(final DebugEnvironment environment) { - final String sessionName = environment.getSessionName(); - final long pollTimeout = environment.getPollTimeout(); - final Semaphore semaphore = new Semaphore(); - semaphore.down(); - - final AtomicBoolean connectorIsReady = new AtomicBoolean(false); - myDebugProcessDispatcher.addListener(new DebugProcessListener() { - @Override - public void connectorIsReady() { - connectorIsReady.set(true); - semaphore.up(); - myDebugProcessDispatcher.removeListener(this); - } - }); + return executionResult; + } - // reload to make sure that source positions are initialized - DebuggerManagerEx.getInstanceEx(myProject).getBreakpointManager().reloadBreakpoints(); + private void fail() { + // need this in order to prevent calling stop() twice + if (myIsFailed.compareAndSet(false, true)) { + stop(false); + } + } - getManagerThread().schedule(new DebuggerCommandImpl() { - @Override - protected void action() { - VirtualMachine vm = null; + private void createVirtualMachine(final DebugEnvironment environment) { + final String sessionName = environment.getSessionName(); + final long pollTimeout = environment.getPollTimeout(); + final Semaphore semaphore = new Semaphore(); + semaphore.down(); - try { - final long time = System.currentTimeMillis(); - do { - try { - vm = createVirtualMachineInt(); - break; - } - catch (final ExecutionException e) { - if (pollTimeout > 0 && !myConnection.isServerMode() && e.getCause() instanceof IOException) { - synchronized (this) { - try { - wait(500); - } - catch (InterruptedException ignored) { - break; - } + final AtomicBoolean connectorIsReady = new AtomicBoolean(false); + myDebugProcessDispatcher.addListener(new DebugProcessListener() { + @Override + public void connectorIsReady() { + connectorIsReady.set(true); + semaphore.up(); + myDebugProcessDispatcher.removeListener(this); + } + }); + + // reload to make sure that source positions are initialized + DebuggerManagerEx.getInstanceEx(myProject).getBreakpointManager().reloadBreakpoints(); + + getManagerThread().schedule(new DebuggerCommandImpl() { + @Override + protected void action() { + VirtualMachine vm = null; + + try { + final long time = System.currentTimeMillis(); + do { + try { + vm = createVirtualMachineInt(); + break; + } + catch (final ExecutionException e) { + if (pollTimeout > 0 && !myConnection.isServerMode() && e.getCause() instanceof IOException) { + synchronized (this) { + try { + wait(500); + } + catch (InterruptedException ignored) { + break; + } + } + } + else { + ProcessHandler processHandler = getProcessHandler(); + boolean terminated = + processHandler != null && (processHandler.isProcessTerminating() || processHandler.isProcessTerminated()); + + fail(); + DebuggerInvocationUtil.swingInvokeLater(myProject, () -> + { + // propagate exception only in case we succeeded to obtain execution result, + // otherwise if the error is induced by the fact that there is nothing to debug, and there is no need to show + // this problem to the user + if ((myExecutionResult != null && !terminated) || !connectorIsReady.get()) { + ExecutionUtil.handleExecutionError(myProject, ToolWindowId.DEBUG, sessionName, e); + } + }); + break; + } + } + } + while (System.currentTimeMillis() - time < pollTimeout); + } + finally { + semaphore.up(); } - } - else { - ProcessHandler processHandler = getProcessHandler(); - boolean terminated = - processHandler != null && (processHandler.isProcessTerminating() || processHandler.isProcessTerminated()); - fail(); - DebuggerInvocationUtil.swingInvokeLater(myProject, () -> - { - // propagate exception only in case we succeeded to obtain execution result, - // otherwise if the error is induced by the fact that there is nothing to debug, and there is no need to show - // this problem to the user - if ((myExecutionResult != null && !terminated) || !connectorIsReady.get()) { - ExecutionUtil.handleExecutionError(myProject, ToolWindowId.DEBUG, sessionName, e); - } - }); - break; - } + if (vm != null) { + final VirtualMachine vm1 = vm; + afterProcessStarted(() -> getManagerThread().schedule(new DebuggerCommandImpl() { + @Override + protected void action() throws Exception { + try { + commitVM(vm1); + } + catch (VMDisconnectedException e) { + fail(); + } + } + })); + } } - } - while (System.currentTimeMillis() - time < pollTimeout); - } - finally { - semaphore.up(); - } - if (vm != null) { - final VirtualMachine vm1 = vm; - afterProcessStarted(() -> getManagerThread().schedule(new DebuggerCommandImpl() { @Override - protected void action() throws Exception { - try { - commitVM(vm1); - } - catch (VMDisconnectedException e) { - fail(); - } + protected void commandCancelled() { + try { + super.commandCancelled(); + } + finally { + semaphore.up(); + } } - })); - } - } + }); - @Override - protected void commandCancelled() { - try { - super.commandCancelled(); + semaphore.waitFor(); + } + + private void afterProcessStarted(final Runnable run) { + class MyProcessAdapter extends ProcessAdapter { + private boolean alreadyRun = false; + + public synchronized void run() { + if (!alreadyRun) { + alreadyRun = true; + run.run(); + } + removeProcessListener(this); + } + + @Override + public void startNotified(ProcessEvent event) { + run(); + } } - finally { - semaphore.up(); + MyProcessAdapter processListener = new MyProcessAdapter(); + addProcessListener(processListener); + if (myExecutionResult != null) { + if (myExecutionResult.getProcessHandler().isStartNotified()) { + processListener.run(); + } } - } - }); + } - semaphore.waitFor(); - } + public boolean isPausePressed() { + final VirtualMachineProxyImpl vm = myVirtualMachineProxy; + return vm != null && vm.isPausePressed(); + } - private void afterProcessStarted(final Runnable run) { - class MyProcessAdapter extends ProcessAdapter { - private boolean alreadyRun = false; + @Nonnull + public DebuggerCommandImpl createPauseCommand() { + return new PauseCommand(); + } - public synchronized void run() { - if (!alreadyRun) { - alreadyRun = true; - run.run(); - } - removeProcessListener(this); - } + @Nonnull + public ResumeCommand createResumeCommand(SuspendContextImpl suspendContext) { + return createResumeCommand(suspendContext, PrioritizedTask.Priority.HIGH); + } - @Override - public void startNotified(ProcessEvent event) { - run(); - } + @Nonnull + public ResumeCommand createResumeCommand(SuspendContextImpl suspendContext, final PrioritizedTask.Priority priority) { + final BreakpointManager breakpointManager = DebuggerManagerEx.getInstanceEx(getProject()).getBreakpointManager(); + return new ResumeCommand(suspendContext) { + @Override + public void contextAction(@Nonnull SuspendContextImpl suspendContext) { + breakpointManager.applyThreadFilter(DebugProcessImpl.this, null); // clear the filter on resume + if (myReturnValueWatcher != null) { + myReturnValueWatcher.clear(); + } + super.contextAction(suspendContext); + } + + @Override + public Priority getPriority() { + return priority; + } + }; } - MyProcessAdapter processListener = new MyProcessAdapter(); - addProcessListener(processListener); - if (myExecutionResult != null) { - if (myExecutionResult.getProcessHandler().isStartNotified()) { - processListener.run(); - } + + @Nonnull + public ResumeCommand createStepOverCommand(SuspendContextImpl suspendContext, boolean ignoreBreakpoints) { + return createStepOverCommand(suspendContext, ignoreBreakpoints, StepRequest.STEP_LINE); } - } - - public boolean isPausePressed() { - final VirtualMachineProxyImpl vm = myVirtualMachineProxy; - return vm != null && vm.isPausePressed(); - } - - @Nonnull - public DebuggerCommandImpl createPauseCommand() { - return new PauseCommand(); - } - - @Nonnull - public ResumeCommand createResumeCommand(SuspendContextImpl suspendContext) { - return createResumeCommand(suspendContext, PrioritizedTask.Priority.HIGH); - } - - @Nonnull - public ResumeCommand createResumeCommand(SuspendContextImpl suspendContext, final PrioritizedTask.Priority priority) { - final BreakpointManager breakpointManager = DebuggerManagerEx.getInstanceEx(getProject()).getBreakpointManager(); - return new ResumeCommand(suspendContext) { - @Override - public void contextAction(@Nonnull SuspendContextImpl suspendContext) { - breakpointManager.applyThreadFilter(DebugProcessImpl.this, null); // clear the filter on resume - if (myReturnValueWatcher != null) { - myReturnValueWatcher.clear(); - } - super.contextAction(suspendContext); - } - @Override - public Priority getPriority() { - return priority; - } - }; - } - - @Nonnull - public ResumeCommand createStepOverCommand(SuspendContextImpl suspendContext, boolean ignoreBreakpoints) { - return createStepOverCommand(suspendContext, ignoreBreakpoints, StepRequest.STEP_LINE); - } - - @Nonnull - public ResumeCommand createStepOverCommand(SuspendContextImpl suspendContext, boolean ignoreBreakpoints, int stepSize) { - return new StepOverCommand(suspendContext, ignoreBreakpoints, stepSize); - } - - @Nonnull - public ResumeCommand createStepOutCommand(SuspendContextImpl suspendContext) { - return createStepOutCommand(suspendContext, StepRequest.STEP_LINE); - } - - @Nonnull - public ResumeCommand createStepOutCommand(SuspendContextImpl suspendContext, int stepSize) { - return new StepOutCommand(suspendContext, stepSize); - } - - @Nonnull - public ResumeCommand createStepIntoCommand(SuspendContextImpl suspendContext, boolean ignoreFilters, final MethodFilter smartStepFilter) { - return createStepIntoCommand(suspendContext, ignoreFilters, smartStepFilter, StepRequest.STEP_LINE); - } - - @Nonnull - public ResumeCommand createStepIntoCommand( - SuspendContextImpl suspendContext, - boolean ignoreFilters, - final MethodFilter smartStepFilter, - int stepSize - ) { - return new StepIntoCommand(suspendContext, ignoreFilters, smartStepFilter, stepSize); - } - - @Nonnull - @RequiredReadAction - public ResumeCommand createRunToCursorCommand( - SuspendContextImpl suspendContext, - @Nonnull XSourcePosition position, - boolean ignoreBreakpoints - ) throws EvaluateException { - RunToCursorCommand runToCursorCommand = new RunToCursorCommand(suspendContext, position, ignoreBreakpoints); - if (runToCursorCommand.myRunToCursorBreakpoint == null) { - PsiFile psiFile = PsiManager.getInstance(myProject).findFile(position.getFile()); - throw new EvaluateException( - DebuggerBundle.message( - "error.running.to.cursor.no.executable.code", - psiFile != null ? psiFile.getName() : "", - position.getLine() - ), - null - ); - } - return runToCursorCommand; - } - - @Nonnull - public DebuggerCommandImpl createFreezeThreadCommand(ThreadReferenceProxyImpl thread) { - return new FreezeThreadCommand(thread); - } - - @Nonnull - public SuspendContextCommandImpl createResumeThreadCommand(SuspendContextImpl suspendContext, @Nonnull ThreadReferenceProxyImpl thread) { - return new ResumeThreadCommand(suspendContext, thread); - } - - @Nonnull - public SuspendContextCommandImpl createPopFrameCommand(DebuggerContextImpl context, StackFrameProxyImpl stackFrame) { - return new PopFrameCommand(context, stackFrame); - } - - //public void setBreakpointsMuted(final boolean muted) { - // XDebugSession session = mySession.getXDebugSession(); - // if (isAttached()) { - // getManagerThread().schedule(new DebuggerCommandImpl() { - // @Override - // protected void action() throws Exception { - // // set the flag before enabling/disabling cause it affects if breakpoints will create requests - // if (myBreakpointsMuted.getAndSet(muted) != muted) { - // final BreakpointManager breakpointManager = DebuggerManagerEx.getInstanceEx(myProject).getBreakpointManager(); - // if (muted) { - // breakpointManager.disableBreakpoints(DebugProcessImpl.this); - // } - // else { - // breakpointManager.enableBreakpoints(DebugProcessImpl.this); - // } - // } - // } - // }); - // } - // else { - // session.setBreakpointMuted(muted); - // } - //} - - @Nonnull - public DebuggerContextImpl getDebuggerContext() { - return mySession.getContextManager().getContext(); - } - - public void setXDebugProcess(JavaDebugProcess XDebugProcess) { - myXDebugProcess = XDebugProcess; - } - - @Nullable - public JavaDebugProcess getXdebugProcess() { - return myXDebugProcess; - } - - public boolean areBreakpointsMuted() { - XDebugSession session = mySession.getXDebugSession(); - return session != null && session.areBreakpointsMuted(); - } - - public DebuggerSession getSession() { - return mySession; - } - - static boolean isResumeOnlyCurrentThread() { - return DebuggerSettings.getInstance().RESUME_ONLY_CURRENT_THREAD; - } + @Nonnull + public ResumeCommand createStepOverCommand(SuspendContextImpl suspendContext, boolean ignoreBreakpoints, int stepSize) { + return new StepOverCommand(suspendContext, ignoreBreakpoints, stepSize); + } + + @Nonnull + public ResumeCommand createStepOutCommand(SuspendContextImpl suspendContext) { + return createStepOutCommand(suspendContext, StepRequest.STEP_LINE); + } + + @Nonnull + public ResumeCommand createStepOutCommand(SuspendContextImpl suspendContext, int stepSize) { + return new StepOutCommand(suspendContext, stepSize); + } + + @Nonnull + public ResumeCommand createStepIntoCommand(SuspendContextImpl suspendContext, boolean ignoreFilters, final MethodFilter smartStepFilter) { + return createStepIntoCommand(suspendContext, ignoreFilters, smartStepFilter, StepRequest.STEP_LINE); + } + + @Nonnull + public ResumeCommand createStepIntoCommand( + SuspendContextImpl suspendContext, + boolean ignoreFilters, + final MethodFilter smartStepFilter, + int stepSize + ) { + return new StepIntoCommand(suspendContext, ignoreFilters, smartStepFilter, stepSize); + } + + @Nonnull + @RequiredReadAction + public ResumeCommand createRunToCursorCommand( + SuspendContextImpl suspendContext, + @Nonnull XSourcePosition position, + boolean ignoreBreakpoints + ) throws EvaluateException { + RunToCursorCommand runToCursorCommand = new RunToCursorCommand(suspendContext, position, ignoreBreakpoints); + if (runToCursorCommand.myRunToCursorBreakpoint == null) { + PsiFile psiFile = PsiManager.getInstance(myProject).findFile(position.getFile()); + throw new EvaluateException( + DebuggerBundle.message( + "error.running.to.cursor.no.executable.code", + psiFile != null ? psiFile.getName() : "", + position.getLine() + ), + null + ); + } + return runToCursorCommand; + } + + @Nonnull + public DebuggerCommandImpl createFreezeThreadCommand(ThreadReferenceProxyImpl thread) { + return new FreezeThreadCommand(thread); + } + + @Nonnull + public SuspendContextCommandImpl createResumeThreadCommand( + SuspendContextImpl suspendContext, + @Nonnull ThreadReferenceProxyImpl thread + ) { + return new ResumeThreadCommand(suspendContext, thread); + } + + @Nonnull + public SuspendContextCommandImpl createPopFrameCommand(DebuggerContextImpl context, StackFrameProxyImpl stackFrame) { + return new PopFrameCommand(context, stackFrame); + } + + //public void setBreakpointsMuted(final boolean muted) { + // XDebugSession session = mySession.getXDebugSession(); + // if (isAttached()) { + // getManagerThread().schedule(new DebuggerCommandImpl() { + // @Override + // protected void action() throws Exception { + // // set the flag before enabling/disabling cause it affects if breakpoints will create requests + // if (myBreakpointsMuted.getAndSet(muted) != muted) { + // final BreakpointManager breakpointManager = DebuggerManagerEx.getInstanceEx(myProject).getBreakpointManager(); + // if (muted) { + // breakpointManager.disableBreakpoints(DebugProcessImpl.this); + // } + // else { + // breakpointManager.enableBreakpoints(DebugProcessImpl.this); + // } + // } + // } + // }); + // } + // else { + // session.setBreakpointMuted(muted); + // } + //} + + @Nonnull + public DebuggerContextImpl getDebuggerContext() { + return mySession.getContextManager().getContext(); + } + + public void setXDebugProcess(JavaDebugProcess XDebugProcess) { + myXDebugProcess = XDebugProcess; + } + + @Nullable + public JavaDebugProcess getXdebugProcess() { + return myXDebugProcess; + } + + public boolean areBreakpointsMuted() { + XDebugSession session = mySession.getXDebugSession(); + return session != null && session.areBreakpointsMuted(); + } + + public DebuggerSession getSession() { + return mySession; + } + + static boolean isResumeOnlyCurrentThread() { + return DebuggerSettings.getInstance().RESUME_ONLY_CURRENT_THREAD; + } } diff --git a/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/engine/ExtraSteppingFilter.java b/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/engine/ExtraSteppingFilter.java index 9d3d3a5850..57007f3f24 100644 --- a/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/engine/ExtraSteppingFilter.java +++ b/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/engine/ExtraSteppingFilter.java @@ -26,12 +26,12 @@ */ @ExtensionAPI(ComponentScope.APPLICATION) public interface ExtraSteppingFilter { - ExtensionPointName EP_NAME = ExtensionPointName.create(ExtraSteppingFilter.class); + ExtensionPointName EP_NAME = ExtensionPointName.create(ExtraSteppingFilter.class); - boolean isApplicable(SuspendContext context); + boolean isApplicable(SuspendContext context); - /** - * @return Step request depth as defined in {@link StepRequest} - */ - int getStepRequestDepth(SuspendContext context); + /** + * @return Step request depth as defined in {@link StepRequest} + */ + int getStepRequestDepth(SuspendContext context); } diff --git a/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/engine/FrameExtraVariablesProvider.java b/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/engine/FrameExtraVariablesProvider.java index 955c2bc9eb..f2e0459091 100644 --- a/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/engine/FrameExtraVariablesProvider.java +++ b/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/engine/FrameExtraVariablesProvider.java @@ -29,9 +29,9 @@ */ @ExtensionAPI(ComponentScope.APPLICATION) public interface FrameExtraVariablesProvider { - ExtensionPointName EP_NAME = ExtensionPointName.create(FrameExtraVariablesProvider.class); + ExtensionPointName EP_NAME = ExtensionPointName.create(FrameExtraVariablesProvider.class); - boolean isAvailable(SourcePosition sourcePosition, EvaluationContext evalContext); + boolean isAvailable(SourcePosition sourcePosition, EvaluationContext evalContext); - Set collectVariables(SourcePosition sourcePosition, EvaluationContext evalContext, Set alreadyCollected); + Set collectVariables(SourcePosition sourcePosition, EvaluationContext evalContext, Set alreadyCollected); } diff --git a/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/engine/JavaBreakpointHandlerFactory.java b/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/engine/JavaBreakpointHandlerFactory.java index ef75d875e4..f8428da384 100644 --- a/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/engine/JavaBreakpointHandlerFactory.java +++ b/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/engine/JavaBreakpointHandlerFactory.java @@ -25,9 +25,8 @@ */ @ExtensionAPI(ComponentScope.APPLICATION) public interface JavaBreakpointHandlerFactory { - ExtensionPointName EP_NAME = - ExtensionPointName.create(JavaBreakpointHandlerFactory.class); + ExtensionPointName EP_NAME = ExtensionPointName.create(JavaBreakpointHandlerFactory.class); - @Nonnull - JavaBreakpointHandler createHandler(DebugProcessImpl process); + @Nonnull + JavaBreakpointHandler createHandler(DebugProcessImpl process); } diff --git a/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/engine/JavaStackFrame.java b/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/engine/JavaStackFrame.java index defa516ea9..31d86b52a5 100644 --- a/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/engine/JavaStackFrame.java +++ b/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/engine/JavaStackFrame.java @@ -79,9 +79,10 @@ */ public class JavaStackFrame extends XStackFrame implements JVMStackFrameInfoProvider { private static final Logger LOG = Logger.getInstance(JavaStackFrame.class); - public static final DummyMessageValueNode LOCAL_VARIABLES_INFO_UNAVAILABLE_MESSAGE_NODE = - new DummyMessageValueNode(MessageDescriptor.LOCAL_VARIABLES_INFO_UNAVAILABLE.getLabel(), - XDebuggerUIConstants.INFORMATION_MESSAGE_ICON); + public static final DummyMessageValueNode LOCAL_VARIABLES_INFO_UNAVAILABLE_MESSAGE_NODE = new DummyMessageValueNode( + MessageDescriptor.LOCAL_VARIABLES_INFO_UNAVAILABLE.getLabel(), + XDebuggerUIConstants.INFORMATION_MESSAGE_ICON + ); private final DebugProcessImpl myDebugProcess; @Nullable @@ -101,7 +102,7 @@ public JavaStackFrame(@Nonnull StackFrameDescriptorImpl descriptor, boolean upda myDescriptor.updateRepresentation(null, DescriptorLabelListener.DUMMY_LISTENER); } myEqualityObject = update ? NodeManagerImpl.getContextKeyForFrame(myDescriptor.getFrameProxy()) : null; - myDebugProcess = ((DebugProcessImpl) descriptor.getDebugProcess()); + myDebugProcess = ((DebugProcessImpl)descriptor.getDebugProcess()); myNodeManager = myDebugProcess.getXdebugProcess().getNodeManager(); myXSourcePosition = DebuggerUtilsEx.toXSourcePosition(myDescriptor.getSourcePosition()); } @@ -135,7 +136,7 @@ public void customizePresentation(@Nonnull ColoredTextContainer component) { if (xSession != null) { XStackFrame frame = xSession.getCurrentStackFrame(); if (frame instanceof JavaStackFrame) { - selectedDescriptor = ((JavaStackFrame) frame).getDescriptor(); + selectedDescriptor = ((JavaStackFrame)frame).getDescriptor(); } } } @@ -150,30 +151,33 @@ public void computeChildren(@Nonnull final XCompositeNode node) { if (node.isObsolete()) { return; } - myDebugProcess.getManagerThread() - .schedule(new DebuggerContextCommandImpl(myDebugProcess.getDebuggerContext(), - myDescriptor.getFrameProxy().threadProxy()) { - @Override - public Priority getPriority() { - return Priority.NORMAL; - } + myDebugProcess.getManagerThread().schedule(new DebuggerContextCommandImpl( + myDebugProcess.getDebuggerContext(), + myDescriptor.getFrameProxy().threadProxy() + ) { + @Override + public Priority getPriority() { + return Priority.NORMAL; + } - @Override - public void threadAction() { - if (node.isObsolete()) { - return; - } - if (myInsertCapturePoint != null) { - node.setMessage("Async stacktrace from " + myInsertCapturePoint.myClassName + "." + myInsertCapturePoint.myMethodName + " could be available here, enable in", - XDebuggerUIConstants.INFORMATION_MESSAGE_ICON, - SimpleTextAttributes.REGULAR_ATTRIBUTES, - StackFrameItem.CAPTURE_SETTINGS_OPENER); - } - XValueChildrenList children = new XValueChildrenList(); - buildVariablesThreadAction(getFrameDebuggerContext(getDebuggerContext()), children, node); - node.addChildren(children, true); + @Override + public void threadAction() { + if (node.isObsolete()) { + return; + } + if (myInsertCapturePoint != null) { + node.setMessage( + "Async stacktrace from " + myInsertCapturePoint.myClassName + "." + myInsertCapturePoint.myMethodName + " could be available here, enable in", + XDebuggerUIConstants.INFORMATION_MESSAGE_ICON, + SimpleTextAttributes.REGULAR_ATTRIBUTES, + StackFrameItem.CAPTURE_SETTINGS_OPENER + ); } - }); + XValueChildrenList children = new XValueChildrenList(); + buildVariablesThreadAction(getFrameDebuggerContext(getDebuggerContext()), children, node); + node.addChildren(children, true); + } + }); } DebuggerContextImpl getFrameDebuggerContext(@Nullable DebuggerContextImpl context) { @@ -184,10 +188,12 @@ DebuggerContextImpl getFrameDebuggerContext(@Nullable DebuggerContextImpl contex if (context.getFrameProxy() != getStackFrameProxy()) { SuspendContextImpl threadSuspendContext = SuspendManagerUtil.findContextByThread(myDebugProcess.getSuspendManager(), getStackFrameProxy().threadProxy()); - context = DebuggerContextImpl.createDebuggerContext(myDebugProcess.mySession, + context = DebuggerContextImpl.createDebuggerContext( + myDebugProcess.mySession, threadSuspendContext, getStackFrameProxy().threadProxy(), - getStackFrameProxy()); + getStackFrameProxy() + ); context.setPositionCache(myDescriptor.getSourcePosition()); context.initCaches(); } @@ -237,15 +243,17 @@ else if (location != null) { for (Pair pair : DebuggerUtilsEx.getEventDescriptors(debuggerContext.getSuspendContext())) { Event debugEvent = pair.getSecond(); if (debugEvent instanceof ExceptionEvent) { - ObjectReference exception = ((ExceptionEvent) debugEvent).exception(); + ObjectReference exception = ((ExceptionEvent)debugEvent).exception(); if (exception != null) { exceptions.add(exception); } } } - exceptions.forEach(e -> children.add(JavaValue.create(myNodeManager.getThrownExceptionObjectDescriptor(myDescriptor, e), + exceptions.forEach(e -> children.add(JavaValue.create( + myNodeManager.getThrownExceptionObjectDescriptor(myDescriptor, e), evaluationContext, - myNodeManager))); + myNodeManager + ))); try { buildVariables(debuggerContext, evaluationContext, debugProcess, children, thisObjectReference, location); @@ -279,12 +287,14 @@ else if (location != null) { Pair.create(Collections.emptySet(), Collections.emptySet()); // copied from FrameVariablesTree - private void buildVariables(DebuggerContextImpl debuggerContext, - final EvaluationContextImpl evaluationContext, - @Nonnull DebugProcessImpl debugProcess, - XValueChildrenList children, - ObjectReference thisObjectReference, - Location location) throws EvaluateException { + private void buildVariables( + DebuggerContextImpl debuggerContext, + final EvaluationContextImpl evaluationContext, + @Nonnull DebugProcessImpl debugProcess, + XValueChildrenList children, + ObjectReference thisObjectReference, + Location location + ) throws EvaluateException { final Set visibleLocals = new HashSet<>(); if (NodeRendererSettings.getInstance().getClassRenderer().SHOW_VAL_FIELDS_AS_LOCAL_VARIABLES) { if (thisObjectReference != null && debugProcess.getVirtualMachineProxy().canGetSyntheticAttribute()) { @@ -292,8 +302,10 @@ private void buildVariables(DebuggerContextImpl debuggerContext, if (thisRefType instanceof ClassType && location != null && thisRefType.equals(location.declaringType()) && thisRefType.name() .contains("$")) { // makes sense for nested classes only for (Field field : thisRefType.fields()) { - if (DebuggerUtils.isSynthetic(field) && StringUtil.startsWith(field.name(), FieldDescriptorImpl.OUTER_LOCAL_VAR_FIELD_PREFIX)) { - final FieldDescriptorImpl fieldDescriptor = myNodeManager.getFieldDescriptor(myDescriptor, thisObjectReference, field); + if (DebuggerUtils.isSynthetic(field) + && StringUtil.startsWith(field.name(), FieldDescriptorImpl.OUTER_LOCAL_VAR_FIELD_PREFIX)) { + final FieldDescriptorImpl fieldDescriptor = + myNodeManager.getFieldDescriptor(myDescriptor, thisObjectReference, field); children.add(JavaValue.create(fieldDescriptor, evaluationContext, myNodeManager)); visibleLocals.add(fieldDescriptor.calcValueName()); } @@ -320,14 +332,21 @@ private void buildVariables(DebuggerContextImpl debuggerContext, Pair, Set> usedVars = EMPTY_USED_VARS; if (sourcePosition != null) { usedVars = - ReadAction.compute(() -> findReferencedVars(ContainerUtil.union(visibleVariables.keySet(), visibleLocals), sourcePosition)); + ReadAction.compute(() -> findReferencedVars( + ContainerUtil.union(visibleVariables.keySet(), visibleLocals), + sourcePosition + )); } // add locals if (myAutoWatchMode) { for (String var : usedVars.first) { LocalVariableProxyImpl local = visibleVariables.get(var); if (local != null) { - children.add(JavaValue.create(myNodeManager.getLocalVariableDescriptor(null, local), evaluationContext, myNodeManager)); + children.add(JavaValue.create( + myNodeManager.getLocalVariableDescriptor(null, local), + evaluationContext, + myNodeManager + )); } } } @@ -352,11 +371,13 @@ private void buildVariables(DebuggerContextImpl debuggerContext, children.add(LOCAL_VARIABLES_INFO_UNAVAILABLE_MESSAGE_NODE); // trying to collect values from variable slots try { - for (Map.Entry entry : LocalVariablesUtil.fetchValues(getStackFrameProxy(), debugProcess, true) - .entrySet()) { - children.add(JavaValue.create(myNodeManager.getArgumentValueDescriptor(null, entry.getKey(), entry.getValue()), + for (Map.Entry entry + : LocalVariablesUtil.fetchValues(getStackFrameProxy(), debugProcess, true).entrySet()) { + children.add(JavaValue.create( + myNodeManager.getArgumentValueDescriptor(null, entry.getKey(), entry.getValue()), evaluationContext, - myNodeManager)); + myNodeManager + )); } } catch (Exception ex) { @@ -369,9 +390,11 @@ private void buildVariables(DebuggerContextImpl debuggerContext, } } - private static Set computeExtraVars(Pair, Set> usedVars, - @Nonnull SourcePosition sourcePosition, - @Nonnull EvaluationContextImpl evalContext) { + private static Set computeExtraVars( + Pair, Set> usedVars, + @Nonnull SourcePosition sourcePosition, + @Nonnull EvaluationContextImpl evalContext + ) { Set alreadyCollected = new HashSet<>(usedVars.first); usedVars.second.stream().map(TextWithImports::getText).forEach(alreadyCollected::add); Set extra = new HashSet<>(); @@ -426,7 +449,8 @@ public String toString() { } } - protected void superBuildVariables(final EvaluationContextImpl evaluationContext, XValueChildrenList children) throws EvaluateException { + protected void superBuildVariables(final EvaluationContextImpl evaluationContext, XValueChildrenList children) + throws EvaluateException { for (LocalVariableProxyImpl local : getVisibleVariables()) { children.add(JavaValue.create(myNodeManager.getLocalVariableDescriptor(null, local), evaluationContext, myNodeManager)); } @@ -496,7 +520,7 @@ public void visitReferenceExpression(final PsiReferenceExpression reference) { if (myLineRange.intersects(reference.getTextRange())) { final PsiElement psiElement = reference.resolve(); if (psiElement instanceof PsiVariable) { - final PsiVariable var = (PsiVariable) psiElement; + final PsiVariable var = (PsiVariable)psiElement; if (var instanceof PsiField) { if (myCollectExpressions && !DebuggerUtils.hasSideEffectsOrReferencesMissingVars(reference, myVisibleLocals)) { /* @@ -531,8 +555,10 @@ public void visitReferenceExpression(final PsiReferenceExpression reference) { } else { // fix for variables used in inner classes - if (!Comparing.equal(PsiTreeUtil.getParentOfType(reference, PsiClass.class), - PsiTreeUtil.getParentOfType(var, PsiClass.class))) { + if (!Comparing.equal( + PsiTreeUtil.getParentOfType(reference, PsiClass.class), + PsiTreeUtil.getParentOfType(var, PsiClass.class) + )) { myExpressions.add(new TextWithImportsImpl(reference)); } } @@ -587,16 +613,14 @@ private static boolean shouldSkipLine(final PsiFile file, Document doc, int line } TextRange alreadyChecked = null; - for (PsiElement elem = - file.findElementAt(_start); elem != null && elem.getTextOffset() <= end && (alreadyChecked == null || !alreadyChecked.contains(elem - .getTextRange())); - elem = elem - .getNextSibling()) { + for (PsiElement elem = file.findElementAt(_start); + elem != null && elem.getTextOffset() <= end && (alreadyChecked == null || !alreadyChecked.contains(elem.getTextRange())); + elem = elem.getNextSibling()) { for (PsiElement _elem = elem; _elem.getTextOffset() >= _start; _elem = _elem.getParent()) { alreadyChecked = _elem.getTextRange(); if (_elem instanceof PsiDeclarationStatement) { - final PsiElement[] declared = ((PsiDeclarationStatement) _elem).getDeclaredElements(); + final PsiElement[] declared = ((PsiDeclarationStatement)_elem).getDeclaredElements(); for (PsiElement declaredElement : declared) { if (declaredElement instanceof PsiVariable) { return false; @@ -606,7 +630,7 @@ private static boolean shouldSkipLine(final PsiFile file, Document doc, int line if (_elem instanceof PsiJavaCodeReferenceElement) { try { - final PsiElement resolved = ((PsiJavaCodeReferenceElement) _elem).resolve(); + final PsiElement resolved = ((PsiJavaCodeReferenceElement)_elem).resolve(); if (resolved instanceof PsiVariable) { return false; } diff --git a/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/engine/RequestHint.java b/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/engine/RequestHint.java index cd9e820a65..884f16644d 100644 --- a/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/engine/RequestHint.java +++ b/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/engine/RequestHint.java @@ -44,332 +44,282 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; -public class RequestHint -{ - public static final int STOP = 0; - public static final int RESUME = -100; - - private static final Logger LOG = Logger.getInstance(RequestHint.class); - @MagicConstant(intValues = { - StepRequest.STEP_MIN, - StepRequest.STEP_LINE - }) - private final int mySize; - @MagicConstant(intValues = { - StepRequest.STEP_INTO, - StepRequest.STEP_OVER, - StepRequest.STEP_OUT - }) - private final int myDepth; - private final SourcePosition myPosition; - private final int myFrameCount; - private boolean mySteppedOut = false; - - @Nullable - private final MethodFilter myMethodFilter; - private boolean myTargetMethodMatched = false; - - private boolean myIgnoreFilters = false; - private boolean myResetIgnoreFilters = false; - private boolean myRestoreBreakpoints = false; - - public RequestHint(final ThreadReferenceProxyImpl stepThread, final SuspendContextImpl suspendContext, @Nonnull MethodFilter methodFilter) - { - this(stepThread, suspendContext, StepRequest.STEP_LINE, StepRequest.STEP_INTO, methodFilter); - } - - public RequestHint(final ThreadReferenceProxyImpl stepThread, - final SuspendContextImpl suspendContext, - @MagicConstant(intValues = { - StepRequest.STEP_INTO, - StepRequest.STEP_OVER, - StepRequest.STEP_OUT - }) int depth) - { - this(stepThread, suspendContext, StepRequest.STEP_LINE, depth, null); - } - - protected RequestHint(final ThreadReferenceProxyImpl stepThread, - final SuspendContextImpl suspendContext, - @MagicConstant(intValues = { - StepRequest.STEP_MIN, - StepRequest.STEP_LINE - }) int stepSize, - @MagicConstant(intValues = { - StepRequest.STEP_INTO, - StepRequest.STEP_OVER, - StepRequest.STEP_OUT - }) int depth, - @Nullable MethodFilter methodFilter) - { - mySize = stepSize; - myDepth = depth; - myMethodFilter = methodFilter; - - int frameCount = 0; - SourcePosition position = null; - try - { - frameCount = stepThread.frameCount(); - - position = ContextUtil.getSourcePosition(new StackFrameContext() - { - @Override - public StackFrameProxy getFrameProxy() - { - try - { - return stepThread.frame(0); - } - catch(EvaluateException e) - { - LOG.debug(e); - return null; - } - } - - @Override - @Nonnull - public DebugProcess getDebugProcess() - { - return suspendContext.getDebugProcess(); - } - }); - } - catch(Exception e) - { - LOG.info(e); - } - finally - { - myFrameCount = frameCount; - myPosition = position; - } - } - - public void setIgnoreFilters(boolean ignoreFilters) - { - myIgnoreFilters = ignoreFilters; - } - - public void setResetIgnoreFilters(boolean resetIgnoreFilters) - { - myResetIgnoreFilters = resetIgnoreFilters; - } - - public boolean isResetIgnoreFilters() - { - return myResetIgnoreFilters; - } - - public void setRestoreBreakpoints(boolean restoreBreakpoints) - { - myRestoreBreakpoints = restoreBreakpoints; - } - - public boolean isRestoreBreakpoints() - { - return myRestoreBreakpoints; - } - - public boolean isIgnoreFilters() - { - return myIgnoreFilters; - } - - @MagicConstant(intValues = { - StepRequest.STEP_MIN, - StepRequest.STEP_LINE - }) - public int getSize() - { - return mySize; - } - - @MagicConstant(intValues = { - StepRequest.STEP_INTO, - StepRequest.STEP_OVER, - StepRequest.STEP_OUT - }) - public int getDepth() - { - return myDepth; - } - - @Nullable - public MethodFilter getMethodFilter() - { - return myMethodFilter; - } - - public boolean wasStepTargetMethodMatched() - { - return myMethodFilter instanceof BreakpointStepMethodFilter || myTargetMethodMatched; - } - - protected boolean isTheSameFrame(SuspendContextImpl context) - { - if(mySteppedOut) - { - return false; - } - final ThreadReferenceProxyImpl contextThread = context.getThread(); - if(contextThread != null) - { - try - { - int currentDepth = contextThread.frameCount(); - if(currentDepth < myFrameCount) - { - mySteppedOut = true; - } - return currentDepth == myFrameCount; - } - catch(EvaluateException ignored) - { - } - } - return false; - } - - private boolean isOnTheSameLine(SourcePosition locationPosition) - { - if(myMethodFilter == null) - { - return myPosition.getLine() == locationPosition.getLine(); - } - else - { - Range exprLines = myMethodFilter.getCallingExpressionLines(); - return exprLines != null && exprLines.isWithin(locationPosition.getLine()); - } - } - - protected boolean isSteppedOut() - { - return mySteppedOut; - } - - public int getNextStepDepth(final SuspendContextImpl context) - { - try - { - final StackFrameProxyImpl frameProxy = context.getFrameProxy(); - - // smart step feature stop check - if(myMethodFilter != null && frameProxy != null && !(myMethodFilter instanceof BreakpointStepMethodFilter) && myMethodFilter.locationMatches(context.getDebugProcess(), frameProxy - .location(), frameProxy::thisObject) && !isTheSameFrame(context)) - { - myTargetMethodMatched = true; - return myMethodFilter.onReached(context, this); - } - - if((myDepth == StepRequest.STEP_OVER || myDepth == StepRequest.STEP_INTO) && myPosition != null) - { - SourcePosition locationPosition = ContextUtil.getSourcePosition(context); - if(locationPosition != null) - { - Integer resultDepth = ReadAction.compute(() -> - { - if(myPosition.getFile().equals(locationPosition.getFile()) && isTheSameFrame(context) && !mySteppedOut) - { - return isOnTheSameLine(locationPosition) ? myDepth : STOP; - } - return null; - }); - if(resultDepth != null) - { - return resultDepth.intValue(); - } - } - } - - // Now check filters - - final DebuggerSettings settings = DebuggerSettings.getInstance(); - - if((myMethodFilter != null || (settings.SKIP_SYNTHETIC_METHODS && !myIgnoreFilters)) && frameProxy != null) - { - final Location location = frameProxy.location(); - if(location != null) - { - if(DebuggerUtils.isSynthetic(location.method())) - { - return myDepth; - } - } - } - - if(!myIgnoreFilters) - { - if(settings.SKIP_GETTERS) - { - boolean isGetter = ReadAction.compute(() -> - { - PsiElement contextElement = ContextUtil.getContextElement(context); - return contextElement != null && DebuggerUtils.isInsideSimpleGetter(contextElement); - }).booleanValue(); - - if(isGetter) - { - return StepRequest.STEP_OUT; - } - } - - if(frameProxy != null) - { - if(settings.SKIP_CONSTRUCTORS) - { - final Location location = frameProxy.location(); - if(location != null) - { - final Method method = location.method(); - if(method != null && method.isConstructor()) - { - return StepRequest.STEP_OUT; - } - } - } - - if(settings.SKIP_CLASSLOADERS) - { - final Location location = frameProxy.location(); - if(location != null && DebuggerUtilsEx.isAssignableFrom("java.lang.ClassLoader", location.declaringType())) - { - return StepRequest.STEP_OUT; - } - } - } - - for(ExtraSteppingFilter filter : ExtraSteppingFilter.EP_NAME.getExtensions()) - { - try - { - if(filter.isApplicable(context)) - { - return filter.getStepRequestDepth(context); - } - } - catch(Exception | AssertionError e) - { - LOG.error(e); - } - } - } - // smart step feature - if(myMethodFilter != null && !mySteppedOut) - { - return StepRequest.STEP_OUT; - } - } - catch(VMDisconnectedException ignored) - { - } - catch(EvaluateException e) - { - LOG.error(e); - } - return STOP; - } +public class RequestHint { + public static final int STOP = 0; + public static final int RESUME = -100; + + private static final Logger LOG = Logger.getInstance(RequestHint.class); + @MagicConstant(intValues = { + StepRequest.STEP_MIN, + StepRequest.STEP_LINE + }) + private final int mySize; + @MagicConstant(intValues = { + StepRequest.STEP_INTO, + StepRequest.STEP_OVER, + StepRequest.STEP_OUT + }) + private final int myDepth; + private final SourcePosition myPosition; + private final int myFrameCount; + private boolean mySteppedOut = false; + + @Nullable + private final MethodFilter myMethodFilter; + private boolean myTargetMethodMatched = false; + + private boolean myIgnoreFilters = false; + private boolean myResetIgnoreFilters = false; + private boolean myRestoreBreakpoints = false; + + public RequestHint( + final ThreadReferenceProxyImpl stepThread, + final SuspendContextImpl suspendContext, + @Nonnull MethodFilter methodFilter + ) { + this(stepThread, suspendContext, StepRequest.STEP_LINE, StepRequest.STEP_INTO, methodFilter); + } + + public RequestHint( + final ThreadReferenceProxyImpl stepThread, + final SuspendContextImpl suspendContext, + @MagicConstant(intValues = { + StepRequest.STEP_INTO, + StepRequest.STEP_OVER, + StepRequest.STEP_OUT + }) int depth + ) { + this(stepThread, suspendContext, StepRequest.STEP_LINE, depth, null); + } + + protected RequestHint( + final ThreadReferenceProxyImpl stepThread, + final SuspendContextImpl suspendContext, + @MagicConstant(intValues = { + StepRequest.STEP_MIN, + StepRequest.STEP_LINE + }) int stepSize, + @MagicConstant(intValues = { + StepRequest.STEP_INTO, + StepRequest.STEP_OVER, + StepRequest.STEP_OUT + }) int depth, + @Nullable MethodFilter methodFilter + ) { + mySize = stepSize; + myDepth = depth; + myMethodFilter = methodFilter; + + int frameCount = 0; + SourcePosition position = null; + try { + frameCount = stepThread.frameCount(); + + position = ContextUtil.getSourcePosition(new StackFrameContext() { + @Override + public StackFrameProxy getFrameProxy() { + try { + return stepThread.frame(0); + } + catch (EvaluateException e) { + LOG.debug(e); + return null; + } + } + + @Override + @Nonnull + public DebugProcess getDebugProcess() { + return suspendContext.getDebugProcess(); + } + }); + } + catch (Exception e) { + LOG.info(e); + } + finally { + myFrameCount = frameCount; + myPosition = position; + } + } + + public void setIgnoreFilters(boolean ignoreFilters) { + myIgnoreFilters = ignoreFilters; + } + + public void setResetIgnoreFilters(boolean resetIgnoreFilters) { + myResetIgnoreFilters = resetIgnoreFilters; + } + + public boolean isResetIgnoreFilters() { + return myResetIgnoreFilters; + } + + public void setRestoreBreakpoints(boolean restoreBreakpoints) { + myRestoreBreakpoints = restoreBreakpoints; + } + + public boolean isRestoreBreakpoints() { + return myRestoreBreakpoints; + } + + public boolean isIgnoreFilters() { + return myIgnoreFilters; + } + + @MagicConstant(intValues = { + StepRequest.STEP_MIN, + StepRequest.STEP_LINE + }) + public int getSize() { + return mySize; + } + + @MagicConstant(intValues = { + StepRequest.STEP_INTO, + StepRequest.STEP_OVER, + StepRequest.STEP_OUT + }) + public int getDepth() { + return myDepth; + } + + @Nullable + public MethodFilter getMethodFilter() { + return myMethodFilter; + } + + public boolean wasStepTargetMethodMatched() { + return myMethodFilter instanceof BreakpointStepMethodFilter || myTargetMethodMatched; + } + + protected boolean isTheSameFrame(SuspendContextImpl context) { + if (mySteppedOut) { + return false; + } + final ThreadReferenceProxyImpl contextThread = context.getThread(); + if (contextThread != null) { + try { + int currentDepth = contextThread.frameCount(); + if (currentDepth < myFrameCount) { + mySteppedOut = true; + } + return currentDepth == myFrameCount; + } + catch (EvaluateException ignored) { + } + } + return false; + } + + private boolean isOnTheSameLine(SourcePosition locationPosition) { + if (myMethodFilter == null) { + return myPosition.getLine() == locationPosition.getLine(); + } + else { + Range exprLines = myMethodFilter.getCallingExpressionLines(); + return exprLines != null && exprLines.isWithin(locationPosition.getLine()); + } + } + + protected boolean isSteppedOut() { + return mySteppedOut; + } + + public int getNextStepDepth(final SuspendContextImpl context) { + try { + final StackFrameProxyImpl frameProxy = context.getFrameProxy(); + + // smart step feature stop check + if (myMethodFilter != null && frameProxy != null && !(myMethodFilter instanceof BreakpointStepMethodFilter) + && myMethodFilter.locationMatches(context.getDebugProcess(), frameProxy.location(), frameProxy::thisObject) + && !isTheSameFrame(context)) { + myTargetMethodMatched = true; + return myMethodFilter.onReached(context, this); + } + + if ((myDepth == StepRequest.STEP_OVER || myDepth == StepRequest.STEP_INTO) && myPosition != null) { + SourcePosition locationPosition = ContextUtil.getSourcePosition(context); + if (locationPosition != null) { + Integer resultDepth = ReadAction.compute(() -> { + if (myPosition.getFile().equals(locationPosition.getFile()) && isTheSameFrame(context) && !mySteppedOut) { + return isOnTheSameLine(locationPosition) ? myDepth : STOP; + } + return null; + }); + if (resultDepth != null) { + return resultDepth.intValue(); + } + } + } + + // Now check filters + + final DebuggerSettings settings = DebuggerSettings.getInstance(); + + if ((myMethodFilter != null || (settings.SKIP_SYNTHETIC_METHODS && !myIgnoreFilters)) && frameProxy != null) { + final Location location = frameProxy.location(); + if (location != null) { + if (DebuggerUtils.isSynthetic(location.method())) { + return myDepth; + } + } + } + + if (!myIgnoreFilters) { + if (settings.SKIP_GETTERS) { + boolean isGetter = ReadAction.compute(() -> + { + PsiElement contextElement = ContextUtil.getContextElement(context); + return contextElement != null && DebuggerUtils.isInsideSimpleGetter(contextElement); + }).booleanValue(); + + if (isGetter) { + return StepRequest.STEP_OUT; + } + } + + if (frameProxy != null) { + if (settings.SKIP_CONSTRUCTORS) { + final Location location = frameProxy.location(); + if (location != null) { + final Method method = location.method(); + if (method != null && method.isConstructor()) { + return StepRequest.STEP_OUT; + } + } + } + + if (settings.SKIP_CLASSLOADERS) { + final Location location = frameProxy.location(); + if (location != null && DebuggerUtilsEx.isAssignableFrom("java.lang.ClassLoader", location.declaringType())) { + return StepRequest.STEP_OUT; + } + } + } + + for (ExtraSteppingFilter filter : ExtraSteppingFilter.EP_NAME.getExtensions()) { + try { + if (filter.isApplicable(context)) { + return filter.getStepRequestDepth(context); + } + } + catch (Exception | AssertionError e) { + LOG.error(e); + } + } + } + // smart step feature + if (myMethodFilter != null && !mySteppedOut) { + return StepRequest.STEP_OUT; + } + } + catch (VMDisconnectedException ignored) { + } + catch (EvaluateException e) { + LOG.error(e); + } + return STOP; + } } diff --git a/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/engine/SourcePositionProvider.java b/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/engine/SourcePositionProvider.java index d9ec1a6ac5..11a3aaf590 100644 --- a/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/engine/SourcePositionProvider.java +++ b/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/engine/SourcePositionProvider.java @@ -28,33 +28,38 @@ @ExtensionAPI(ComponentScope.APPLICATION) public abstract class SourcePositionProvider { - public static final ExtensionPointName EP_NAME = - ExtensionPointName.create(SourcePositionProvider.class); + public static final ExtensionPointName EP_NAME = ExtensionPointName.create(SourcePositionProvider.class); - @Nullable - public static SourcePosition getSourcePosition(@Nonnull NodeDescriptor descriptor, - @Nonnull Project project, - @Nonnull DebuggerContextImpl context) { - return getSourcePosition(descriptor, project, context, false); - } + @Nullable + public static SourcePosition getSourcePosition( + @Nonnull NodeDescriptor descriptor, + @Nonnull Project project, + @Nonnull DebuggerContextImpl context + ) { + return getSourcePosition(descriptor, project, context, false); + } - @Nullable - public static SourcePosition getSourcePosition(@Nonnull NodeDescriptor descriptor, - @Nonnull Project project, - @Nonnull DebuggerContextImpl context, - boolean nearest) { - for (SourcePositionProvider provider : EP_NAME.getExtensions()) { - SourcePosition sourcePosition = provider.computeSourcePosition(descriptor, project, context, nearest); - if (sourcePosition != null) { - return sourcePosition; - } + @Nullable + public static SourcePosition getSourcePosition( + @Nonnull NodeDescriptor descriptor, + @Nonnull Project project, + @Nonnull DebuggerContextImpl context, + boolean nearest + ) { + for (SourcePositionProvider provider : EP_NAME.getExtensions()) { + SourcePosition sourcePosition = provider.computeSourcePosition(descriptor, project, context, nearest); + if (sourcePosition != null) { + return sourcePosition; + } + } + return null; } - return null; - } - @Nullable - protected abstract SourcePosition computeSourcePosition(@Nonnull NodeDescriptor descriptor, - @Nonnull Project project, - @Nonnull DebuggerContextImpl context, - boolean nearest); + @Nullable + protected abstract SourcePosition computeSourcePosition( + @Nonnull NodeDescriptor descriptor, + @Nonnull Project project, + @Nonnull DebuggerContextImpl context, + boolean nearest + ); } diff --git a/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/settings/NodeRendererSettings.java b/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/settings/NodeRendererSettings.java index b325f7b4dd..602259f319 100644 --- a/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/settings/NodeRendererSettings.java +++ b/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/settings/NodeRendererSettings.java @@ -60,6 +60,7 @@ import org.jetbrains.annotations.NonNls; import jakarta.annotation.Nullable; + import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -69,523 +70,567 @@ @ServiceAPI(ComponentScope.APPLICATION) @ServiceImpl public class NodeRendererSettings implements PersistentStateComponent { - @NonNls - private static final String REFERENCE_RENDERER = "Reference renderer"; - @NonNls - public static final String RENDERER_TAG = "Renderer"; - @NonNls - private static final String RENDERER_ID = "ID"; - - private final EventDispatcher myDispatcher = EventDispatcher.create(NodeRendererSettingsListener.class); - private RendererConfiguration myCustomRenderers = new RendererConfiguration(this); - - // base renderers - private final PrimitiveRenderer myPrimitiveRenderer = new PrimitiveRenderer(); - private final ArrayRenderer myArrayRenderer = new ArrayRenderer(); - private final ClassRenderer myClassRenderer = new ClassRenderer(); - private final HexRenderer myHexRenderer = new HexRenderer(); - private final ToStringRenderer myToStringRenderer = new ToStringRenderer(); - // alternate collections - private final NodeRenderer[] myAlternateCollectionRenderers = new NodeRenderer[]{ - createCompoundReferenceRenderer("Map", - JavaClassNames.JAVA_UTIL_MAP, - createLabelRenderer(" size = ", "size()", null), - createExpressionChildrenRenderer("entrySet().toArray()", - "!isEmpty" + "()")), - createCompoundReferenceRenderer("Map.Entry", - "java.util.Map$Entry", - new MapEntryLabelRenderer()/*createLabelRenderer(null, "\" \" + getKey() + \" -> \" + getValue()", null)*/, - createEnumerationChildrenRenderer(new String[][]{ - { - "key", - "getKey()" - }, - { - "value", - "getValue()" - } - })), - new ListObjectRenderer(this), - createCompoundReferenceRenderer("Collection", - "java.util.Collection", - createLabelRenderer(" size = ", "size()", null), - createExpressionChildrenRenderer("toArray()", "!isEmpty()")) - }; - @NonNls - private static final String HEX_VIEW_ENABLED = "HEX_VIEW_ENABLED"; - @NonNls - private static final String ALTERNATIVE_COLLECTION_VIEW_ENABLED = "ALTERNATIVE_COLLECTION_VIEW_ENABLED"; - @NonNls - private static final String CUSTOM_RENDERERS_TAG_NAME = "CustomRenderers"; - - public NodeRendererSettings() { - // default configuration - myHexRenderer.setEnabled(false); - myToStringRenderer.setEnabled(true); - setAlternateCollectionViewsEnabled(true); - } - - public static NodeRendererSettings getInstance() { - return ServiceManager.getService(NodeRendererSettings.class); - } - - public void setAlternateCollectionViewsEnabled(boolean enabled) { - for (NodeRenderer myAlternateCollectionRenderer : myAlternateCollectionRenderers) { - myAlternateCollectionRenderer.setEnabled(enabled); - } - } - - public boolean areAlternateCollectionViewsEnabled() { - return myAlternateCollectionRenderers[0].isEnabled(); - } + @NonNls + private static final String REFERENCE_RENDERER = "Reference renderer"; + @NonNls + public static final String RENDERER_TAG = "Renderer"; + @NonNls + private static final String RENDERER_ID = "ID"; + + private final EventDispatcher myDispatcher = EventDispatcher.create(NodeRendererSettingsListener.class); + private RendererConfiguration myCustomRenderers = new RendererConfiguration(this); + + // base renderers + private final PrimitiveRenderer myPrimitiveRenderer = new PrimitiveRenderer(); + private final ArrayRenderer myArrayRenderer = new ArrayRenderer(); + private final ClassRenderer myClassRenderer = new ClassRenderer(); + private final HexRenderer myHexRenderer = new HexRenderer(); + private final ToStringRenderer myToStringRenderer = new ToStringRenderer(); + // alternate collections + private final NodeRenderer[] myAlternateCollectionRenderers = new NodeRenderer[]{ + createCompoundReferenceRenderer( + "Map", + JavaClassNames.JAVA_UTIL_MAP, + createLabelRenderer(" size = ", "size()", null), + createExpressionChildrenRenderer( + "entrySet().toArray()", + "!isEmpty" + "()" + ) + ), + createCompoundReferenceRenderer( + "Map.Entry", + "java.util.Map$Entry", + new MapEntryLabelRenderer()/*createLabelRenderer(null, "\" \" + getKey() + \" -> \" + getValue()", null)*/, + createEnumerationChildrenRenderer(new String[][]{ + { + "key", + "getKey()" + }, + { + "value", + "getValue()" + } + }) + ), + new ListObjectRenderer(this), + createCompoundReferenceRenderer( + "Collection", + "java.util.Collection", + createLabelRenderer(" size = ", "size()", null), + createExpressionChildrenRenderer("toArray()", "!isEmpty()") + ) + }; + @NonNls + private static final String HEX_VIEW_ENABLED = "HEX_VIEW_ENABLED"; + @NonNls + private static final String ALTERNATIVE_COLLECTION_VIEW_ENABLED = "ALTERNATIVE_COLLECTION_VIEW_ENABLED"; + @NonNls + private static final String CUSTOM_RENDERERS_TAG_NAME = "CustomRenderers"; - public boolean equals(Object o) { - if (!(o instanceof NodeRendererSettings)) { - return false; + public NodeRendererSettings() { + // default configuration + myHexRenderer.setEnabled(false); + myToStringRenderer.setEnabled(true); + setAlternateCollectionViewsEnabled(true); } - return DebuggerUtilsEx.elementsEqual(getState(), ((NodeRendererSettings)o).getState()); - } - - public void addListener(NodeRendererSettingsListener listener, Disposable disposable) { - myDispatcher.addListener(listener, disposable); - } - - @Override - @SuppressWarnings({"HardCodedStringLiteral"}) - public Element getState() { - final Element element = new Element("NodeRendererSettings"); - if (myHexRenderer.isEnabled()) { - JDOMExternalizerUtil.writeField(element, HEX_VIEW_ENABLED, "true"); - } - if (!areAlternateCollectionViewsEnabled()) { - JDOMExternalizerUtil.writeField(element, ALTERNATIVE_COLLECTION_VIEW_ENABLED, "false"); + public static NodeRendererSettings getInstance() { + return ServiceManager.getService(NodeRendererSettings.class); } - try { - element.addContent(writeRenderer(myArrayRenderer)); - element.addContent(writeRenderer(myToStringRenderer)); - element.addContent(writeRenderer(myClassRenderer)); - element.addContent(writeRenderer(myPrimitiveRenderer)); - if (myCustomRenderers.getRendererCount() > 0) { - final Element custom = new Element(CUSTOM_RENDERERS_TAG_NAME); - element.addContent(custom); - myCustomRenderers.writeExternal(custom); - } + public void setAlternateCollectionViewsEnabled(boolean enabled) { + for (NodeRenderer myAlternateCollectionRenderer : myAlternateCollectionRenderers) { + myAlternateCollectionRenderer.setEnabled(enabled); + } } - catch (WriteExternalException e) { - // ignore + + public boolean areAlternateCollectionViewsEnabled() { + return myAlternateCollectionRenderers[0].isEnabled(); } - return element; - } - - @Override - @SuppressWarnings({"HardCodedStringLiteral"}) - public void loadState(final Element root) { - final String hexEnabled = JDOMExternalizerUtil.readField(root, HEX_VIEW_ENABLED); - if (hexEnabled != null) { - myHexRenderer.setEnabled("true".equalsIgnoreCase(hexEnabled)); + + public boolean equals(Object o) { + if (!(o instanceof NodeRendererSettings)) { + return false; + } + + return DebuggerUtilsEx.elementsEqual(getState(), ((NodeRendererSettings)o).getState()); } - final String alternativeEnabled = JDOMExternalizerUtil.readField(root, ALTERNATIVE_COLLECTION_VIEW_ENABLED); - if (alternativeEnabled != null) { - setAlternateCollectionViewsEnabled("true".equalsIgnoreCase(alternativeEnabled)); + public void addListener(NodeRendererSettingsListener listener, Disposable disposable) { + myDispatcher.addListener(listener, disposable); } - for (final Element elem : root.getChildren(RENDERER_TAG)) { - final String id = elem.getAttributeValue(RENDERER_ID); - if (id == null) { - continue; - } - try { - if (ArrayRenderer.UNIQUE_ID.equals(id)) { - myArrayRenderer.readExternal(elem); + @Override + @SuppressWarnings({"HardCodedStringLiteral"}) + public Element getState() { + final Element element = new Element("NodeRendererSettings"); + if (myHexRenderer.isEnabled()) { + JDOMExternalizerUtil.writeField(element, HEX_VIEW_ENABLED, "true"); } - else if (ToStringRenderer.UNIQUE_ID.equals(id)) { - myToStringRenderer.readExternal(elem); + if (!areAlternateCollectionViewsEnabled()) { + JDOMExternalizerUtil.writeField(element, ALTERNATIVE_COLLECTION_VIEW_ENABLED, "false"); } - else if (ClassRenderer.UNIQUE_ID.equals(id)) { - myClassRenderer.readExternal(elem); + + try { + element.addContent(writeRenderer(myArrayRenderer)); + element.addContent(writeRenderer(myToStringRenderer)); + element.addContent(writeRenderer(myClassRenderer)); + element.addContent(writeRenderer(myPrimitiveRenderer)); + if (myCustomRenderers.getRendererCount() > 0) { + final Element custom = new Element(CUSTOM_RENDERERS_TAG_NAME); + element.addContent(custom); + myCustomRenderers.writeExternal(custom); + } } - else if (PrimitiveRenderer.UNIQUE_ID.equals(id)) { - myPrimitiveRenderer.readExternal(elem); + catch (WriteExternalException e) { + // ignore } - } - catch (InvalidDataException e) { - // ignore - } - } - final Element custom = root.getChild(CUSTOM_RENDERERS_TAG_NAME); - if (custom != null) { - myCustomRenderers.readExternal(custom); + return element; } - myDispatcher.getMulticaster().renderersChanged(); - } + @Override + @SuppressWarnings({"HardCodedStringLiteral"}) + public void loadState(final Element root) { + final String hexEnabled = JDOMExternalizerUtil.readField(root, HEX_VIEW_ENABLED); + if (hexEnabled != null) { + myHexRenderer.setEnabled("true".equalsIgnoreCase(hexEnabled)); + } - public RendererConfiguration getCustomRenderers() { - return myCustomRenderers; - } + final String alternativeEnabled = JDOMExternalizerUtil.readField(root, ALTERNATIVE_COLLECTION_VIEW_ENABLED); + if (alternativeEnabled != null) { + setAlternateCollectionViewsEnabled("true".equalsIgnoreCase(alternativeEnabled)); + } - public void setCustomRenderers(@Nonnull final RendererConfiguration customRenderers) { - RendererConfiguration oldConfig = myCustomRenderers; - myCustomRenderers = customRenderers; - if (oldConfig == null || !oldConfig.equals(customRenderers)) { - fireRenderersChanged(); - } - } - - public PrimitiveRenderer getPrimitiveRenderer() { - return myPrimitiveRenderer; - } - - public ArrayRenderer getArrayRenderer() { - return myArrayRenderer; - } - - public ClassRenderer getClassRenderer() { - return myClassRenderer; - } - - public HexRenderer getHexRenderer() { - return myHexRenderer; - } - - public ToStringRenderer getToStringRenderer() { - return myToStringRenderer; - } - - public NodeRenderer[] getAlternateCollectionRenderers() { - return myAlternateCollectionRenderers; - } - - public void fireRenderersChanged() { - myDispatcher.getMulticaster().renderersChanged(); - } - - public List getAllRenderers() { - // the order is important as the renderers are applied according to it - final List allRenderers = new ArrayList<>(); - - // user defined renderers must come first - myCustomRenderers.iterateRenderers(renderer -> - { - allRenderers.add(renderer); - return true; - }); - - // plugins registered renderers come after that - Collections.addAll(allRenderers, NodeRenderer.EP_NAME.getExtensions()); - - // now all predefined stuff - allRenderers.add(myHexRenderer); - allRenderers.add(myPrimitiveRenderer); - Collections.addAll(allRenderers, myAlternateCollectionRenderers); - allRenderers.add(myToStringRenderer); - allRenderers.add(myArrayRenderer); - allRenderers.add(myClassRenderer); - return allRenderers; - } - - public Renderer readRenderer(Element root) throws InvalidDataException { - if (root == null) { - return null; - } + for (final Element elem : root.getChildren(RENDERER_TAG)) { + final String id = elem.getAttributeValue(RENDERER_ID); + if (id == null) { + continue; + } + try { + if (ArrayRenderer.UNIQUE_ID.equals(id)) { + myArrayRenderer.readExternal(elem); + } + else if (ToStringRenderer.UNIQUE_ID.equals(id)) { + myToStringRenderer.readExternal(elem); + } + else if (ClassRenderer.UNIQUE_ID.equals(id)) { + myClassRenderer.readExternal(elem); + } + else if (PrimitiveRenderer.UNIQUE_ID.equals(id)) { + myPrimitiveRenderer.readExternal(elem); + } + } + catch (InvalidDataException e) { + // ignore + } + } + final Element custom = root.getChild(CUSTOM_RENDERERS_TAG_NAME); + if (custom != null) { + myCustomRenderers.readExternal(custom); + } - if (!RENDERER_TAG.equals(root.getName())) { - throw new InvalidDataException("Cannot read renderer - tag name is not '" + RENDERER_TAG + "'"); + myDispatcher.getMulticaster().renderersChanged(); } - final String rendererId = root.getAttributeValue(RENDERER_ID); - if (rendererId == null) { - throw new InvalidDataException("unknown renderer ID: " + rendererId); + public RendererConfiguration getCustomRenderers() { + return myCustomRenderers; } - final Renderer renderer = createRenderer(rendererId); - if (renderer == null) { - throw new InvalidDataException("unknown renderer ID: " + rendererId); + public void setCustomRenderers(@Nonnull final RendererConfiguration customRenderers) { + RendererConfiguration oldConfig = myCustomRenderers; + myCustomRenderers = customRenderers; + if (oldConfig == null || !oldConfig.equals(customRenderers)) { + fireRenderersChanged(); + } } - renderer.readExternal(root); - - return renderer; - } - - public Element writeRenderer(Renderer renderer) throws WriteExternalException { - Element root = new Element(RENDERER_TAG); - if (renderer != null) { - root.setAttribute(RENDERER_ID, renderer.getUniqueId()); - renderer.writeExternal(root); + public PrimitiveRenderer getPrimitiveRenderer() { + return myPrimitiveRenderer; } - return root; - } - public Renderer createRenderer(final String rendererId) { - if (ClassRenderer.UNIQUE_ID.equals(rendererId)) { - return myClassRenderer; - } - else if (ArrayRenderer.UNIQUE_ID.equals(rendererId)) { - return myArrayRenderer; - } - else if (PrimitiveRenderer.UNIQUE_ID.equals(rendererId)) { - return myPrimitiveRenderer; - } - else if (HexRenderer.UNIQUE_ID.equals(rendererId)) { - return myHexRenderer; - } - else if (rendererId.equals(ExpressionChildrenRenderer.UNIQUE_ID)) { - return new ExpressionChildrenRenderer(); - } - else if (rendererId.equals(LabelRenderer.UNIQUE_ID)) { - return new LabelRenderer(); + public ArrayRenderer getArrayRenderer() { + return myArrayRenderer; } - else if (rendererId.equals(EnumerationChildrenRenderer.UNIQUE_ID)) { - return new EnumerationChildrenRenderer(); - } - else if (rendererId.equals(ToStringRenderer.UNIQUE_ID)) { - return myToStringRenderer; - } - else if (rendererId.equals(CompoundNodeRenderer.UNIQUE_ID) || rendererId.equals(REFERENCE_RENDERER)) { - return createCompoundReferenceRenderer("unnamed", JavaClassNames.JAVA_LANG_OBJECT, null, null); - } - else if (rendererId.equals(CompoundTypeRenderer.UNIQUE_ID)) { - return createCompoundTypeRenderer("unnamed", JavaClassNames.JAVA_LANG_OBJECT, null, null); + + public ClassRenderer getClassRenderer() { + return myClassRenderer; } - return null; - } - - public CompoundTypeRenderer createCompoundTypeRenderer(@NonNls final String rendererName, - @NonNls final String className, - final ValueLabelRenderer labelRenderer, - final ChildrenRenderer childrenRenderer) { - CompoundTypeRenderer renderer = new CompoundTypeRenderer(this, rendererName, labelRenderer, childrenRenderer); - renderer.setClassName(className); - return renderer; - } - - public CompoundReferenceRenderer createCompoundReferenceRenderer(@NonNls final String rendererName, - @NonNls final String className, - final ValueLabelRenderer labelRenderer, - final ChildrenRenderer childrenRenderer) { - CompoundReferenceRenderer renderer = new CompoundReferenceRenderer(this, rendererName, labelRenderer, childrenRenderer); - renderer.setClassName(className); - return renderer; - } - - public static ExpressionChildrenRenderer createExpressionChildrenRenderer(@NonNls String expressionText, - @NonNls String childrenExpandableText) { - final ExpressionChildrenRenderer childrenRenderer = new ExpressionChildrenRenderer(); - childrenRenderer.setChildrenExpression(new TextWithImportsImpl(CodeFragmentKind.EXPRESSION, expressionText, "", JavaFileType.INSTANCE)); - if (childrenExpandableText != null) { - childrenRenderer.setChildrenExpandable(new TextWithImportsImpl(CodeFragmentKind.EXPRESSION, - childrenExpandableText, - "", - JavaFileType.INSTANCE)); + + public HexRenderer getHexRenderer() { + return myHexRenderer; } - return childrenRenderer; - } - - public static EnumerationChildrenRenderer createEnumerationChildrenRenderer(@NonNls String[][] expressions) { - EnumerationChildrenRenderer childrenRenderer = new EnumerationChildrenRenderer(); - if (expressions != null && expressions.length > 0) { - ArrayList childrenList = new ArrayList<>(expressions.length); - for (String[] expression : expressions) { - childrenList.add(new EnumerationChildrenRenderer.ChildInfo(expression[0], - new TextWithImportsImpl(CodeFragmentKind.EXPRESSION, - expression[1], - "", - JavaFileType.INSTANCE), - false)); - } - childrenRenderer.setChildren(childrenList); + + public ToStringRenderer getToStringRenderer() { + return myToStringRenderer; } - return childrenRenderer; - } - - private static LabelRenderer createLabelRenderer(@NonNls final String prefix, - @NonNls final String expressionText, - @NonNls final String postfix) { - final LabelRenderer labelRenderer = new LabelRenderer() { - @Override - public String calcLabel(ValueDescriptor descriptor, - EvaluationContext evaluationContext, - DescriptorLabelListener labelListener) throws EvaluateException { - final String evaluated = super.calcLabel(descriptor, evaluationContext, labelListener); - if (prefix == null && postfix == null) { - return evaluated; - } - if (prefix != null && postfix != null) { - return prefix + evaluated + postfix; - } - if (prefix != null) { - return prefix + evaluated; - } - return evaluated + postfix; - } - }; - labelRenderer.setLabelExpression(new TextWithImportsImpl(CodeFragmentKind.EXPRESSION, expressionText, "", JavaFileType.INSTANCE)); - return labelRenderer; - } - - private static class MapEntryLabelRenderer extends ReferenceRenderer implements ValueLabelRenderer { - private static final Computable NULL_LABEL_COMPUTABLE = () -> "null"; - - private final MyCachedEvaluator myKeyExpression = new MyCachedEvaluator(); - private final MyCachedEvaluator myValueExpression = new MyCachedEvaluator(); - - private MapEntryLabelRenderer() { - super("java.util.Map$Entry"); - myKeyExpression.setReferenceExpression(new TextWithImportsImpl(CodeFragmentKind.EXPRESSION, - "this.getKey()", - "", - JavaFileType.INSTANCE)); - myValueExpression.setReferenceExpression(new TextWithImportsImpl(CodeFragmentKind.EXPRESSION, - "this.getValue()", - "", - JavaFileType.INSTANCE)); + + public NodeRenderer[] getAlternateCollectionRenderers() { + return myAlternateCollectionRenderers; } - @Override - public Image calcValueIcon(ValueDescriptor descriptor, - EvaluationContext evaluationContext, - DescriptorLabelListener listener) throws EvaluateException { - return null; + public void fireRenderersChanged() { + myDispatcher.getMulticaster().renderersChanged(); } - @Override - public String calcLabel(ValueDescriptor descriptor, - EvaluationContext evaluationContext, - DescriptorLabelListener listener) throws EvaluateException { - final DescriptorUpdater descriptorUpdater = new DescriptorUpdater(descriptor, listener); + public List getAllRenderers() { + // the order is important as the renderers are applied according to it + final List allRenderers = new ArrayList<>(); - final Value originalValue = descriptor.getValue(); - final Pair, ValueDescriptorImpl> keyPair = - createValueComputable(evaluationContext, originalValue, myKeyExpression, descriptorUpdater); - final Pair, ValueDescriptorImpl> valuePair = - createValueComputable(evaluationContext, originalValue, myValueExpression, descriptorUpdater); + // user defined renderers must come first + myCustomRenderers.iterateRenderers(renderer -> + { + allRenderers.add(renderer); + return true; + }); - descriptorUpdater.setKeyDescriptor(keyPair.second); - descriptorUpdater.setValueDescriptor(valuePair.second); + // plugins registered renderers come after that + Collections.addAll(allRenderers, NodeRenderer.EP_NAME.getExtensions()); - return DescriptorUpdater.constructLabelText(keyPair.first.compute(), valuePair.first.compute()); + // now all predefined stuff + allRenderers.add(myHexRenderer); + allRenderers.add(myPrimitiveRenderer); + Collections.addAll(allRenderers, myAlternateCollectionRenderers); + allRenderers.add(myToStringRenderer); + allRenderers.add(myArrayRenderer); + allRenderers.add(myClassRenderer); + return allRenderers; } - private Pair, ValueDescriptorImpl> createValueComputable(final EvaluationContext evaluationContext, - Value originalValue, - final MyCachedEvaluator evaluator, - final DescriptorLabelListener listener) throws EvaluateException { - final Value eval = doEval(evaluationContext, originalValue, evaluator); - if (eval != null) { - final WatchItemDescriptor evalDescriptor = - new WatchItemDescriptor(evaluationContext.getProject(), evaluator.getReferenceExpression(), eval); - evalDescriptor.setShowIdLabel(false); - return new Pair<>(() -> - { - evalDescriptor.updateRepresentation((EvaluationContextImpl)evaluationContext, listener); - return evalDescriptor.getValueLabel(); - }, evalDescriptor); - } - return new Pair<>(NULL_LABEL_COMPUTABLE, null); + public Renderer readRenderer(Element root) throws InvalidDataException { + if (root == null) { + return null; + } + + if (!RENDERER_TAG.equals(root.getName())) { + throw new InvalidDataException("Cannot read renderer - tag name is not '" + RENDERER_TAG + "'"); + } + + final String rendererId = root.getAttributeValue(RENDERER_ID); + if (rendererId == null) { + throw new InvalidDataException("unknown renderer ID: " + rendererId); + } + + final Renderer renderer = createRenderer(rendererId); + if (renderer == null) { + throw new InvalidDataException("unknown renderer ID: " + rendererId); + } + + renderer.readExternal(root); + + return renderer; } - @Override - public String getUniqueId() { - return "MapEntry renderer"; + public Element writeRenderer(Renderer renderer) throws WriteExternalException { + Element root = new Element(RENDERER_TAG); + if (renderer != null) { + root.setAttribute(RENDERER_ID, renderer.getUniqueId()); + renderer.writeExternal(root); + } + return root; } - private Value doEval(EvaluationContext evaluationContext, - Value originalValue, - MyCachedEvaluator cachedEvaluator) throws EvaluateException { - final DebugProcess debugProcess = evaluationContext.getDebugProcess(); - if (originalValue == null) { + public Renderer createRenderer(final String rendererId) { + if (ClassRenderer.UNIQUE_ID.equals(rendererId)) { + return myClassRenderer; + } + else if (ArrayRenderer.UNIQUE_ID.equals(rendererId)) { + return myArrayRenderer; + } + else if (PrimitiveRenderer.UNIQUE_ID.equals(rendererId)) { + return myPrimitiveRenderer; + } + else if (HexRenderer.UNIQUE_ID.equals(rendererId)) { + return myHexRenderer; + } + else if (rendererId.equals(ExpressionChildrenRenderer.UNIQUE_ID)) { + return new ExpressionChildrenRenderer(); + } + else if (rendererId.equals(LabelRenderer.UNIQUE_ID)) { + return new LabelRenderer(); + } + else if (rendererId.equals(EnumerationChildrenRenderer.UNIQUE_ID)) { + return new EnumerationChildrenRenderer(); + } + else if (rendererId.equals(ToStringRenderer.UNIQUE_ID)) { + return myToStringRenderer; + } + else if (rendererId.equals(CompoundNodeRenderer.UNIQUE_ID) || rendererId.equals(REFERENCE_RENDERER)) { + return createCompoundReferenceRenderer("unnamed", JavaClassNames.JAVA_LANG_OBJECT, null, null); + } + else if (rendererId.equals(CompoundTypeRenderer.UNIQUE_ID)) { + return createCompoundTypeRenderer("unnamed", JavaClassNames.JAVA_LANG_OBJECT, null, null); + } return null; - } - try { - final ExpressionEvaluator evaluator = cachedEvaluator.getEvaluator(debugProcess.getProject()); - if (!debugProcess.isAttached()) { - throw EvaluateExceptionUtil.PROCESS_EXITED; - } - final EvaluationContext thisEvaluationContext = evaluationContext.createEvaluationContext(originalValue); - return evaluator.evaluate(thisEvaluationContext); - } - catch (final EvaluateException ex) { - throw new EvaluateException(DebuggerBundle.message("error.unable.to.evaluate.expression") + " " + ex.getMessage(), ex); - } } - private class MyCachedEvaluator extends CachedEvaluator { - @Override - protected String getClassName() { - return MapEntryLabelRenderer.this.getClassName(); - } + public CompoundTypeRenderer createCompoundTypeRenderer( + @NonNls final String rendererName, + @NonNls final String className, + final ValueLabelRenderer labelRenderer, + final ChildrenRenderer childrenRenderer + ) { + CompoundTypeRenderer renderer = new CompoundTypeRenderer(this, rendererName, labelRenderer, childrenRenderer); + renderer.setClassName(className); + return renderer; + } + + public CompoundReferenceRenderer createCompoundReferenceRenderer( + @NonNls final String rendererName, + @NonNls final String className, + final ValueLabelRenderer labelRenderer, + final ChildrenRenderer childrenRenderer + ) { + CompoundReferenceRenderer renderer = new CompoundReferenceRenderer(this, rendererName, labelRenderer, childrenRenderer); + renderer.setClassName(className); + return renderer; + } + + public static ExpressionChildrenRenderer createExpressionChildrenRenderer( + @NonNls String expressionText, + @NonNls String childrenExpandableText + ) { + final ExpressionChildrenRenderer childrenRenderer = new ExpressionChildrenRenderer(); + childrenRenderer.setChildrenExpression(new TextWithImportsImpl( + CodeFragmentKind.EXPRESSION, + expressionText, + "", + JavaFileType.INSTANCE + )); + if (childrenExpandableText != null) { + childrenRenderer.setChildrenExpandable(new TextWithImportsImpl( + CodeFragmentKind.EXPRESSION, + childrenExpandableText, + "", + JavaFileType.INSTANCE + )); + } + return childrenRenderer; + } + + public static EnumerationChildrenRenderer createEnumerationChildrenRenderer(@NonNls String[][] expressions) { + EnumerationChildrenRenderer childrenRenderer = new EnumerationChildrenRenderer(); + if (expressions != null && expressions.length > 0) { + ArrayList childrenList = new ArrayList<>(expressions.length); + for (String[] expression : expressions) { + childrenList.add(new EnumerationChildrenRenderer.ChildInfo( + expression[0], + new TextWithImportsImpl( + CodeFragmentKind.EXPRESSION, + expression[1], + "", + JavaFileType.INSTANCE + ), + false + )); + } + childrenRenderer.setChildren(childrenList); + } + return childrenRenderer; + } + + private static LabelRenderer createLabelRenderer( + @NonNls final String prefix, + @NonNls final String expressionText, + @NonNls final String postfix + ) { + final LabelRenderer labelRenderer = new LabelRenderer() { + @Override + public String calcLabel(ValueDescriptor descriptor, EvaluationContext evaluationContext, DescriptorLabelListener labelListener) + throws EvaluateException { + final String evaluated = super.calcLabel(descriptor, evaluationContext, labelListener); + if (prefix == null && postfix == null) { + return evaluated; + } + if (prefix != null && postfix != null) { + return prefix + evaluated + postfix; + } + if (prefix != null) { + return prefix + evaluated; + } + return evaluated + postfix; + } + }; + labelRenderer.setLabelExpression(new TextWithImportsImpl(CodeFragmentKind.EXPRESSION, expressionText, "", JavaFileType.INSTANCE)); + return labelRenderer; + } + + private static class MapEntryLabelRenderer extends ReferenceRenderer implements ValueLabelRenderer { + private static final Computable NULL_LABEL_COMPUTABLE = () -> "null"; + + private final MyCachedEvaluator myKeyExpression = new MyCachedEvaluator(); + private final MyCachedEvaluator myValueExpression = new MyCachedEvaluator(); + + private MapEntryLabelRenderer() { + super("java.util.Map$Entry"); + myKeyExpression.setReferenceExpression(new TextWithImportsImpl( + CodeFragmentKind.EXPRESSION, + "this.getKey()", + "", + JavaFileType.INSTANCE + )); + myValueExpression.setReferenceExpression(new TextWithImportsImpl( + CodeFragmentKind.EXPRESSION, + "this.getValue()", + "", + JavaFileType.INSTANCE + )); + } - @Override - public ExpressionEvaluator getEvaluator(Project project) throws EvaluateException { - return super.getEvaluator(project); - } - } - } + @Override + public Image calcValueIcon(ValueDescriptor descriptor, EvaluationContext evaluationContext, DescriptorLabelListener listener) + throws EvaluateException { + return null; + } - private static class ListObjectRenderer extends CompoundReferenceRenderer { - public ListObjectRenderer(NodeRendererSettings rendererSettings) { - super(rendererSettings, - "List", - createLabelRenderer(" size = ", "size()", null), - createExpressionChildrenRenderer("toArray()", "!isEmpty()")); - setClassName(JavaClassNames.JAVA_UTIL_LIST); - } + @Override + public String calcLabel( + ValueDescriptor descriptor, + EvaluationContext evaluationContext, + DescriptorLabelListener listener + ) throws EvaluateException { + final DescriptorUpdater descriptorUpdater = new DescriptorUpdater(descriptor, listener); - @Override - public PsiElement getChildValueExpression(DebuggerTreeNode node, DebuggerContext context) throws EvaluateException { - LOG.assertTrue(node.getDescriptor() instanceof ArrayElementDescriptorImpl); - try { - return getChildValueExpression("this.get(" + ((ArrayElementDescriptorImpl)node.getDescriptor()).getIndex() + ")", node, context); - } - catch (IncorrectOperationException e) { - // fallback to original - return super.getChildValueExpression(node, context); - } - } - } - - private static class DescriptorUpdater implements DescriptorLabelListener { - private final ValueDescriptor myTargetDescriptor; - @Nullable - private ValueDescriptorImpl myKeyDescriptor; - @Nullable - private ValueDescriptorImpl myValueDescriptor; - private final DescriptorLabelListener myDelegate; - - private DescriptorUpdater(ValueDescriptor descriptor, DescriptorLabelListener delegate) { - myTargetDescriptor = descriptor; - myDelegate = delegate; - } + final Value originalValue = descriptor.getValue(); + final Pair, ValueDescriptorImpl> keyPair = + createValueComputable(evaluationContext, originalValue, myKeyExpression, descriptorUpdater); + final Pair, ValueDescriptorImpl> valuePair = + createValueComputable(evaluationContext, originalValue, myValueExpression, descriptorUpdater); - public void setKeyDescriptor(@Nullable ValueDescriptorImpl keyDescriptor) { - myKeyDescriptor = keyDescriptor; - } + descriptorUpdater.setKeyDescriptor(keyPair.second); + descriptorUpdater.setValueDescriptor(valuePair.second); - public void setValueDescriptor(@Nullable ValueDescriptorImpl valueDescriptor) { - myValueDescriptor = valueDescriptor; - } + return DescriptorUpdater.constructLabelText(keyPair.first.compute(), valuePair.first.compute()); + } - @Override - public void labelChanged() { - myTargetDescriptor.setValueLabel(constructLabelText(getDescriptorLabel(myKeyDescriptor), getDescriptorLabel(myValueDescriptor))); - myDelegate.labelChanged(); + private Pair, ValueDescriptorImpl> createValueComputable( + final EvaluationContext evaluationContext, + Value originalValue, + final MyCachedEvaluator evaluator, + final DescriptorLabelListener listener + ) throws EvaluateException { + final Value eval = doEval(evaluationContext, originalValue, evaluator); + if (eval != null) { + final WatchItemDescriptor evalDescriptor = + new WatchItemDescriptor(evaluationContext.getProject(), evaluator.getReferenceExpression(), eval); + evalDescriptor.setShowIdLabel(false); + return new Pair<>(() -> + { + evalDescriptor.updateRepresentation((EvaluationContextImpl)evaluationContext, listener); + return evalDescriptor.getValueLabel(); + }, evalDescriptor); + } + return new Pair<>(NULL_LABEL_COMPUTABLE, null); + } + + @Override + public String getUniqueId() { + return "MapEntry renderer"; + } + + private Value doEval( + EvaluationContext evaluationContext, + Value originalValue, + MyCachedEvaluator cachedEvaluator + ) throws EvaluateException { + final DebugProcess debugProcess = evaluationContext.getDebugProcess(); + if (originalValue == null) { + return null; + } + try { + final ExpressionEvaluator evaluator = cachedEvaluator.getEvaluator(debugProcess.getProject()); + if (!debugProcess.isAttached()) { + throw EvaluateExceptionUtil.PROCESS_EXITED; + } + final EvaluationContext thisEvaluationContext = evaluationContext.createEvaluationContext(originalValue); + return evaluator.evaluate(thisEvaluationContext); + } + catch (final EvaluateException ex) { + throw new EvaluateException(DebuggerBundle.message("error.unable.to.evaluate.expression") + " " + ex.getMessage(), ex); + } + } + + private class MyCachedEvaluator extends CachedEvaluator { + @Override + protected String getClassName() { + return MapEntryLabelRenderer.this.getClassName(); + } + + @Override + public ExpressionEvaluator getEvaluator(Project project) throws EvaluateException { + return super.getEvaluator(project); + } + } } - static String constructLabelText(final String keylabel, final String valueLabel) { - StringBuilder sb = new StringBuilder(); - sb.append('\"').append(keylabel).append("\" -> "); - if (!StringUtil.isEmpty(valueLabel)) { - sb.append('\"').append(valueLabel).append('\"'); - } - return sb.toString(); + private static class ListObjectRenderer extends CompoundReferenceRenderer { + public ListObjectRenderer(NodeRendererSettings rendererSettings) { + super( + rendererSettings, + "List", + createLabelRenderer(" size = ", "size()", null), + createExpressionChildrenRenderer("toArray()", "!isEmpty()") + ); + setClassName(JavaClassNames.JAVA_UTIL_LIST); + } + + @Override + public PsiElement getChildValueExpression(DebuggerTreeNode node, DebuggerContext context) throws EvaluateException { + LOG.assertTrue(node.getDescriptor() instanceof ArrayElementDescriptorImpl); + try { + return getChildValueExpression( + "this.get(" + ((ArrayElementDescriptorImpl)node.getDescriptor()).getIndex() + ")", + node, + context + ); + } + catch (IncorrectOperationException e) { + // fallback to original + return super.getChildValueExpression(node, context); + } + } } - private static String getDescriptorLabel(final ValueDescriptorImpl keyDescriptor) { - return keyDescriptor == null ? "null" : keyDescriptor.getValueLabel(); + private static class DescriptorUpdater implements DescriptorLabelListener { + private final ValueDescriptor myTargetDescriptor; + @Nullable + private ValueDescriptorImpl myKeyDescriptor; + @Nullable + private ValueDescriptorImpl myValueDescriptor; + private final DescriptorLabelListener myDelegate; + + private DescriptorUpdater(ValueDescriptor descriptor, DescriptorLabelListener delegate) { + myTargetDescriptor = descriptor; + myDelegate = delegate; + } + + public void setKeyDescriptor(@Nullable ValueDescriptorImpl keyDescriptor) { + myKeyDescriptor = keyDescriptor; + } + + public void setValueDescriptor(@Nullable ValueDescriptorImpl valueDescriptor) { + myValueDescriptor = valueDescriptor; + } + + @Override + public void labelChanged() { + myTargetDescriptor.setValueLabel(constructLabelText( + getDescriptorLabel(myKeyDescriptor), + getDescriptorLabel(myValueDescriptor) + )); + myDelegate.labelChanged(); + } + + static String constructLabelText(final String keylabel, final String valueLabel) { + StringBuilder sb = new StringBuilder(); + sb.append('\"').append(keylabel).append("\" -> "); + if (!StringUtil.isEmpty(valueLabel)) { + sb.append('\"').append(valueLabel).append('\"'); + } + return sb.toString(); + } + + private static String getDescriptorLabel(final ValueDescriptorImpl keyDescriptor) { + return keyDescriptor == null ? "null" : keyDescriptor.getValueLabel(); + } } - } } diff --git a/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/ui/tree/render/NodeRenderer.java b/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/ui/tree/render/NodeRenderer.java index 10248d0b53..fecdc2cb4f 100644 --- a/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/ui/tree/render/NodeRenderer.java +++ b/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/ui/tree/render/NodeRenderer.java @@ -21,13 +21,13 @@ @ExtensionAPI(ComponentScope.APPLICATION) public interface NodeRenderer extends ChildrenRenderer, ValueLabelRenderer { - ExtensionPointName EP_NAME = ExtensionPointName.create(NodeRenderer.class); + ExtensionPointName EP_NAME = ExtensionPointName.create(NodeRenderer.class); - String getName(); + String getName(); - void setName(String text); + void setName(String text); - boolean isEnabled(); + boolean isEnabled(); - void setEnabled(boolean enabled); + void setEnabled(boolean enabled); } diff --git a/java-execution-api/src/main/java/com/intellij/java/execution/JUnitRecognizer.java b/java-execution-api/src/main/java/com/intellij/java/execution/JUnitRecognizer.java index 6c78280ff5..0fd8f82db2 100644 --- a/java-execution-api/src/main/java/com/intellij/java/execution/JUnitRecognizer.java +++ b/java-execution-api/src/main/java/com/intellij/java/execution/JUnitRecognizer.java @@ -12,19 +12,17 @@ */ @ExtensionAPI(ComponentScope.APPLICATION) public abstract class JUnitRecognizer { + public static final ExtensionPointName EP_NAME = ExtensionPointName.create(JUnitRecognizer.class); - public static final ExtensionPointName EP_NAME = ExtensionPointName.create(JUnitRecognizer.class); + public abstract boolean isTestAnnotated(@Nonnull PsiMethod method); - public abstract boolean isTestAnnotated(@Nonnull PsiMethod method); + public static boolean willBeAnnotatedAfterCompilation(@Nonnull PsiMethod method) { + for (JUnitRecognizer jUnitRecognizer : EP_NAME.getExtensionList()) { + if (jUnitRecognizer.isTestAnnotated(method)) { + return true; + } + } - public static boolean willBeAnnotatedAfterCompilation(@Nonnull PsiMethod method) { - for (JUnitRecognizer jUnitRecognizer : EP_NAME.getExtensionList()) { - if (jUnitRecognizer.isTestAnnotated(method)) { - return true; - } + return false; } - - return false; - } - } diff --git a/java-execution-api/src/main/java/com/intellij/java/execution/JavaTestPatcher.java b/java-execution-api/src/main/java/com/intellij/java/execution/JavaTestPatcher.java index f763adf5ac..9e92a8ed1a 100644 --- a/java-execution-api/src/main/java/com/intellij/java/execution/JavaTestPatcher.java +++ b/java-execution-api/src/main/java/com/intellij/java/execution/JavaTestPatcher.java @@ -29,7 +29,7 @@ */ @ExtensionAPI(ComponentScope.APPLICATION) public interface JavaTestPatcher { - ExtensionPointName EP_NAME = ExtensionPointName.create(JavaTestPatcher.class); + ExtensionPointName EP_NAME = ExtensionPointName.create(JavaTestPatcher.class); - void patchJavaParameters(@Nonnull Module module, @Nonnull OwnJavaParameters javaParameters); + void patchJavaParameters(@Nonnull Module module, @Nonnull OwnJavaParameters javaParameters); } diff --git a/java-execution-api/src/main/java/com/intellij/java/execution/filters/ExceptionFilterFactory.java b/java-execution-api/src/main/java/com/intellij/java/execution/filters/ExceptionFilterFactory.java index 24c62d4ab2..12c35ff3e4 100644 --- a/java-execution-api/src/main/java/com/intellij/java/execution/filters/ExceptionFilterFactory.java +++ b/java-execution-api/src/main/java/com/intellij/java/execution/filters/ExceptionFilterFactory.java @@ -30,8 +30,8 @@ */ @ExtensionAPI(ComponentScope.APPLICATION) public interface ExceptionFilterFactory { - ExtensionPointName EP_NAME = ExtensionPointName.create(ExceptionFilterFactory.class); + ExtensionPointName EP_NAME = ExtensionPointName.create(ExceptionFilterFactory.class); - @Nonnull - Filter create(@Nonnull GlobalSearchScope searchScope); + @Nonnull + Filter create(@Nonnull GlobalSearchScope searchScope); } diff --git a/java-execution-api/src/main/java/com/intellij/java/execution/filters/ExceptionFilters.java b/java-execution-api/src/main/java/com/intellij/java/execution/filters/ExceptionFilters.java index c1acc7266e..56bca01357 100644 --- a/java-execution-api/src/main/java/com/intellij/java/execution/filters/ExceptionFilters.java +++ b/java-execution-api/src/main/java/com/intellij/java/execution/filters/ExceptionFilters.java @@ -19,6 +19,7 @@ import consulo.language.psi.scope.GlobalSearchScope; import jakarta.annotation.Nonnull; + import java.util.ArrayList; import java.util.List; @@ -29,16 +30,16 @@ * Time: 7:54 PM */ public class ExceptionFilters { - private ExceptionFilters() { - } + private ExceptionFilters() { + } - @Nonnull - public static List getFilters(@Nonnull GlobalSearchScope searchScope) { - ExceptionFilterFactory[] extensions = ExceptionFilterFactory.EP_NAME.getExtensions(); - List filters = new ArrayList(extensions.length); - for (ExceptionFilterFactory extension : extensions) { - filters.add(extension.create(searchScope)); + @Nonnull + public static List getFilters(@Nonnull GlobalSearchScope searchScope) { + ExceptionFilterFactory[] extensions = ExceptionFilterFactory.EP_NAME.getExtensions(); + List filters = new ArrayList(extensions.length); + for (ExceptionFilterFactory extension : extensions) { + filters.add(extension.create(searchScope)); + } + return filters; } - return filters; - } } diff --git a/java-execution-api/src/main/java/com/intellij/java/execution/runners/JavaPatchableProgramRunner.java b/java-execution-api/src/main/java/com/intellij/java/execution/runners/JavaPatchableProgramRunner.java index b339ed50aa..10e4ee2f0e 100644 --- a/java-execution-api/src/main/java/com/intellij/java/execution/runners/JavaPatchableProgramRunner.java +++ b/java-execution-api/src/main/java/com/intellij/java/execution/runners/JavaPatchableProgramRunner.java @@ -26,15 +26,18 @@ * @author spleaner */ public abstract class JavaPatchableProgramRunner extends GenericProgramRunner { + public abstract void patch( + OwnJavaParameters javaParameters, + RunnerSettings settings, + RunProfile runProfile, + final boolean beforeExecution + ) throws ExecutionException; - public abstract void patch(OwnJavaParameters javaParameters, RunnerSettings settings, RunProfile runProfile, final boolean beforeExecution) throws ExecutionException; - - - protected static void runCustomPatchers(OwnJavaParameters javaParameters, Executor executor, RunProfile runProfile) { - if (runProfile != null) { - for (JavaProgramPatcher patcher : JavaProgramPatcher.EP_NAME.getExtensionList()) { - patcher.patchJavaParameters(executor, runProfile, javaParameters); - } + protected static void runCustomPatchers(OwnJavaParameters javaParameters, Executor executor, RunProfile runProfile) { + if (runProfile != null) { + for (JavaProgramPatcher patcher : JavaProgramPatcher.EP_NAME.getExtensionList()) { + patcher.patchJavaParameters(executor, runProfile, javaParameters); + } + } } - } } diff --git a/java-execution-api/src/main/java/com/intellij/java/execution/runners/JavaProgramPatcher.java b/java-execution-api/src/main/java/com/intellij/java/execution/runners/JavaProgramPatcher.java index fa93917898..93e3b857b9 100644 --- a/java-execution-api/src/main/java/com/intellij/java/execution/runners/JavaProgramPatcher.java +++ b/java-execution-api/src/main/java/com/intellij/java/execution/runners/JavaProgramPatcher.java @@ -14,7 +14,7 @@ */ @ExtensionAPI(ComponentScope.APPLICATION) public abstract class JavaProgramPatcher { - public static final ExtensionPointName EP_NAME = ExtensionPointName.create(JavaProgramPatcher.class); + public static final ExtensionPointName EP_NAME = ExtensionPointName.create(JavaProgramPatcher.class); - public abstract void patchJavaParameters(Executor executor, RunProfile configuration, OwnJavaParameters javaParameters); + public abstract void patchJavaParameters(Executor executor, RunProfile configuration, OwnJavaParameters javaParameters); } diff --git a/java-execution-impl/src/main/java/com/intellij/java/execution/impl/JavaTestFrameworkRunnableState.java b/java-execution-impl/src/main/java/com/intellij/java/execution/impl/JavaTestFrameworkRunnableState.java index 993cb75a0a..2c97aab0a0 100644 --- a/java-execution-impl/src/main/java/com/intellij/java/execution/impl/JavaTestFrameworkRunnableState.java +++ b/java-execution-impl/src/main/java/com/intellij/java/execution/impl/JavaTestFrameworkRunnableState.java @@ -90,393 +90,409 @@ public abstract class JavaTestFrameworkRunnableState & CommonJavaRunConfigurationParameters & ConfigurationWithCommandLineShortener & SMRunnerConsolePropertiesProvider> extends JavaCommandLineState implements RemoteConnectionCreator { - private static final Logger LOG = Logger.getInstance(JavaTestFrameworkRunnableState.class); - protected ServerSocket myServerSocket; - protected File myTempFile; - protected File myWorkingDirsFile = null; + private static final Logger LOG = Logger.getInstance(JavaTestFrameworkRunnableState.class); + protected ServerSocket myServerSocket; + protected File myTempFile; + protected File myWorkingDirsFile = null; - private RemoteConnectionCreator remoteConnectionCreator; - private final List myArgumentFileFilters = new ArrayList<>(); + private RemoteConnectionCreator remoteConnectionCreator; + private final List myArgumentFileFilters = new ArrayList<>(); - public void setRemoteConnectionCreator(RemoteConnectionCreator remoteConnectionCreator) { - this.remoteConnectionCreator = remoteConnectionCreator; - } + public void setRemoteConnectionCreator(RemoteConnectionCreator remoteConnectionCreator) { + this.remoteConnectionCreator = remoteConnectionCreator; + } - @Nullable - @Override - public RemoteConnection createRemoteConnection(ExecutionEnvironment environment) { - return remoteConnectionCreator == null ? null : remoteConnectionCreator.createRemoteConnection(environment); - } + @Nullable + @Override + public RemoteConnection createRemoteConnection(ExecutionEnvironment environment) { + return remoteConnectionCreator == null ? null : remoteConnectionCreator.createRemoteConnection(environment); + } - @Override - public boolean isPollConnection() { - return remoteConnectionCreator != null && remoteConnectionCreator.isPollConnection(); - } + @Override + public boolean isPollConnection() { + return remoteConnectionCreator != null && remoteConnectionCreator.isPollConnection(); + } - public JavaTestFrameworkRunnableState(ExecutionEnvironment environment) { - super(environment); - } + public JavaTestFrameworkRunnableState(ExecutionEnvironment environment) { + super(environment); + } - @Nonnull - protected abstract String getFrameworkName(); + @Nonnull + protected abstract String getFrameworkName(); - @Nonnull - protected abstract String getFrameworkId(); + @Nonnull + protected abstract String getFrameworkId(); - protected abstract void passTempFile(ParametersList parametersList, String tempFilePath); + protected abstract void passTempFile(ParametersList parametersList, String tempFilePath); - @Nonnull - protected abstract T getConfiguration(); + @Nonnull + protected abstract T getConfiguration(); - @Nullable - protected abstract TestSearchScope getScope(); + @Nullable + protected abstract TestSearchScope getScope(); - @Nonnull - protected abstract String getForkMode(); + @Nonnull + protected abstract String getForkMode(); - @Nonnull - protected abstract ProcessHandler createHandler(Executor executor) throws ExecutionException; + @Nonnull + protected abstract ProcessHandler createHandler(Executor executor) throws ExecutionException; - public SearchForTestsTask createSearchingForTestsTask() { - return null; - } + public SearchForTestsTask createSearchingForTestsTask() { + return null; + } - protected boolean configureByModule(Module module) { - return module != null; - } + protected boolean configureByModule(Module module) { + return module != null; + } - protected boolean isIdBasedTestTree() { - return false; - } + protected boolean isIdBasedTestTree() { + return false; + } - @Override - protected GeneralCommandLine createCommandLine() throws ExecutionException { - GeneralCommandLine commandLine = super.createCommandLine(); - Map content = commandLine.getUserData(OwnJdkUtil.COMMAND_LINE_CONTENT); - if (content != null) { - content.forEach((key, value) -> myArgumentFileFilters.add(new ArgumentFileFilter(key, value))); + @Override + protected GeneralCommandLine createCommandLine() throws ExecutionException { + GeneralCommandLine commandLine = super.createCommandLine(); + Map content = commandLine.getUserData(OwnJdkUtil.COMMAND_LINE_CONTENT); + if (content != null) { + content.forEach((key, value) -> myArgumentFileFilters.add(new ArgumentFileFilter(key, value))); + } + return commandLine; } - return commandLine; - } - @Nonnull - @Override - public ExecutionResult execute(@Nonnull Executor executor, @Nonnull ProgramRunner runner) throws ExecutionException { - final RunnerSettings runnerSettings = getRunnerSettings(); + @Nonnull + @Override + public ExecutionResult execute(@Nonnull Executor executor, @Nonnull ProgramRunner runner) throws ExecutionException { + final RunnerSettings runnerSettings = getRunnerSettings(); - final SMTRunnerConsoleProperties testConsoleProperties = getConfiguration().createTestConsoleProperties(executor); - testConsoleProperties.setIdBasedTestTree(isIdBasedTestTree()); - testConsoleProperties.setIfUndefined(TestConsoleProperties.HIDE_PASSED_TESTS, false); + final SMTRunnerConsoleProperties testConsoleProperties = getConfiguration().createTestConsoleProperties(executor); + testConsoleProperties.setIdBasedTestTree(isIdBasedTestTree()); + testConsoleProperties.setIfUndefined(TestConsoleProperties.HIDE_PASSED_TESTS, false); - final BaseTestsOutputConsoleView consoleView = SMTestRunnerConnectionUtil.createConsole(getFrameworkName(), testConsoleProperties); - final SMTestRunnerResultsForm viewer = ((SMTRunnerConsoleView) consoleView).getResultsViewer(); - Disposer.register(getConfiguration().getProject(), consoleView); + final BaseTestsOutputConsoleView consoleView = SMTestRunnerConnectionUtil.createConsole(getFrameworkName(), testConsoleProperties); + final SMTestRunnerResultsForm viewer = ((SMTRunnerConsoleView)consoleView).getResultsViewer(); + Disposer.register(getConfiguration().getProject(), consoleView); - final ProcessHandler handler = createHandler(executor); + final ProcessHandler handler = createHandler(executor); - for (ArgumentFileFilter filter : myArgumentFileFilters) { - consoleView.addMessageFilter(filter); - } + for (ArgumentFileFilter filter : myArgumentFileFilters) { + consoleView.addMessageFilter(filter); + } - consoleView.attachToProcess(handler); - final AbstractTestProxy root = viewer.getRoot(); - if (root instanceof TestProxyRoot testProxyRoot) { - testProxyRoot.setHandler(handler); - } - handler.addProcessListener(new ProcessAdapter() { - @Override - public void startNotified(@Nonnull ProcessEvent event) { - if (getConfiguration().isSaveOutputToFile()) { - final File file = OutputFileUtil.getOutputFile(getConfiguration()); - root.setOutputFilePath(file != null ? file.getAbsolutePath() : null); + consoleView.attachToProcess(handler); + final AbstractTestProxy root = viewer.getRoot(); + if (root instanceof TestProxyRoot testProxyRoot) { + testProxyRoot.setHandler(handler); } - } - - @Override - public void processTerminated(@Nonnull ProcessEvent event) { - Runnable runnable = () -> - { - root.flushOutputFile(); - deleteTempFiles(); - clear(); - }; - UIUtil.invokeLaterIfNeeded(runnable); - handler.removeProcessListener(this); - } - }); - - AbstractRerunFailedTestsAction rerunFailedTestsAction = testConsoleProperties.createRerunFailedTestsAction(consoleView); - LOG.assertTrue(rerunFailedTestsAction != null); - rerunFailedTestsAction.setModelProvider(() -> viewer); - - final DefaultExecutionResult result = new DefaultExecutionResult(consoleView, handler); - result.setRestartActions(rerunFailedTestsAction, new ToggleAutoTestAction() { - @Override - public boolean isDelayApplicable() { - return false; - } - - @Override - public AbstractAutoTestManager getAutoTestManager(Project project) { - return JavaAutoRunManager.getInstance(project); - } - }); - - JavaRunConfigurationExtensionManager.getInstance().attachExtensionsToProcess(getConfiguration(), handler, runnerSettings); - return result; - } - - protected abstract void configureRTClasspath(OwnJavaParameters javaParameters) throws CantRunException; - - @Override - protected OwnJavaParameters createJavaParameters() throws ExecutionException { - final OwnJavaParameters javaParameters = new OwnJavaParameters(); - Project project = getConfiguration().getProject(); - final Module module = getConfiguration().getConfigurationModule().getModule(); - - Sdk jdk = module == null ? null : ModuleUtilCore.getSdk(module, JavaModuleExtension.class); - javaParameters.setJdk(jdk); - - final String parameters = getConfiguration().getProgramParameters(); - getConfiguration().setProgramParameters(null); - try { - JavaParametersUtil.configureConfiguration(javaParameters, getConfiguration()); - } finally { - getConfiguration().setProgramParameters(parameters); + handler.addProcessListener(new ProcessAdapter() { + @Override + public void startNotified(@Nonnull ProcessEvent event) { + if (getConfiguration().isSaveOutputToFile()) { + final File file = OutputFileUtil.getOutputFile(getConfiguration()); + root.setOutputFilePath(file != null ? file.getAbsolutePath() : null); + } + } + + @Override + public void processTerminated(@Nonnull ProcessEvent event) { + Runnable runnable = () -> + { + root.flushOutputFile(); + deleteTempFiles(); + clear(); + }; + UIUtil.invokeLaterIfNeeded(runnable); + handler.removeProcessListener(this); + } + }); + + AbstractRerunFailedTestsAction rerunFailedTestsAction = testConsoleProperties.createRerunFailedTestsAction(consoleView); + LOG.assertTrue(rerunFailedTestsAction != null); + rerunFailedTestsAction.setModelProvider(() -> viewer); + + final DefaultExecutionResult result = new DefaultExecutionResult(consoleView, handler); + result.setRestartActions(rerunFailedTestsAction, new ToggleAutoTestAction() { + @Override + public boolean isDelayApplicable() { + return false; + } + + @Override + public AbstractAutoTestManager getAutoTestManager(Project project) { + return JavaAutoRunManager.getInstance(project); + } + }); + + JavaRunConfigurationExtensionManager.getInstance().attachExtensionsToProcess(getConfiguration(), handler, runnerSettings); + return result; } - javaParameters.getClassPath().addFirst(JavaSdkUtil.getJavaRtJarPath()); - configureClasspath(javaParameters); - final JavaTestPatcher[] patchers = JavaTestPatcher.EP_NAME.getExtensions(); - for (JavaTestPatcher patcher : patchers) { - patcher.patchJavaParameters(module, javaParameters); - } + protected abstract void configureRTClasspath(OwnJavaParameters javaParameters) throws CantRunException; - // Append coverage parameters if appropriate - for (RunConfigurationExtension ext : RunConfigurationExtension.EP_NAME.getExtensionList()) { - ext.updateJavaParameters(getConfiguration(), javaParameters, getRunnerSettings()); - } + @Override + protected OwnJavaParameters createJavaParameters() throws ExecutionException { + final OwnJavaParameters javaParameters = new OwnJavaParameters(); + Project project = getConfiguration().getProject(); + final Module module = getConfiguration().getConfigurationModule().getModule(); - if (!StringUtil.isEmptyOrSpaces(parameters)) { - javaParameters.getProgramParametersList().addAll(getNamedParams(parameters)); - } + Sdk jdk = module == null ? null : ModuleUtilCore.getSdk(module, JavaModuleExtension.class); + javaParameters.setJdk(jdk); - if (ConsoleBuffer.useCycleBuffer()) { - javaParameters.getVMParametersList().addProperty("idea.test.cyclic.buffer.size", String.valueOf(ConsoleBuffer.getCycleBufferSize())); - } + final String parameters = getConfiguration().getProgramParameters(); + getConfiguration().setProgramParameters(null); + try { + JavaParametersUtil.configureConfiguration(javaParameters, getConfiguration()); + } + finally { + getConfiguration().setProgramParameters(parameters); + } + javaParameters.getClassPath().addFirst(JavaSdkUtil.getJavaRtJarPath()); + configureClasspath(javaParameters); - javaParameters.setShortenCommandLine(getConfiguration().getShortenCommandLine(), project); - return javaParameters; - } + final JavaTestPatcher[] patchers = JavaTestPatcher.EP_NAME.getExtensions(); + for (JavaTestPatcher patcher : patchers) { + patcher.patchJavaParameters(module, javaParameters); + } - protected List getNamedParams(String parameters) { - return Collections.singletonList("@name" + parameters); - } + // Append coverage parameters if appropriate + for (RunConfigurationExtension ext : RunConfigurationExtension.EP_NAME.getExtensionList()) { + ext.updateJavaParameters(getConfiguration(), javaParameters, getRunnerSettings()); + } - private ServerSocket myForkSocket = null; + if (!StringUtil.isEmptyOrSpaces(parameters)) { + javaParameters.getProgramParametersList().addAll(getNamedParams(parameters)); + } - @Nullable - public ServerSocket getForkSocket() { - if (myForkSocket == null && (!Comparing.strEqual(getForkMode(), "none") || forkPerModule()) && getRunnerSettings() != null) { - try { - myForkSocket = new ServerSocket(0, 0, InetAddress.getByName("127.0.0.1")); - } catch (IOException e) { - LOG.error(e); - } - } - return myForkSocket; - } - - private boolean isExecutorDisabledInForkedMode() { - final RunnerSettings settings = getRunnerSettings(); - return settings != null && !(settings instanceof GenericDebuggerRunnerSettings); - } - - protected void appendForkInfo(Executor executor) throws ExecutionException { - final String forkMode = getForkMode(); - if (Comparing.strEqual(forkMode, "none")) { - if (forkPerModule()) { - if (isExecutorDisabledInForkedMode()) { - final String actionName = executor.getStartActionText().map(Presentation.NO_MNEMONIC).get(); - throw new CantRunException("'" + actionName + "' is disabled when per-module working directory is configured.
" + "Please specify single working directory, or change test " + - "scope to single module."); + if (ConsoleBuffer.useCycleBuffer()) { + javaParameters.getVMParametersList() + .addProperty("idea.test.cyclic.buffer.size", String.valueOf(ConsoleBuffer.getCycleBufferSize())); } - } else { - return; - } - } else if (isExecutorDisabledInForkedMode()) { - final String actionName = executor.getStartActionText().toLowerCase().map(Presentation.NO_MNEMONIC).get(); - throw new CantRunException(actionName + " is disabled in fork mode.
Please change fork mode to <none> to " + actionName + "."); + + javaParameters.setShortenCommandLine(getConfiguration().getShortenCommandLine(), project); + return javaParameters; } - final OwnJavaParameters javaParameters = getJavaParameters(); - final Sdk jdk = javaParameters.getJdk(); - if (jdk == null) { - throw new ExecutionException(ExecutionLocalize.runConfigurationErrorNoJdkSpecified().get()); + protected List getNamedParams(String parameters) { + return Collections.singletonList("@name" + parameters); } - try { - final File tempFile = FileUtil.createTempFile("command.line", "", true); - try (PrintWriter writer = new PrintWriter(tempFile, CharsetToolkit.UTF8)) { - if (forkPerModule()) { - writer.println("use classpath jar"); - } else { - writer.println(""); + private ServerSocket myForkSocket = null; + + @Nullable + public ServerSocket getForkSocket() { + if (myForkSocket == null && (!Comparing.strEqual(getForkMode(), "none") || forkPerModule()) && getRunnerSettings() != null) { + try { + myForkSocket = new ServerSocket(0, 0, InetAddress.getByName("127.0.0.1")); + } + catch (IOException e) { + LOG.error(e); + } } + return myForkSocket; + } - JavaSdkType sdkType = (JavaSdkType) jdk.getSdkType(); - GeneralCommandLine commandLine = new GeneralCommandLine(); - sdkType.setupCommandLine(commandLine, jdk); + private boolean isExecutorDisabledInForkedMode() { + final RunnerSettings settings = getRunnerSettings(); + return settings != null && !(settings instanceof GenericDebuggerRunnerSettings); + } - List commandLineList = commandLine.getCommandLineList(null); - for (String line : commandLineList) { - writer.println(line); + protected void appendForkInfo(Executor executor) throws ExecutionException { + final String forkMode = getForkMode(); + if (Comparing.strEqual(forkMode, "none")) { + if (forkPerModule()) { + if (isExecutorDisabledInForkedMode()) { + final String actionName = executor.getStartActionText().map(Presentation.NO_MNEMONIC).get(); + throw new CantRunException("'" + actionName + "' is disabled when per-module working directory is configured.
" + "Please specify single working directory, or change test " + + "scope to single module."); + } + } + else { + return; + } + } + else if (isExecutorDisabledInForkedMode()) { + final String actionName = executor.getStartActionText().toLowerCase().map(Presentation.NO_MNEMONIC).get(); + throw new CantRunException(actionName + " is disabled in fork mode.
Please change fork mode to <none> to " + actionName + "."); } - for (String vmParameter : javaParameters.getVMParametersList().getList()) { - writer.println(vmParameter); + final OwnJavaParameters javaParameters = getJavaParameters(); + final Sdk jdk = javaParameters.getJdk(); + if (jdk == null) { + throw new ExecutionException(ExecutionLocalize.runConfigurationErrorNoJdkSpecified().get()); } - } - passForkMode(getForkMode(), tempFile, javaParameters); - } catch (IOException e) { - LOG.error(e); - } - } - - protected abstract void passForkMode(String forkMode, File tempFile, OwnJavaParameters parameters) throws ExecutionException; - - protected void collectListeners(OwnJavaParameters javaParameters, StringBuilder buf, Class epName, String delimiter) { - final T configuration = getConfiguration(); - final Object[] listeners = Application.get().getExtensionPoint(epName).getExtensions(); - for (final Object listener : listeners) { - boolean enabled = true; - for (RunConfigurationExtension ext : Extensions.getExtensions(RunConfigurationExtension.EP_NAME)) { - if (ext.isListenerDisabled(configuration, listener, getRunnerSettings())) { - enabled = false; - break; + try { + final File tempFile = FileUtil.createTempFile("command.line", "", true); + try (PrintWriter writer = new PrintWriter(tempFile, CharsetToolkit.UTF8)) { + if (forkPerModule()) { + writer.println("use classpath jar"); + } + else { + writer.println(""); + } + + JavaSdkType sdkType = (JavaSdkType)jdk.getSdkType(); + GeneralCommandLine commandLine = new GeneralCommandLine(); + sdkType.setupCommandLine(commandLine, jdk); + + List commandLineList = commandLine.getCommandLineList(null); + for (String line : commandLineList) { + writer.println(line); + } + + for (String vmParameter : javaParameters.getVMParametersList().getList()) { + writer.println(vmParameter); + } + } + + passForkMode(getForkMode(), tempFile, javaParameters); } - } - if (enabled) { - if (buf.length() > 0) { - buf.append(delimiter); + catch (IOException e) { + LOG.error(e); } - final Class classListener = listener.getClass(); - buf.append(classListener.getName()); - javaParameters.getClassPath().add(ClassPathUtil.getJarPathForClass(classListener)); - } - } - } - - protected void configureClasspath(final OwnJavaParameters javaParameters) throws CantRunException { - configureRTClasspath(javaParameters); - RunConfigurationModule module = getConfiguration().getConfigurationModule(); - final String alternativeJreName = getConfiguration().isAlternativeJrePathEnabled() ? getConfiguration().getAlternativeJrePath() : null; - final int pathType = OwnJavaParameters.JDK_AND_CLASSES_AND_TESTS; - if (configureByModule(module.getModule())) { - JavaParametersUtil.configureModule(module, javaParameters, pathType, alternativeJreName); - } else { - JavaParametersUtil.configureProject(getConfiguration().getProject(), javaParameters, pathType, alternativeJreName); } - } - - protected void createServerSocket(OwnJavaParameters javaParameters) { - try { - myServerSocket = new ServerSocket(0, 0, InetAddress.getByName("127.0.0.1")); - javaParameters.getProgramParametersList().add("-socket" + myServerSocket.getLocalPort()); - } catch (IOException e) { - LOG.error(e); + + protected abstract void passForkMode(String forkMode, File tempFile, OwnJavaParameters parameters) throws ExecutionException; + + protected void collectListeners(OwnJavaParameters javaParameters, StringBuilder buf, Class epName, String delimiter) { + final T configuration = getConfiguration(); + final Object[] listeners = Application.get().getExtensionPoint(epName).getExtensions(); + for (final Object listener : listeners) { + boolean enabled = true; + for (RunConfigurationExtension ext : Extensions.getExtensions(RunConfigurationExtension.EP_NAME)) { + if (ext.isListenerDisabled(configuration, listener, getRunnerSettings())) { + enabled = false; + break; + } + } + if (enabled) { + if (buf.length() > 0) { + buf.append(delimiter); + } + final Class classListener = listener.getClass(); + buf.append(classListener.getName()); + javaParameters.getClassPath().add(ClassPathUtil.getJarPathForClass(classListener)); + } + } } - } - - protected boolean spansMultipleModules(final String qualifiedName) { - if (qualifiedName != null) { - final Project project = getConfiguration().getProject(); - final PsiJavaPackage aPackage = JavaPsiFacade.getInstance(project).findPackage(qualifiedName); - if (aPackage != null) { - final TestSearchScope scope = getScope(); - if (scope != null) { - final SourceScope sourceScope = scope.getSourceScope(getConfiguration()); - if (sourceScope != null) { - final GlobalSearchScope configurationSearchScope = GlobalSearchScopesCore.projectTestScope(project).intersectWith(sourceScope.getGlobalSearchScope()); - final PsiDirectory[] directories = aPackage.getDirectories(configurationSearchScope); - return Arrays.stream(directories).map(dir -> ModuleUtilCore.findModuleForFile(dir.getVirtualFile(), project)).filter(Objects::nonNull).distinct().count() > 1; - } + + protected void configureClasspath(final OwnJavaParameters javaParameters) throws CantRunException { + configureRTClasspath(javaParameters); + RunConfigurationModule module = getConfiguration().getConfigurationModule(); + final String alternativeJreName = + getConfiguration().isAlternativeJrePathEnabled() ? getConfiguration().getAlternativeJrePath() : null; + final int pathType = OwnJavaParameters.JDK_AND_CLASSES_AND_TESTS; + if (configureByModule(module.getModule())) { + JavaParametersUtil.configureModule(module, javaParameters, pathType, alternativeJreName); + } + else { + JavaParametersUtil.configureProject(getConfiguration().getProject(), javaParameters, pathType, alternativeJreName); } - } } - return false; - } - - /** - * Configuration based on package which spans multiple modules - */ - protected boolean forkPerModule() { - final String workingDirectory = getConfiguration().getWorkingDirectory(); - return getScope() != TestSearchScope.SINGLE_MODULE && ("$" + PathMacroUtil.MODULE_DIR_MACRO_NAME + "$").equals(workingDirectory) && spansMultipleModules(getConfiguration().getPackage()); - } - - protected void createTempFiles(OwnJavaParameters javaParameters) { - try { - myWorkingDirsFile = FileUtil.createTempFile("idea_working_dirs_" + getFrameworkId(), ".tmp", true); - javaParameters.getProgramParametersList().add("@w@" + myWorkingDirsFile.getAbsolutePath()); - - myTempFile = FileUtil.createTempFile("idea_" + getFrameworkId(), ".tmp", true); - passTempFile(javaParameters.getProgramParametersList(), myTempFile.getAbsolutePath()); - } catch (Exception e) { - LOG.error(e); + + protected void createServerSocket(OwnJavaParameters javaParameters) { + try { + myServerSocket = new ServerSocket(0, 0, InetAddress.getByName("127.0.0.1")); + javaParameters.getProgramParametersList().add("-socket" + myServerSocket.getLocalPort()); + } + catch (IOException e) { + LOG.error(e); + } } - } - - protected void writeClassesPerModule( - String packageName, - OwnJavaParameters javaParameters, - Map> perModule - ) throws FileNotFoundException, UnsupportedEncodingException, CantRunException { - if (perModule != null) { - final String classpath = getScope() == TestSearchScope.WHOLE_PROJECT ? null : javaParameters.getClassPath().getPathsString(); - - try (PrintWriter wWriter = new PrintWriter(myWorkingDirsFile, CharsetToolkit.UTF8)) { - wWriter.println(packageName); - for (Module module : perModule.keySet()) { - wWriter.println(module.getModuleDirPath()); - wWriter.println(module.getName()); - - if (classpath == null) { - final OwnJavaParameters parameters = new OwnJavaParameters(); - parameters.getClassPath().add(JavaSdkUtil.getJavaRtJarPath()); - configureRTClasspath(parameters); - JavaParametersUtil.configureModule( - module, - parameters, - OwnJavaParameters.JDK_AND_CLASSES_AND_TESTS, - getConfiguration().isAlternativeJrePathEnabled() ? getConfiguration() - .getAlternativeJrePath() : null - ); - wWriter.println(parameters.getClassPath().getPathsString()); - } - else { - wWriter.println(classpath); - } - - final List classNames = perModule.get(module); - wWriter.println(classNames.size()); - for (String className : classNames) { - wWriter.println(className); - } + + protected boolean spansMultipleModules(final String qualifiedName) { + if (qualifiedName != null) { + final Project project = getConfiguration().getProject(); + final PsiJavaPackage aPackage = JavaPsiFacade.getInstance(project).findPackage(qualifiedName); + if (aPackage != null) { + final TestSearchScope scope = getScope(); + if (scope != null) { + final SourceScope sourceScope = scope.getSourceScope(getConfiguration()); + if (sourceScope != null) { + final GlobalSearchScope configurationSearchScope = + GlobalSearchScopesCore.projectTestScope(project).intersectWith(sourceScope.getGlobalSearchScope()); + final PsiDirectory[] directories = aPackage.getDirectories(configurationSearchScope); + return Arrays.stream(directories) + .map(dir -> ModuleUtilCore.findModuleForFile(dir.getVirtualFile(), project)) + .filter(Objects::nonNull) + .distinct() + .count() > 1; + } + } + } } - } + return false; + } + + /** + * Configuration based on package which spans multiple modules + */ + protected boolean forkPerModule() { + final String workingDirectory = getConfiguration().getWorkingDirectory(); + return getScope() != TestSearchScope.SINGLE_MODULE && ("$" + PathMacroUtil.MODULE_DIR_MACRO_NAME + "$").equals(workingDirectory) && spansMultipleModules( + getConfiguration().getPackage()); } - } - protected void deleteTempFiles() { - if (myTempFile != null) { - FileUtil.delete(myTempFile); + protected void createTempFiles(OwnJavaParameters javaParameters) { + try { + myWorkingDirsFile = FileUtil.createTempFile("idea_working_dirs_" + getFrameworkId(), ".tmp", true); + javaParameters.getProgramParametersList().add("@w@" + myWorkingDirsFile.getAbsolutePath()); + + myTempFile = FileUtil.createTempFile("idea_" + getFrameworkId(), ".tmp", true); + passTempFile(javaParameters.getProgramParametersList(), myTempFile.getAbsolutePath()); + } + catch (Exception e) { + LOG.error(e); + } } - if (myWorkingDirsFile != null) { - FileUtil.delete(myWorkingDirsFile); + protected void writeClassesPerModule( + String packageName, + OwnJavaParameters javaParameters, + Map> perModule + ) throws FileNotFoundException, UnsupportedEncodingException, CantRunException { + if (perModule != null) { + final String classpath = getScope() == TestSearchScope.WHOLE_PROJECT ? null : javaParameters.getClassPath().getPathsString(); + + try (PrintWriter wWriter = new PrintWriter(myWorkingDirsFile, CharsetToolkit.UTF8)) { + wWriter.println(packageName); + for (Module module : perModule.keySet()) { + wWriter.println(module.getModuleDirPath()); + wWriter.println(module.getName()); + + if (classpath == null) { + final OwnJavaParameters parameters = new OwnJavaParameters(); + parameters.getClassPath().add(JavaSdkUtil.getJavaRtJarPath()); + configureRTClasspath(parameters); + JavaParametersUtil.configureModule( + module, + parameters, + OwnJavaParameters.JDK_AND_CLASSES_AND_TESTS, + getConfiguration().isAlternativeJrePathEnabled() ? getConfiguration() + .getAlternativeJrePath() : null + ); + wWriter.println(parameters.getClassPath().getPathsString()); + } + else { + wWriter.println(classpath); + } + + final List classNames = perModule.get(module); + wWriter.println(classNames.size()); + for (String className : classNames) { + wWriter.println(className); + } + } + } + } } - } + protected void deleteTempFiles() { + if (myTempFile != null) { + FileUtil.delete(myTempFile); + } + + if (myWorkingDirsFile != null) { + FileUtil.delete(myWorkingDirsFile); + } + } } diff --git a/java-execution-impl/src/main/java/com/intellij/java/execution/impl/RunConfigurationExtension.java b/java-execution-impl/src/main/java/com/intellij/java/execution/impl/RunConfigurationExtension.java index e2c87dc473..3cf46fd5b8 100644 --- a/java-execution-impl/src/main/java/com/intellij/java/execution/impl/RunConfigurationExtension.java +++ b/java-execution-impl/src/main/java/com/intellij/java/execution/impl/RunConfigurationExtension.java @@ -40,50 +40,61 @@ @ExtensionAPI(ComponentScope.APPLICATION) public abstract class RunConfigurationExtension extends RunConfigurationExtensionBase { - public static final ExtensionPointName EP_NAME = ExtensionPointName.create(RunConfigurationExtension.class); + public static final ExtensionPointName EP_NAME = ExtensionPointName.create(RunConfigurationExtension.class); - public abstract void updateJavaParameters(final T configuration, final OwnJavaParameters params, RunnerSettings runnerSettings) throws ExecutionException; + public abstract void updateJavaParameters( + final T configuration, + final OwnJavaParameters params, + RunnerSettings runnerSettings + ) throws ExecutionException; + @Override + protected void patchCommandLine( + @Nonnull RunConfigurationBase configuration, + RunnerSettings runnerSettings, + @Nonnull GeneralCommandLine cmdLine, + @Nonnull String runnerId + ) throws ExecutionException { + } - @Override - protected void patchCommandLine(@Nonnull RunConfigurationBase configuration, - RunnerSettings runnerSettings, - @Nonnull GeneralCommandLine cmdLine, - @Nonnull String runnerId) throws ExecutionException {} - - @Override - protected boolean isEnabledFor(@Nonnull RunConfigurationBase applicableConfiguration, @Nullable RunnerSettings runnerSettings) { - return true; - } + @Override + protected boolean isEnabledFor(@Nonnull RunConfigurationBase applicableConfiguration, @Nullable RunnerSettings runnerSettings) { + return true; + } - @Override - protected void extendTemplateConfiguration(@Nonnull RunConfigurationBase configuration) { - } + @Override + protected void extendTemplateConfiguration(@Nonnull RunConfigurationBase configuration) { + } - public void cleanUserData(RunConfigurationBase runConfigurationBase) {} + public void cleanUserData(RunConfigurationBase runConfigurationBase) { + } - public static void cleanExtensionsUserData(RunConfigurationBase runConfigurationBase) { - for (RunConfigurationExtension extension : EP_NAME.getExtensionList()) { - extension.cleanUserData(runConfigurationBase); + public static void cleanExtensionsUserData(RunConfigurationBase runConfigurationBase) { + for (RunConfigurationExtension extension : EP_NAME.getExtensionList()) { + extension.cleanUserData(runConfigurationBase); + } } - } - public RefactoringElementListener wrapElementListener(PsiElement element, - RunConfigurationBase runJavaConfiguration, - RefactoringElementListener listener) { - return listener; - } + public RefactoringElementListener wrapElementListener( + PsiElement element, + RunConfigurationBase runJavaConfiguration, + RefactoringElementListener listener + ) { + return listener; + } - public static RefactoringElementListener wrapRefactoringElementListener(PsiElement element, - RunConfigurationBase runConfigurationBase, - RefactoringElementListener listener) { - for (RunConfigurationExtension extension : Extensions.getExtensions(EP_NAME)) { - listener = extension.wrapElementListener(element, runConfigurationBase, listener); + public static RefactoringElementListener wrapRefactoringElementListener( + PsiElement element, + RunConfigurationBase runConfigurationBase, + RefactoringElementListener listener + ) { + for (RunConfigurationExtension extension : Extensions.getExtensions(EP_NAME)) { + listener = extension.wrapElementListener(element, runConfigurationBase, listener); + } + return listener; } - return listener; - } - public boolean isListenerDisabled(RunConfigurationBase configuration, Object listener, RunnerSettings runnerSettings) { - return false; - } + public boolean isListenerDisabled(RunConfigurationBase configuration, Object listener, RunnerSettings runnerSettings) { + return false; + } } \ No newline at end of file diff --git a/java-execution-impl/src/main/java/com/intellij/java/execution/impl/application/BaseJavaApplicationCommandLineState.java b/java-execution-impl/src/main/java/com/intellij/java/execution/impl/application/BaseJavaApplicationCommandLineState.java index c67f2e5f1f..fc07609c74 100644 --- a/java-execution-impl/src/main/java/com/intellij/java/execution/impl/application/BaseJavaApplicationCommandLineState.java +++ b/java-execution-impl/src/main/java/com/intellij/java/execution/impl/application/BaseJavaApplicationCommandLineState.java @@ -32,37 +32,37 @@ * @author nik */ public abstract class BaseJavaApplicationCommandLineState extends JavaCommandLineState { - protected final T myConfiguration; + protected final T myConfiguration; - public BaseJavaApplicationCommandLineState(ExecutionEnvironment environment, @Nonnull final T configuration) { - super(environment); - myConfiguration = configuration; - } + public BaseJavaApplicationCommandLineState(ExecutionEnvironment environment, @Nonnull final T configuration) { + super(environment); + myConfiguration = configuration; + } - protected void setupJavaParameters(OwnJavaParameters params) throws ExecutionException { - JavaParametersUtil.configureConfiguration(params, myConfiguration); + protected void setupJavaParameters(OwnJavaParameters params) throws ExecutionException { + JavaParametersUtil.configureConfiguration(params, myConfiguration); - for (RunConfigurationExtension ext : RunConfigurationExtension.EP_NAME.getExtensionList()) { - ext.updateJavaParameters(getConfiguration(), params, getRunnerSettings()); + for (RunConfigurationExtension ext : RunConfigurationExtension.EP_NAME.getExtensionList()) { + ext.updateJavaParameters(getConfiguration(), params, getRunnerSettings()); + } } - } - @Override - protected void setupProcessHandler(@Nonnull ProcessHandler handler) { - JavaRunConfigurationExtensionManager.getInstance().attachExtensionsToProcess(getConfiguration(), handler, getRunnerSettings()); - } + @Override + protected void setupProcessHandler(@Nonnull ProcessHandler handler) { + JavaRunConfigurationExtensionManager.getInstance().attachExtensionsToProcess(getConfiguration(), handler, getRunnerSettings()); + } - @Override - protected void buildProcessHandler(@Nonnull ProcessHandlerBuilder builder) throws ExecutionException { - builder.colored().killable().silentReader(); - } + @Override + protected void buildProcessHandler(@Nonnull ProcessHandlerBuilder builder) throws ExecutionException { + builder.colored().killable().silentReader(); + } - @Override - protected boolean ansiColoringEnabled() { - return true; - } + @Override + protected boolean ansiColoringEnabled() { + return true; + } - protected T getConfiguration() { - return myConfiguration; - } + protected T getConfiguration() { + return myConfiguration; + } } diff --git a/java-execution-impl/src/main/java/com/intellij/java/execution/impl/ui/JreProvider.java b/java-execution-impl/src/main/java/com/intellij/java/execution/impl/ui/JreProvider.java index 5d0d3be419..f9cfd6c585 100644 --- a/java-execution-impl/src/main/java/com/intellij/java/execution/impl/ui/JreProvider.java +++ b/java-execution-impl/src/main/java/com/intellij/java/execution/impl/ui/JreProvider.java @@ -32,9 +32,8 @@ @DeprecationInfo("Unused") @ExtensionAPI(ComponentScope.APPLICATION) public interface JreProvider { + ExtensionPointName EP_NAME = ExtensionPointName.create(JreProvider.class); - ExtensionPointName EP_NAME = ExtensionPointName.create(JreProvider.class); - - @Nonnull - String getJrePath(); + @Nonnull + String getJrePath(); } diff --git a/java-indexing-impl/src/main/java/com/intellij/java/indexing/impl/search/CustomPropertyScopeProvider.java b/java-indexing-impl/src/main/java/com/intellij/java/indexing/impl/search/CustomPropertyScopeProvider.java index 6ab17374ba..8407f98b1e 100644 --- a/java-indexing-impl/src/main/java/com/intellij/java/indexing/impl/search/CustomPropertyScopeProvider.java +++ b/java-indexing-impl/src/main/java/com/intellij/java/indexing/impl/search/CustomPropertyScopeProvider.java @@ -23,7 +23,7 @@ @ExtensionAPI(ComponentScope.APPLICATION) public interface CustomPropertyScopeProvider { - ExtensionPointName EP_NAME = ExtensionPointName.create(CustomPropertyScopeProvider.class); + ExtensionPointName EP_NAME = ExtensionPointName.create(CustomPropertyScopeProvider.class); - SearchScope getScope(final Project project); + SearchScope getScope(final Project project); } \ No newline at end of file diff --git a/java-language-api/src/main/java/com/intellij/java/language/annoPackages/AnnotationPackageSupport.java b/java-language-api/src/main/java/com/intellij/java/language/annoPackages/AnnotationPackageSupport.java index 50eb9049da..a7054e9051 100644 --- a/java-language-api/src/main/java/com/intellij/java/language/annoPackages/AnnotationPackageSupport.java +++ b/java-language-api/src/main/java/com/intellij/java/language/annoPackages/AnnotationPackageSupport.java @@ -20,46 +20,48 @@ */ @ExtensionAPI(ComponentScope.APPLICATION) public interface AnnotationPackageSupport { - ExtensionPointName EP_NAME = ExtensionPointName.create(AnnotationPackageSupport.class); + ExtensionPointName EP_NAME = ExtensionPointName.create(AnnotationPackageSupport.class); - /** - * Returns nullability by a container annotation - * - * @param anno annotation to check - * @param context target PsiElement (usually, method or variable) - * @param types target types - * @param superPackage if true then the annotation is applied to the super-package - * @return NullabilityAnnotationInfo object if given annotation is recognized default annotation; null otherwise - */ - @Nullable - @RequiredReadAction - default NullabilityAnnotationInfo getNullabilityByContainerAnnotation(@Nonnull PsiAnnotation anno, - @Nonnull PsiElement context, - @Nonnull PsiAnnotation.TargetType[] types, - boolean superPackage) { - return null; - } + /** + * Returns nullability by a container annotation + * + * @param anno annotation to check + * @param context target PsiElement (usually, method or variable) + * @param types target types + * @param superPackage if true then the annotation is applied to the super-package + * @return NullabilityAnnotationInfo object if given annotation is recognized default annotation; null otherwise + */ + @Nullable + @RequiredReadAction + default NullabilityAnnotationInfo getNullabilityByContainerAnnotation( + @Nonnull PsiAnnotation anno, + @Nonnull PsiElement context, + @Nonnull PsiAnnotation.TargetType[] types, + boolean superPackage + ) { + return null; + } - /** - * @param nullability desired nullability - * @return list of explicit annotations which denote given nullability (and may denote additional semantics) - */ - @Nonnull - default List getNullabilityAnnotations(@Nonnull Nullability nullability) { - return Collections.emptyList(); - } + /** + * @param nullability desired nullability + * @return list of explicit annotations which denote given nullability (and may denote additional semantics) + */ + @Nonnull + default List getNullabilityAnnotations(@Nonnull Nullability nullability) { + return Collections.emptyList(); + } - /** - * @return true if the annotations defined by this support cannot be placed at wildcards or type parameters - */ - default boolean isTypeUseAnnotationLocationRestricted() { - return false; - } + /** + * @return true if the annotations defined by this support cannot be placed at wildcards or type parameters + */ + default boolean isTypeUseAnnotationLocationRestricted() { + return false; + } - /** - * @return true if the annotations defined by this support can be used to annotate local variables - */ - default boolean canAnnotateLocals() { - return true; - } + /** + * @return true if the annotations defined by this support can be used to annotate local variables + */ + default boolean canAnnotateLocals() { + return true; + } } diff --git a/java-language-api/src/main/java/com/intellij/java/language/codeInsight/AnnotationTargetUtil.java b/java-language-api/src/main/java/com/intellij/java/language/codeInsight/AnnotationTargetUtil.java index 12e4226c1f..8f6e257cec 100644 --- a/java-language-api/src/main/java/com/intellij/java/language/codeInsight/AnnotationTargetUtil.java +++ b/java-language-api/src/main/java/com/intellij/java/language/codeInsight/AnnotationTargetUtil.java @@ -35,322 +35,345 @@ * @author peter */ public class AnnotationTargetUtil { - private static final Logger LOG = Logger.getInstance(AnnotationTargetUtil.class); + private static final Logger LOG = Logger.getInstance(AnnotationTargetUtil.class); - public static final Set DEFAULT_TARGETS = Set.of( - TargetType.PACKAGE, TargetType.TYPE, TargetType.ANNOTATION_TYPE, TargetType.FIELD, TargetType.METHOD, TargetType.CONSTRUCTOR, - TargetType.PARAMETER, TargetType.LOCAL_VARIABLE, TargetType.MODULE, TargetType.RECORD_COMPONENT); + public static final Set DEFAULT_TARGETS = Set.of( + TargetType.PACKAGE, + TargetType.TYPE, + TargetType.ANNOTATION_TYPE, + TargetType.FIELD, + TargetType.METHOD, + TargetType.CONSTRUCTOR, + TargetType.PARAMETER, + TargetType.LOCAL_VARIABLE, + TargetType.MODULE, + TargetType.RECORD_COMPONENT + ); - private static final TargetType[] PACKAGE_TARGETS = {TargetType.PACKAGE}; - private static final TargetType[] TYPE_USE_TARGETS = {TargetType.TYPE_USE}; - private static final TargetType[] ANNOTATION_TARGETS = { - TargetType.ANNOTATION_TYPE, - TargetType.TYPE, - TargetType.TYPE_USE - }; - private static final TargetType[] TYPE_TARGETS = { - TargetType.TYPE, - TargetType.TYPE_USE - }; - private static final TargetType[] TYPE_PARAMETER_TARGETS = { - TargetType.TYPE_PARAMETER, - TargetType.TYPE_USE - }; - private static final TargetType[] CONSTRUCTOR_TARGETS = { - TargetType.CONSTRUCTOR, - TargetType.TYPE_USE - }; - private static final TargetType[] METHOD_TARGETS = { - TargetType.METHOD, - TargetType.TYPE_USE - }; - private static final TargetType[] FIELD_TARGETS = { - TargetType.FIELD, - TargetType.TYPE_USE - }; - private static final TargetType[] RECORD_COMPONENT_TARGETS = { - TargetType.RECORD_COMPONENT, - TargetType.FIELD, - TargetType.METHOD, - TargetType.PARAMETER, - TargetType.TYPE_USE - }; - private static final TargetType[] PARAMETER_TARGETS = { - TargetType.PARAMETER, - TargetType.TYPE_USE - }; - private static final TargetType[] LOCAL_VARIABLE_TARGETS = { - TargetType.LOCAL_VARIABLE, - TargetType.TYPE_USE - }; - private static final TargetType[] MODULE_TARGETS = {TargetType.MODULE}; + private static final TargetType[] PACKAGE_TARGETS = {TargetType.PACKAGE}; + private static final TargetType[] TYPE_USE_TARGETS = {TargetType.TYPE_USE}; + private static final TargetType[] ANNOTATION_TARGETS = { + TargetType.ANNOTATION_TYPE, + TargetType.TYPE, + TargetType.TYPE_USE + }; + private static final TargetType[] TYPE_TARGETS = { + TargetType.TYPE, + TargetType.TYPE_USE + }; + private static final TargetType[] TYPE_PARAMETER_TARGETS = { + TargetType.TYPE_PARAMETER, + TargetType.TYPE_USE + }; + private static final TargetType[] CONSTRUCTOR_TARGETS = { + TargetType.CONSTRUCTOR, + TargetType.TYPE_USE + }; + private static final TargetType[] METHOD_TARGETS = { + TargetType.METHOD, + TargetType.TYPE_USE + }; + private static final TargetType[] FIELD_TARGETS = { + TargetType.FIELD, + TargetType.TYPE_USE + }; + private static final TargetType[] RECORD_COMPONENT_TARGETS = { + TargetType.RECORD_COMPONENT, + TargetType.FIELD, + TargetType.METHOD, + TargetType.PARAMETER, + TargetType.TYPE_USE + }; + private static final TargetType[] PARAMETER_TARGETS = { + TargetType.PARAMETER, + TargetType.TYPE_USE + }; + private static final TargetType[] LOCAL_VARIABLE_TARGETS = { + TargetType.LOCAL_VARIABLE, + TargetType.TYPE_USE + }; + private static final TargetType[] MODULE_TARGETS = {TargetType.MODULE}; - @Nonnull - public static TargetType[] getTargetsForLocation(@Nullable PsiAnnotationOwner owner) { - if (owner == null) { - return TargetType.EMPTY_ARRAY; - } - - if (owner instanceof PsiType || owner instanceof PsiTypeElement) { - return TYPE_USE_TARGETS; - } - - if (owner instanceof PsiTypeParameter) { - return TYPE_PARAMETER_TARGETS; - } - - if (owner instanceof PsiModifierList) { - PsiElement element = ((PsiModifierList) owner).getParent(); - if (element instanceof PsiPackageStatement) { - return PACKAGE_TARGETS; - } - if (element instanceof PsiClass) { - if (((PsiClass) element).getModifierList() != owner) { - return TargetType.EMPTY_ARRAY; + @Nonnull + public static TargetType[] getTargetsForLocation(@Nullable PsiAnnotationOwner owner) { + if (owner == null) { + return TargetType.EMPTY_ARRAY; } - if (((PsiClass) element).isAnnotationType()) { - return ANNOTATION_TARGETS; - } else { - return TYPE_TARGETS; - } - } - if (element instanceof PsiRecordComponent) { - return RECORD_COMPONENT_TARGETS; - } - if (element instanceof PsiMethod) { - if (((PsiMethod) element).isConstructor()) { - return CONSTRUCTOR_TARGETS; - } else { - return METHOD_TARGETS; - } - } - if (element instanceof PsiField) { - return FIELD_TARGETS; - } - if (element instanceof PsiParameter) { - // PARAMETER applies only to formal parameters (methods & lambdas) and catch parameters - // see https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.6.4.1 - PsiElement scope = element.getParent(); - if (scope instanceof PsiForeachStatement || element instanceof PsiPatternVariable) { - return LOCAL_VARIABLE_TARGETS; - } - if (scope instanceof PsiParameterList && scope.getParent() instanceof PsiLambdaExpression && - ((PsiParameter) element).getTypeElement() == null) { - return TargetType.EMPTY_ARRAY; + + if (owner instanceof PsiType || owner instanceof PsiTypeElement) { + return TYPE_USE_TARGETS; } - return PARAMETER_TARGETS; - } - if (element instanceof PsiLocalVariable) { - return LOCAL_VARIABLE_TARGETS; - } - if (element instanceof PsiReceiverParameter) { - return TYPE_USE_TARGETS; - } - if (element instanceof PsiJavaModule) { - return MODULE_TARGETS; - } - } + if (owner instanceof PsiTypeParameter) { + return TYPE_PARAMETER_TARGETS; + } - return TargetType.EMPTY_ARRAY; - } + if (owner instanceof PsiModifierList) { + PsiElement element = ((PsiModifierList)owner).getParent(); + if (element instanceof PsiPackageStatement) { + return PACKAGE_TARGETS; + } + if (element instanceof PsiClass) { + if (((PsiClass)element).getModifierList() != owner) { + return TargetType.EMPTY_ARRAY; + } + if (((PsiClass)element).isAnnotationType()) { + return ANNOTATION_TARGETS; + } + else { + return TYPE_TARGETS; + } + } + if (element instanceof PsiRecordComponent) { + return RECORD_COMPONENT_TARGETS; + } + if (element instanceof PsiMethod) { + if (((PsiMethod)element).isConstructor()) { + return CONSTRUCTOR_TARGETS; + } + else { + return METHOD_TARGETS; + } + } + if (element instanceof PsiField) { + return FIELD_TARGETS; + } + if (element instanceof PsiParameter) { + // PARAMETER applies only to formal parameters (methods & lambdas) and catch parameters + // see https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.6.4.1 + PsiElement scope = element.getParent(); + if (scope instanceof PsiForeachStatement || element instanceof PsiPatternVariable) { + return LOCAL_VARIABLE_TARGETS; + } + if (scope instanceof PsiParameterList && scope.getParent() instanceof PsiLambdaExpression && + ((PsiParameter)element).getTypeElement() == null) { + return TargetType.EMPTY_ARRAY; + } - @Nullable - public static Set extractRequiredAnnotationTargets(@Nullable PsiAnnotationMemberValue value) { - if (value instanceof PsiReference) { - TargetType targetType = translateTargetRef((PsiReference) value); - if (targetType != null) { - return Collections.singleton(targetType); - } - } else if (value instanceof PsiArrayInitializerMemberValue) { - Set targets = new HashSet<>(); - for (PsiAnnotationMemberValue initializer : ((PsiArrayInitializerMemberValue) value).getInitializers()) { - if (initializer instanceof PsiReference) { - TargetType targetType = translateTargetRef((PsiReference) initializer); - if (targetType != null) { - targets.add(targetType); - } + return PARAMETER_TARGETS; + } + if (element instanceof PsiLocalVariable) { + return LOCAL_VARIABLE_TARGETS; + } + if (element instanceof PsiReceiverParameter) { + return TYPE_USE_TARGETS; + } + if (element instanceof PsiJavaModule) { + return MODULE_TARGETS; + } } - } - return targets; + + return TargetType.EMPTY_ARRAY; } - return null; - } + @Nullable + public static Set extractRequiredAnnotationTargets(@Nullable PsiAnnotationMemberValue value) { + if (value instanceof PsiReference) { + TargetType targetType = translateTargetRef((PsiReference)value); + if (targetType != null) { + return Collections.singleton(targetType); + } + } + else if (value instanceof PsiArrayInitializerMemberValue) { + Set targets = new HashSet<>(); + for (PsiAnnotationMemberValue initializer : ((PsiArrayInitializerMemberValue)value).getInitializers()) { + if (initializer instanceof PsiReference) { + TargetType targetType = translateTargetRef((PsiReference)initializer); + if (targetType != null) { + targets.add(targetType); + } + } + } + return targets; + } - @Nullable - private static TargetType translateTargetRef(@Nonnull PsiReference reference) { - PsiElement field = reference.resolve(); - if (field instanceof PsiEnumConstant) { - String name = ((PsiEnumConstant) field).getName(); - try { - return TargetType.valueOf(name); - } catch (IllegalArgumentException e) { - LOG.warn("Unknown target: " + name); - } + return null; } - return null; - } - /** - * Returns {@code true} if the annotation resolves to a class having {@link TargetType#TYPE_USE} in it's targets. - */ - public static boolean isTypeAnnotation(@Nonnull PsiAnnotation element) { - return findAnnotationTarget(element, TargetType.TYPE_USE) == TargetType.TYPE_USE; - } - - /** - * From given targets, returns first where the annotation may be applied. Returns {@code null} when the annotation is not applicable - * at any of the targets, or {@linkplain TargetType#UNKNOWN} if the annotation does not resolve to a valid annotation type. - */ - @Nullable - public static TargetType findAnnotationTarget(@Nonnull PsiAnnotation annotation, @Nonnull TargetType... types) { - if (types.length != 0) { - PsiJavaCodeReferenceElement ref = annotation.getNameReferenceElement(); - if (ref != null) { - PsiElement annotationType = ref.resolve(); - if (annotationType instanceof PsiClass) { - return findAnnotationTarget((PsiClass) annotationType, types); + @Nullable + private static TargetType translateTargetRef(@Nonnull PsiReference reference) { + PsiElement field = reference.resolve(); + if (field instanceof PsiEnumConstant) { + String name = ((PsiEnumConstant)field).getName(); + try { + return TargetType.valueOf(name); + } + catch (IllegalArgumentException e) { + LOG.warn("Unknown target: " + name); + } } - } + return null; } - return TargetType.UNKNOWN; - } + /** + * Returns {@code true} if the annotation resolves to a class having {@link TargetType#TYPE_USE} in it's targets. + */ + public static boolean isTypeAnnotation(@Nonnull PsiAnnotation element) { + return findAnnotationTarget(element, TargetType.TYPE_USE) == TargetType.TYPE_USE; + } - /** - * From given targets, returns first where the annotation may be applied. Returns {@code null} when the annotation is not applicable - * at any of the targets, or {@linkplain TargetType#UNKNOWN} if the type is not a valid annotation (e.g. cannot be resolved). - */ - @Nullable - public static TargetType findAnnotationTarget(@Nonnull PsiClass annotationType, @Nonnull TargetType... types) { - if (types.length != 0) { - Set targets = getAnnotationTargets(annotationType); - if (targets != null) { - for (TargetType type : types) { - if (type != TargetType.UNKNOWN && targets.contains(type)) { - return type; - } + /** + * From given targets, returns first where the annotation may be applied. Returns {@code null} when the annotation is not applicable + * at any of the targets, or {@linkplain TargetType#UNKNOWN} if the annotation does not resolve to a valid annotation type. + */ + @Nullable + public static TargetType findAnnotationTarget(@Nonnull PsiAnnotation annotation, @Nonnull TargetType... types) { + if (types.length != 0) { + PsiJavaCodeReferenceElement ref = annotation.getNameReferenceElement(); + if (ref != null) { + PsiElement annotationType = ref.resolve(); + if (annotationType instanceof PsiClass) { + return findAnnotationTarget((PsiClass)annotationType, types); + } + } } - return null; - } + + return TargetType.UNKNOWN; } - return TargetType.UNKNOWN; - } + /** + * From given targets, returns first where the annotation may be applied. Returns {@code null} when the annotation is not applicable + * at any of the targets, or {@linkplain TargetType#UNKNOWN} if the type is not a valid annotation (e.g. cannot be resolved). + */ + @Nullable + public static TargetType findAnnotationTarget(@Nonnull PsiClass annotationType, @Nonnull TargetType... types) { + if (types.length != 0) { + Set targets = getAnnotationTargets(annotationType); + if (targets != null) { + for (TargetType type : types) { + if (type != TargetType.UNKNOWN && targets.contains(type)) { + return type; + } + } + return null; + } + } - /** - * Returns a set of targets where the given annotation may be applied, or {@code null} when the type is not a valid annotation. - */ - @Nullable - public static Set getAnnotationTargets(@Nonnull PsiClass annotationType) { - if (!annotationType.isAnnotationType()) { - return null; - } - PsiModifierList modifierList = annotationType.getModifierList(); - if (modifierList == null) { - return null; + return TargetType.UNKNOWN; } - PsiAnnotation target = modifierList.findAnnotation(JavaClassNames.JAVA_LANG_ANNOTATION_TARGET); - if (target == null) { - return DEFAULT_TARGETS; // if omitted it is applicable to all but Java 8 TYPE_USE/TYPE_PARAMETERS targets - } - - return extractRequiredAnnotationTargets(target.findAttributeValue(null)); - } - /** - * @param modifierListOwner modifier list owner - * @param annotation the qualified name of the annotation to add - * @return a target annotation owner to add the annotation (either modifier list or type element depending on the annotation target) - * Returns null if {@code modifierListOwner.getModifierList()} is null. - */ - @Nullable - public static PsiAnnotationOwner getTarget(@Nonnull PsiModifierListOwner modifierListOwner, @Nonnull String annotation) { - PsiModifierList list = modifierListOwner.getModifierList(); - if (list == null) return null; - PsiClass annotationClass = JavaPsiFacade.getInstance(modifierListOwner.getProject()) - .findClass(annotation, modifierListOwner.getResolveScope()); - return getTarget(modifierListOwner, annotationClass != null && findAnnotationTarget(annotationClass, TargetType.TYPE_USE) != null); - } + /** + * Returns a set of targets where the given annotation may be applied, or {@code null} when the type is not a valid annotation. + */ + @Nullable + public static Set getAnnotationTargets(@Nonnull PsiClass annotationType) { + if (!annotationType.isAnnotationType()) { + return null; + } + PsiModifierList modifierList = annotationType.getModifierList(); + if (modifierList == null) { + return null; + } + PsiAnnotation target = modifierList.findAnnotation(JavaClassNames.JAVA_LANG_ANNOTATION_TARGET); + if (target == null) { + return DEFAULT_TARGETS; // if omitted it is applicable to all but Java 8 TYPE_USE/TYPE_PARAMETERS targets + } - /** - * @param modifierListOwner modifier list owner - * @param existsTypeUseTarget true, if annotation contains a type use target - * @return a target annotation owner to add the annotation (either modifier list or type element depending on the annotation target) - * Returns null if {@code modifierListOwner.getModifierList()} is null. - *

The method should be called under read action - * and the caller should be prepared for {@link IndexNotReadyException}. - */ - @Contract(pure = true) - public static @Nullable PsiAnnotationOwner getTarget(@Nonnull PsiModifierListOwner modifierListOwner, boolean existsTypeUseTarget) { - PsiModifierList list = modifierListOwner.getModifierList(); - if (list == null) return null; - if (existsTypeUseTarget && !(modifierListOwner instanceof PsiCompiledElement)) { - PsiElement parent = list.getParent(); - PsiTypeElement type = null; - if (parent instanceof PsiMethod) { - type = ((PsiMethod)parent).getReturnTypeElement(); - } - else if (parent instanceof PsiVariable) { - type = ((PsiVariable)parent).getTypeElement(); - } - if (type != null && type.acceptsAnnotations()) return type; + return extractRequiredAnnotationTargets(target.findAttributeValue(null)); } - return list; - } - /** - * Remove type_use annotations when at the usage place "normal" target works as well - * - * @param modifierList the place where type appears - */ - @RequiredReadAction - public static PsiType keepStrictlyTypeUseAnnotations(@Nullable PsiModifierList modifierList, @Nonnull PsiType type) { - if (modifierList == null) return type; - List annotations = new ArrayList<>(); - PsiAnnotation[] originalAnnotations = type.getAnnotations(); - for (PsiAnnotation annotation : originalAnnotations) { - if (isStrictlyTypeUseAnnotation(modifierList, annotation)) { - annotations.add(annotation); - } + /** + * @param modifierListOwner modifier list owner + * @param annotation the qualified name of the annotation to add + * @return a target annotation owner to add the annotation (either modifier list or type element depending on the annotation target) + * Returns null if {@code modifierListOwner.getModifierList()} is null. + */ + @Nullable + public static PsiAnnotationOwner getTarget(@Nonnull PsiModifierListOwner modifierListOwner, @Nonnull String annotation) { + PsiModifierList list = modifierListOwner.getModifierList(); + if (list == null) { + return null; + } + PsiClass annotationClass = JavaPsiFacade.getInstance(modifierListOwner.getProject()) + .findClass(annotation, modifierListOwner.getResolveScope()); + return getTarget(modifierListOwner, annotationClass != null && findAnnotationTarget(annotationClass, TargetType.TYPE_USE) != null); } - if (originalAnnotations.length == annotations.size()) { - return type; + /** + * @param modifierListOwner modifier list owner + * @param existsTypeUseTarget true, if annotation contains a type use target + * @return a target annotation owner to add the annotation (either modifier list or type element depending on the annotation target) + * Returns null if {@code modifierListOwner.getModifierList()} is null. + *

The method should be called under read action + * and the caller should be prepared for {@link IndexNotReadyException}. + */ + @Contract(pure = true) + public static @Nullable PsiAnnotationOwner getTarget(@Nonnull PsiModifierListOwner modifierListOwner, boolean existsTypeUseTarget) { + PsiModifierList list = modifierListOwner.getModifierList(); + if (list == null) { + return null; + } + if (existsTypeUseTarget && !(modifierListOwner instanceof PsiCompiledElement)) { + PsiElement parent = list.getParent(); + PsiTypeElement type = null; + if (parent instanceof PsiMethod) { + type = ((PsiMethod)parent).getReturnTypeElement(); + } + else if (parent instanceof PsiVariable) { + type = ((PsiVariable)parent).getTypeElement(); + } + if (type != null && type.acceptsAnnotations()) { + return type; + } + } + return list; } - return annotations.isEmpty() - ? type.annotate(TypeAnnotationProvider.EMPTY) - : type.annotate(TypeAnnotationProvider.Static.create(annotations.toArray(PsiAnnotation.EMPTY_ARRAY))); - } + /** + * Remove type_use annotations when at the usage place "normal" target works as well + * + * @param modifierList the place where type appears + */ + @RequiredReadAction + public static PsiType keepStrictlyTypeUseAnnotations(@Nullable PsiModifierList modifierList, @Nonnull PsiType type) { + if (modifierList == null) { + return type; + } + List annotations = new ArrayList<>(); + PsiAnnotation[] originalAnnotations = type.getAnnotations(); + for (PsiAnnotation annotation : originalAnnotations) { + if (isStrictlyTypeUseAnnotation(modifierList, annotation)) { + annotations.add(annotation); + } + } + + if (originalAnnotations.length == annotations.size()) { + return type; + } - /** - * Prefers "normal" target when type use annotation appears at the place where it's also applicable. - * Treat nullability annotations as TYPE_USE: we need to copy these annotations otherwise, and then the place of the annotation in modifier list may differ - * - * @param modifierList the place where annotation appears - * @return true iff annotation is type_use and - * appears at the "type_use place" (e.g. {@code List<@Nullable String>}) or - * none of it normal target types are acceptable (e.g. {@code void f(@Nullable String p)} if {@code @Nullable annotation is not applicable to parameters}) - */ - @RequiredReadAction - public static boolean isStrictlyTypeUseAnnotation(PsiModifierList modifierList, PsiAnnotation annotation) { - PsiElement parent = annotation.getParent(); - if (parent instanceof PsiJavaCodeReferenceElement || parent instanceof PsiTypeElement) { - return true; + return annotations.isEmpty() + ? type.annotate(TypeAnnotationProvider.EMPTY) + : type.annotate(TypeAnnotationProvider.Static.create(annotations.toArray(PsiAnnotation.EMPTY_ARRAY))); } - PsiClass annotationClass = annotation.resolveAnnotationType(); - if (annotationClass != null) { - Set targets = getAnnotationTargets(annotationClass); - if (targets != null && targets.contains(PsiAnnotation.TargetType.TYPE_USE) && - (targets.size() == 1 || - NullableNotNullManager.isNullabilityAnnotation(annotation) || - !ContainerUtil.exists(getTargetsForLocation(modifierList), - target -> target != PsiAnnotation.TargetType.TYPE_USE && targets.contains(target)))) { - return true; - } + + /** + * Prefers "normal" target when type use annotation appears at the place where it's also applicable. + * Treat nullability annotations as TYPE_USE: we need to copy these annotations otherwise, and then the place of the annotation in modifier list may differ + * + * @param modifierList the place where annotation appears + * @return true iff annotation is type_use and + * appears at the "type_use place" (e.g. {@code List<@Nullable String>}) or + * none of it normal target types are acceptable (e.g. {@code void f(@Nullable String p)} if {@code @Nullable annotation is not applicable to parameters}) + */ + @RequiredReadAction + public static boolean isStrictlyTypeUseAnnotation(PsiModifierList modifierList, PsiAnnotation annotation) { + PsiElement parent = annotation.getParent(); + if (parent instanceof PsiJavaCodeReferenceElement || parent instanceof PsiTypeElement) { + return true; + } + PsiClass annotationClass = annotation.resolveAnnotationType(); + if (annotationClass != null) { + Set targets = getAnnotationTargets(annotationClass); + if (targets != null && targets.contains(PsiAnnotation.TargetType.TYPE_USE) + && (targets.size() == 1 + || NullableNotNullManager.isNullabilityAnnotation(annotation) + || !ContainerUtil.exists( + getTargetsForLocation(modifierList), + target -> target != PsiAnnotation.TargetType.TYPE_USE && targets.contains(target) + ))) { + return true; + } + } + return false; } - return false; - } } \ No newline at end of file diff --git a/java-language-api/src/main/java/com/intellij/java/language/codeInsight/ImportFilter.java b/java-language-api/src/main/java/com/intellij/java/language/codeInsight/ImportFilter.java index 97907c7dfe..553f77c89f 100644 --- a/java-language-api/src/main/java/com/intellij/java/language/codeInsight/ImportFilter.java +++ b/java-language-api/src/main/java/com/intellij/java/language/codeInsight/ImportFilter.java @@ -12,17 +12,19 @@ */ @ExtensionAPI(ComponentScope.APPLICATION) public abstract class ImportFilter { - public static final ExtensionPointName EP_NAME = ExtensionPointName.create(ImportFilter.class); + public static final ExtensionPointName EP_NAME = ExtensionPointName.create(ImportFilter.class); - public abstract boolean shouldUseFullyQualifiedName(@Nonnull PsiFile targetFile, - @Nonnull String classQualifiedName); + public abstract boolean shouldUseFullyQualifiedName( + @Nonnull PsiFile targetFile, + @Nonnull String classQualifiedName + ); - public static boolean shouldImport(@Nonnull PsiFile targetFile, @Nonnull String classQualifiedName) { - for (ImportFilter filter : EP_NAME.getExtensionList()) { - if (filter.shouldUseFullyQualifiedName(targetFile, classQualifiedName)) { - return false; - } + public static boolean shouldImport(@Nonnull PsiFile targetFile, @Nonnull String classQualifiedName) { + for (ImportFilter filter : EP_NAME.getExtensionList()) { + if (filter.shouldUseFullyQualifiedName(targetFile, classQualifiedName)) { + return false; + } + } + return true; } - return true; - } } diff --git a/java-language-api/src/main/java/com/intellij/java/language/codeInsight/InferredAnnotationProvider.java b/java-language-api/src/main/java/com/intellij/java/language/codeInsight/InferredAnnotationProvider.java index a4c8437ed4..741176bc24 100644 --- a/java-language-api/src/main/java/com/intellij/java/language/codeInsight/InferredAnnotationProvider.java +++ b/java-language-api/src/main/java/com/intellij/java/language/codeInsight/InferredAnnotationProvider.java @@ -9,6 +9,7 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.List; /** @@ -17,22 +18,22 @@ */ @ExtensionAPI(ComponentScope.PROJECT) public interface InferredAnnotationProvider { - ExtensionPointName EP_NAME = ExtensionPointName.create(InferredAnnotationProvider.class); + ExtensionPointName EP_NAME = ExtensionPointName.create(InferredAnnotationProvider.class); - /** - * @return if exists, an inferred annotation by given qualified name on a given PSI element. Several invocations may return several - * different instances of {@link PsiAnnotation}, which are not guaranteed to be equal. - */ - @Nullable - PsiAnnotation findInferredAnnotation(@Nonnull PsiModifierListOwner listOwner, @Nonnull String annotationFQN); + /** + * @return if exists, an inferred annotation by given qualified name on a given PSI element. Several invocations may return several + * different instances of {@link PsiAnnotation}, which are not guaranteed to be equal. + */ + @Nullable + PsiAnnotation findInferredAnnotation(@Nonnull PsiModifierListOwner listOwner, @Nonnull String annotationFQN); - /** - * When annotation name is known, prefer {@link #findInferredAnnotation(PsiModifierListOwner, String)} as - * potentially faster. - * - * @return all inferred annotations for the given element. - */ - @Nonnull - List findInferredAnnotations(@Nonnull PsiModifierListOwner listOwner); + /** + * When annotation name is known, prefer {@link #findInferredAnnotation(PsiModifierListOwner, String)} as + * potentially faster. + * + * @return all inferred annotations for the given element. + */ + @Nonnull + List findInferredAnnotations(@Nonnull PsiModifierListOwner listOwner); } diff --git a/java-language-api/src/main/java/com/intellij/java/language/codeInsight/runner/JavaMainMethodProvider.java b/java-language-api/src/main/java/com/intellij/java/language/codeInsight/runner/JavaMainMethodProvider.java index 93993df8a7..e9a5c62c03 100644 --- a/java-language-api/src/main/java/com/intellij/java/language/codeInsight/runner/JavaMainMethodProvider.java +++ b/java-language-api/src/main/java/com/intellij/java/language/codeInsight/runner/JavaMainMethodProvider.java @@ -26,11 +26,11 @@ */ @ExtensionAPI(ComponentScope.APPLICATION) public interface JavaMainMethodProvider { - ExtensionPointName EP_NAME = ExtensionPointName.create(JavaMainMethodProvider.class); + ExtensionPointName EP_NAME = ExtensionPointName.create(JavaMainMethodProvider.class); - boolean isApplicable(final PsiClass clazz); + boolean isApplicable(final PsiClass clazz); - boolean hasMainMethod(final PsiClass clazz); + boolean hasMainMethod(final PsiClass clazz); - PsiMethod findMainInClass(final PsiClass clazz); + PsiMethod findMainInClass(final PsiClass clazz); } diff --git a/java-language-api/src/main/java/com/intellij/java/language/jvm/facade/JvmElementProvider.java b/java-language-api/src/main/java/com/intellij/java/language/jvm/facade/JvmElementProvider.java index 6fe83adcbc..44a4956b68 100644 --- a/java-language-api/src/main/java/com/intellij/java/language/jvm/facade/JvmElementProvider.java +++ b/java-language-api/src/main/java/com/intellij/java/language/jvm/facade/JvmElementProvider.java @@ -26,8 +26,8 @@ @ExtensionAPI(ComponentScope.PROJECT) public interface JvmElementProvider { - ExtensionPointName EP_NAME = ExtensionPointName.create(JvmElementProvider.class); + ExtensionPointName EP_NAME = ExtensionPointName.create(JvmElementProvider.class); - @Nonnull - List getClasses(@Nonnull String qualifiedName, @Nonnull GlobalSearchScope scope); + @Nonnull + List getClasses(@Nonnull String qualifiedName, @Nonnull GlobalSearchScope scope); } diff --git a/java-language-api/src/main/java/com/intellij/java/language/psi/ClassTypePointerFactory.java b/java-language-api/src/main/java/com/intellij/java/language/psi/ClassTypePointerFactory.java index eb8a8a25b3..826a3190ce 100644 --- a/java-language-api/src/main/java/com/intellij/java/language/psi/ClassTypePointerFactory.java +++ b/java-language-api/src/main/java/com/intellij/java/language/psi/ClassTypePointerFactory.java @@ -28,8 +28,8 @@ */ @ExtensionAPI(ComponentScope.APPLICATION) public interface ClassTypePointerFactory { - ExtensionPointName EP_NAME = ExtensionPointName.create(ClassTypePointerFactory.class); + ExtensionPointName EP_NAME = ExtensionPointName.create(ClassTypePointerFactory.class); - @Nullable - SmartTypePointer createClassTypePointer(@Nonnull PsiClassType classType, @Nonnull Project project); + @Nullable + SmartTypePointer createClassTypePointer(@Nonnull PsiClassType classType, @Nonnull Project project); } diff --git a/java-language-api/src/main/java/com/intellij/java/language/psi/augment/PsiAugmentProvider.java b/java-language-api/src/main/java/com/intellij/java/language/psi/augment/PsiAugmentProvider.java index 58f60877a5..a87b054f27 100644 --- a/java-language-api/src/main/java/com/intellij/java/language/psi/augment/PsiAugmentProvider.java +++ b/java-language-api/src/main/java/com/intellij/java/language/psi/augment/PsiAugmentProvider.java @@ -42,254 +42,264 @@ */ @ExtensionAPI(ComponentScope.APPLICATION) public abstract class PsiAugmentProvider { - private static final Logger LOG = Logger.getInstance(PsiAugmentProvider.class); - public static final ExtensionPointName EP_NAME = ExtensionPointName.create(PsiAugmentProvider.class); - - @SuppressWarnings("rawtypes") - private /* non-static */ final Key>> myCacheKey = Key.create(getClass().getName()); - - // - - /** - * An extension that enables one to add children to some PSI elements, e.g. methods to Java classes. - * The class code remains the same, but its method accessors also include the results returned from {@link PsiAugmentProvider}s. - * An augmenter can be called several times with the same parameters in the same state of the code model, - * and the PSI returned from these invocations must be equal and implement {@link #equals}/{@link #hashCode()} accordingly. - * - * @param nameHint the expected name of the requested augmented members, or null if all members of the specified class are to be returned. - * Implementations can ignore this parameter or use it for optimizations. - */ - @SuppressWarnings({ - "unchecked", - "rawtypes" - }) - @Nonnull - protected List getAugments(@Nonnull PsiElement element, - @Nonnull Class type, - @Nullable String nameHint) { - if (nameHint == null) { - return getAugments(element, type); + private static final Logger LOG = Logger.getInstance(PsiAugmentProvider.class); + public static final ExtensionPointName EP_NAME = ExtensionPointName.create(PsiAugmentProvider.class); + + @SuppressWarnings("rawtypes") + private /* non-static */ final Key>> myCacheKey = Key.create(getClass().getName()); + + // + + /** + * An extension that enables one to add children to some PSI elements, e.g. methods to Java classes. + * The class code remains the same, but its method accessors also include the results returned from {@link PsiAugmentProvider}s. + * An augmenter can be called several times with the same parameters in the same state of the code model, + * and the PSI returned from these invocations must be equal and implement {@link #equals}/{@link #hashCode()} accordingly. + * + * @param nameHint the expected name of the requested augmented members, or null if all members of the specified class are to be returned. + * Implementations can ignore this parameter or use it for optimizations. + */ + @SuppressWarnings({ + "unchecked", + "rawtypes" + }) + @Nonnull + protected List getAugments( + @Nonnull PsiElement element, + @Nonnull Class type, + @Nullable String nameHint + ) { + if (nameHint == null) { + return getAugments(element, type); + } + + // cache to emulate previous behavior where augmenters were called just once, not for each name hint separately + Map cache = LanguageCachedValueUtil.getCachedValue(element, myCacheKey, () -> { + Map map = ConcurrentFactoryMap.createMap(c -> getAugments(element, c)); + return CachedValueProvider.Result.create(map, PsiModificationTracker.MODIFICATION_COUNT); + }); + return (List)cache.get(type); } - // cache to emulate previous behavior where augmenters were called just once, not for each name hint separately - Map cache = LanguageCachedValueUtil.getCachedValue(element, myCacheKey, () -> { - Map map = ConcurrentFactoryMap.createMap(c -> getAugments(element, c)); - return CachedValueProvider.Result.create(map, PsiModificationTracker.MODIFICATION_COUNT); - }); - return (List)cache.get(type); - } - - /** - * An extension which enables one to inject extension methods with name {@code nameHint} in class {@code aClass} in context `{@code context}` - * - * @param aClass where extension methods would be injected - * @param nameHint name of the method which is requested. - * Implementations are supposed to use this parameter as no additional name check would be performed - * @param context context where extension methods should be applicable - */ - protected List getExtensionMethods(@Nonnull PsiClass aClass, @Nonnull String nameHint, @Nonnull PsiElement context) { - return Collections.emptyList(); - } - - /** - * @deprecated invoke and override {@link #getAugments(PsiElement, Class, String)}. - */ - @SuppressWarnings("unused") - @Deprecated - @Nonnull - protected List getAugments(@Nonnull PsiElement element, @Nonnull Class type) { - return Collections.emptyList(); - } - - /** - * Extends {@link PsiTypeElement#getType()} so that a type could be retrieved from external place - * (e.g. inferred from a variable initializer). - */ - @Nullable - protected PsiType inferType(@Nonnull PsiTypeElement typeElement) { - return null; - } - - /** - * @return whether this extension might infer the type for the given PSI, - * preferably checked in a lightweight way without actually inferring the type. - */ - protected boolean canInferType(@Nonnull PsiTypeElement typeElement) { - return inferType(typeElement) != null; - } - - /** - * Intercepts {@link PsiModifierList#hasModifierProperty(String)}, so that plugins can add imaginary modifiers or hide existing ones. - */ - @Nonnull - protected Set transformModifiers(@Nonnull PsiModifierList modifierList, @Nonnull Set modifiers) { - return modifiers; - } - - /** - * @param field field to check - * @return true if this field initializer can be changed due to extra-linguistic extensions - * (e.g., it's annotated via some annotation and annotation processor will transform the field to be non-constant) - */ - protected boolean fieldInitializerMightBeChanged(@Nonnull PsiField field) { - return false; - } - - // - - // - - /** - * @deprecated use {@link #collectAugments(PsiElement, Class, String)} - */ - @Nonnull - @Deprecated - public static List collectAugments(@Nonnull PsiElement element, @Nonnull Class type) { - return collectAugments(element, type, null); - } - - @Nonnull - public static List collectAugments(@Nonnull PsiElement element, @Nonnull Class type, - @Nullable String nameHint) { - List result = new SmartList<>(); - - forEach(element.getProject(), provider -> { - List augments = provider.getAugments(element, type, nameHint); - for (Psi augment : augments) { - if (nameHint == null || !(augment instanceof PsiNamedElement) || nameHint.equals(((PsiNamedElement)augment).getName())) { - try { - PsiUtilCore.ensureValid(augment); - result.add(augment); - } - catch (ProcessCanceledException e) { - throw e; - } - catch (Throwable e) { - PluginExceptionUtil.logPluginError(LOG, e.getMessage(), e, provider.getClass()); - } - } - } - return true; - }); - - return result; - } - - @Nonnull - public static List collectExtensionMethods(PsiClass aClass, @Nonnull String nameHint, PsiElement context) { - List extensionMethods = new SmartList<>(); - forEach(aClass.getProject(), provider -> { - List methods = provider.getExtensionMethods(aClass, nameHint, context); - for (PsiExtensionMethod method : methods) { - try { - PsiUtilCore.ensureValid(method); - extensionMethods.add(method); - } - catch (ProcessCanceledException e) { - throw e; - } - catch (Throwable e) { - PluginExceptionUtil.logPluginError(LOG, e.getMessage(), e, provider.getClass()); - } - } - return true; - }); - return extensionMethods; - } - - @Nullable - public static PsiType getInferredType(@Nonnull PsiTypeElement typeElement) { - Ref result = Ref.create(); - - forEach(typeElement.getProject(), provider -> { - PsiType type = provider.inferType(typeElement); - if (type != null) { - try { - PsiUtil.ensureValidType(type); - } - catch (ProcessCanceledException e) { - throw e; - } - catch (Throwable e) { - PluginExceptionUtil.logPluginError(LOG, e.getMessage(), e, provider.getClass()); - } - result.set(type); + /** + * An extension which enables one to inject extension methods with name {@code nameHint} in class {@code aClass} in context `{@code context}` + * + * @param aClass where extension methods would be injected + * @param nameHint name of the method which is requested. + * Implementations are supposed to use this parameter as no additional name check would be performed + * @param context context where extension methods should be applicable + */ + protected List getExtensionMethods( + @Nonnull PsiClass aClass, + @Nonnull String nameHint, + @Nonnull PsiElement context + ) { + return Collections.emptyList(); + } + + /** + * @deprecated invoke and override {@link #getAugments(PsiElement, Class, String)}. + */ + @SuppressWarnings("unused") + @Deprecated + @Nonnull + protected List getAugments(@Nonnull PsiElement element, @Nonnull Class type) { + return Collections.emptyList(); + } + + /** + * Extends {@link PsiTypeElement#getType()} so that a type could be retrieved from external place + * (e.g. inferred from a variable initializer). + */ + @Nullable + protected PsiType inferType(@Nonnull PsiTypeElement typeElement) { + return null; + } + + /** + * @return whether this extension might infer the type for the given PSI, + * preferably checked in a lightweight way without actually inferring the type. + */ + protected boolean canInferType(@Nonnull PsiTypeElement typeElement) { + return inferType(typeElement) != null; + } + + /** + * Intercepts {@link PsiModifierList#hasModifierProperty(String)}, so that plugins can add imaginary modifiers or hide existing ones. + */ + @Nonnull + protected Set transformModifiers(@Nonnull PsiModifierList modifierList, @Nonnull Set modifiers) { + return modifiers; + } + + /** + * @param field field to check + * @return true if this field initializer can be changed due to extra-linguistic extensions + * (e.g., it's annotated via some annotation and annotation processor will transform the field to be non-constant) + */ + protected boolean fieldInitializerMightBeChanged(@Nonnull PsiField field) { return false; - } - else { - return true; - } - }); - - return result.get(); - } - - public static boolean isInferredType(@Nonnull PsiTypeElement typeElement) { - AtomicBoolean result = new AtomicBoolean(); - - forEach(typeElement.getProject(), provider -> { - boolean canInfer = provider.canInferType(typeElement); - if (canInfer) { - result.set(true); - } - return !canInfer; - }); - - return result.get(); - } - - /** - * @param field field to check - * @return true if we can trust the field initializer; - * false if any of providers reported that the initializer might be changed - */ - public static boolean canTrustFieldInitializer(@Nonnull PsiField field) { - AtomicBoolean result = new AtomicBoolean(true); - - forEach(field.getProject(), provider -> { - boolean mightBeReplaced = provider.fieldInitializerMightBeChanged(field); - if (mightBeReplaced) { - result.set(false); - } - return !mightBeReplaced; - }); - - return result.get(); - } - - @Nonnull - public static Set transformModifierProperties(@Nonnull PsiModifierList modifierList, - @Nonnull Project project, - @Nonnull Set modifiers) { - Ref> result = Ref.create(modifiers); - - forEach(project, provider -> { - result.set(provider.transformModifiers(modifierList, Collections.unmodifiableSet(result.get()))); - return true; - }); - - return result.get(); - } - - private static void forEach(Project project, Processor processor) { - boolean dumb = DumbService.isDumb(project); - for (PsiAugmentProvider provider : EP_NAME.getExtensionList()) { - if (!dumb || DumbService.isDumbAware(provider)) { - try { - boolean goOn = processor.process(provider); - if (!goOn) { - break; - } - } - catch (ProcessCanceledException e) { - throw e; - } - catch (Exception e) { - Logger.getInstance(PsiAugmentProvider.class).error("provider: " + provider, e); + } + + // + + // + + /** + * @deprecated use {@link #collectAugments(PsiElement, Class, String)} + */ + @Nonnull + @Deprecated + public static List collectAugments(@Nonnull PsiElement element, @Nonnull Class type) { + return collectAugments(element, type, null); + } + + @Nonnull + public static List collectAugments( + @Nonnull PsiElement element, @Nonnull Class type, + @Nullable String nameHint + ) { + List result = new SmartList<>(); + + forEach(element.getProject(), provider -> { + List augments = provider.getAugments(element, type, nameHint); + for (Psi augment : augments) { + if (nameHint == null || !(augment instanceof PsiNamedElement) || nameHint.equals(((PsiNamedElement)augment).getName())) { + try { + PsiUtilCore.ensureValid(augment); + result.add(augment); + } + catch (ProcessCanceledException e) { + throw e; + } + catch (Throwable e) { + PluginExceptionUtil.logPluginError(LOG, e.getMessage(), e, provider.getClass()); + } + } + } + return true; + }); + + return result; + } + + @Nonnull + public static List collectExtensionMethods(PsiClass aClass, @Nonnull String nameHint, PsiElement context) { + List extensionMethods = new SmartList<>(); + forEach(aClass.getProject(), provider -> { + List methods = provider.getExtensionMethods(aClass, nameHint, context); + for (PsiExtensionMethod method : methods) { + try { + PsiUtilCore.ensureValid(method); + extensionMethods.add(method); + } + catch (ProcessCanceledException e) { + throw e; + } + catch (Throwable e) { + PluginExceptionUtil.logPluginError(LOG, e.getMessage(), e, provider.getClass()); + } + } + return true; + }); + return extensionMethods; + } + + @Nullable + public static PsiType getInferredType(@Nonnull PsiTypeElement typeElement) { + Ref result = Ref.create(); + + forEach(typeElement.getProject(), provider -> { + PsiType type = provider.inferType(typeElement); + if (type != null) { + try { + PsiUtil.ensureValidType(type); + } + catch (ProcessCanceledException e) { + throw e; + } + catch (Throwable e) { + PluginExceptionUtil.logPluginError(LOG, e.getMessage(), e, provider.getClass()); + } + result.set(type); + return false; + } + else { + return true; + } + }); + + return result.get(); + } + + public static boolean isInferredType(@Nonnull PsiTypeElement typeElement) { + AtomicBoolean result = new AtomicBoolean(); + + forEach(typeElement.getProject(), provider -> { + boolean canInfer = provider.canInferType(typeElement); + if (canInfer) { + result.set(true); + } + return !canInfer; + }); + + return result.get(); + } + + /** + * @param field field to check + * @return true if we can trust the field initializer; + * false if any of providers reported that the initializer might be changed + */ + public static boolean canTrustFieldInitializer(@Nonnull PsiField field) { + AtomicBoolean result = new AtomicBoolean(true); + + forEach(field.getProject(), provider -> { + boolean mightBeReplaced = provider.fieldInitializerMightBeChanged(field); + if (mightBeReplaced) { + result.set(false); + } + return !mightBeReplaced; + }); + + return result.get(); + } + + @Nonnull + public static Set transformModifierProperties( + @Nonnull PsiModifierList modifierList, + @Nonnull Project project, + @Nonnull Set modifiers + ) { + Ref> result = Ref.create(modifiers); + + forEach(project, provider -> { + result.set(provider.transformModifiers(modifierList, Collections.unmodifiableSet(result.get()))); + return true; + }); + + return result.get(); + } + + private static void forEach(Project project, Processor processor) { + boolean dumb = DumbService.isDumb(project); + for (PsiAugmentProvider provider : EP_NAME.getExtensionList()) { + if (!dumb || DumbService.isDumbAware(provider)) { + try { + boolean goOn = processor.process(provider); + if (!goOn) { + break; + } + } + catch (ProcessCanceledException e) { + throw e; + } + catch (Exception e) { + Logger.getInstance(PsiAugmentProvider.class).error("provider: " + provider, e); + } + } } - } } - } - // + // } \ No newline at end of file diff --git a/java-language-api/src/main/java/com/intellij/java/language/psi/augment/TypeAnnotationModifier.java b/java-language-api/src/main/java/com/intellij/java/language/psi/augment/TypeAnnotationModifier.java index 89da17d849..4cc160b991 100644 --- a/java-language-api/src/main/java/com/intellij/java/language/psi/augment/TypeAnnotationModifier.java +++ b/java-language-api/src/main/java/com/intellij/java/language/psi/augment/TypeAnnotationModifier.java @@ -27,20 +27,19 @@ @ExtensionAPI(ComponentScope.APPLICATION) public abstract class TypeAnnotationModifier { - public static final ExtensionPointName EP_NAME = ExtensionPointName.create(TypeAnnotationModifier.class); - - /** - * Type annotations are ignored during inference process. When they are present on types which are bounds of the inference variables, - * then the corresponding instantiations of inference variables would contain that type annotations. - * If different bounds contain contradicting type annotations or type annotations on types repeat target type annotations, - * it could be useful to ignore such annotations in the resulted instantiation. - * - * @param inferenceVariableType target type - * @param boundType bound which annotations should be changed according to present annotations - * and annotations on target type - * @return provider based on modified annotations or null if no applicable annotations found - */ - @Nullable - public abstract TypeAnnotationProvider modifyAnnotations(@Nonnull PsiType inferenceVariableType, @Nonnull PsiClassType boundType); + public static final ExtensionPointName EP_NAME = ExtensionPointName.create(TypeAnnotationModifier.class); + /** + * Type annotations are ignored during inference process. When they are present on types which are bounds of the inference variables, + * then the corresponding instantiations of inference variables would contain that type annotations. + * If different bounds contain contradicting type annotations or type annotations on types repeat target type annotations, + * it could be useful to ignore such annotations in the resulted instantiation. + * + * @param inferenceVariableType target type + * @param boundType bound which annotations should be changed according to present annotations + * and annotations on target type + * @return provider based on modified annotations or null if no applicable annotations found + */ + @Nullable + public abstract TypeAnnotationProvider modifyAnnotations(@Nonnull PsiType inferenceVariableType, @Nonnull PsiClassType boundType); } diff --git a/java-language-api/src/main/java/com/intellij/java/language/psi/targets/AliasingPsiTargetMapper.java b/java-language-api/src/main/java/com/intellij/java/language/psi/targets/AliasingPsiTargetMapper.java index ab47801b66..878d62e6a7 100644 --- a/java-language-api/src/main/java/com/intellij/java/language/psi/targets/AliasingPsiTargetMapper.java +++ b/java-language-api/src/main/java/com/intellij/java/language/psi/targets/AliasingPsiTargetMapper.java @@ -25,8 +25,8 @@ @ExtensionAPI(ComponentScope.APPLICATION) public interface AliasingPsiTargetMapper { - ExtensionPointName EP_NAME = ExtensionPointName.create(AliasingPsiTargetMapper.class); + ExtensionPointName EP_NAME = ExtensionPointName.create(AliasingPsiTargetMapper.class); - @Nonnull - Set getTargets(@Nonnull PomTarget target); + @Nonnull + Set getTargets(@Nonnull PomTarget target); } diff --git a/java-language-impl/src/main/java/com/intellij/java/language/impl/codeInsight/ExceptionUtil.java b/java-language-impl/src/main/java/com/intellij/java/language/impl/codeInsight/ExceptionUtil.java index 54b53f4523..73e08b4ecf 100644 --- a/java-language-impl/src/main/java/com/intellij/java/language/impl/codeInsight/ExceptionUtil.java +++ b/java-language-impl/src/main/java/com/intellij/java/language/impl/codeInsight/ExceptionUtil.java @@ -52,854 +52,953 @@ * @author mike */ public class ExceptionUtil { - @NonNls - private static final String CLONE_METHOD_NAME = "clone"; - - private ExceptionUtil() { - } - - @Nonnull - public static List getThrownExceptions(@Nonnull PsiElement[] elements) { - List array = ContainerUtil.newArrayList(); - for (PsiElement element : elements) { - List exceptions = getThrownExceptions(element); - addExceptions(array, exceptions); - } - - return array; - } - - @Nonnull - public static List getThrownCheckedExceptions(@Nonnull PsiElement... elements) { - List exceptions = getThrownExceptions(elements); - if (exceptions.isEmpty()) { - return exceptions; - } - exceptions = filterOutUncheckedExceptions(exceptions); - return exceptions; - } - - @Nonnull - private static List filterOutUncheckedExceptions(@Nonnull List exceptions) { - List array = ContainerUtil.newArrayList(); - for (PsiClassType exception : exceptions) { - if (!isUncheckedException(exception)) { - array.add(exception); - } - } - return array; - } - - @Nonnull - public static List getThrownExceptions(@Nonnull PsiElement element) { - List result = new ArrayList<>(); - element.accept(new JavaRecursiveElementWalkingVisitor() { - @Override - public void visitAnonymousClass(PsiAnonymousClass aClass) { - final PsiExpressionList argumentList = aClass.getArgumentList(); - if (argumentList != null) { - super.visitExpressionList(argumentList); - } - super.visitAnonymousClass(aClass); - } - - @Override - public void visitClass(PsiClass aClass) { - // do not go inside class declaration - } - - @Override - public void visitMethodCallExpression(PsiMethodCallExpression expression) { - PsiReferenceExpression methodRef = expression.getMethodExpression(); - JavaResolveResult resolveResult = methodRef.advancedResolve(false); - PsiMethod method = (PsiMethod) resolveResult.getElement(); - if (method != null) { - addExceptions(result, getExceptionsByMethod(method, resolveResult.getSubstitutor(), element)); + @NonNls + private static final String CLONE_METHOD_NAME = "clone"; + + private ExceptionUtil() { + } + + @Nonnull + public static List getThrownExceptions(@Nonnull PsiElement[] elements) { + List array = ContainerUtil.newArrayList(); + for (PsiElement element : elements) { + List exceptions = getThrownExceptions(element); + addExceptions(array, exceptions); } - super.visitMethodCallExpression(expression); - } - @Override - public void visitNewExpression(PsiNewExpression expression) { - JavaResolveResult resolveResult = expression.resolveMethodGenerics(); - PsiMethod method = (PsiMethod) resolveResult.getElement(); - if (method != null) { - addExceptions(result, getExceptionsByMethod(method, resolveResult.getSubstitutor(), element)); + return array; + } + + @Nonnull + public static List getThrownCheckedExceptions(@Nonnull PsiElement... elements) { + List exceptions = getThrownExceptions(elements); + if (exceptions.isEmpty()) { + return exceptions; + } + exceptions = filterOutUncheckedExceptions(exceptions); + return exceptions; + } + + @Nonnull + private static List filterOutUncheckedExceptions(@Nonnull List exceptions) { + List array = ContainerUtil.newArrayList(); + for (PsiClassType exception : exceptions) { + if (!isUncheckedException(exception)) { + array.add(exception); + } + } + return array; + } + + @Nonnull + public static List getThrownExceptions(@Nonnull PsiElement element) { + List result = new ArrayList<>(); + element.accept(new JavaRecursiveElementWalkingVisitor() { + @Override + public void visitAnonymousClass(PsiAnonymousClass aClass) { + final PsiExpressionList argumentList = aClass.getArgumentList(); + if (argumentList != null) { + super.visitExpressionList(argumentList); + } + super.visitAnonymousClass(aClass); + } + + @Override + public void visitClass(PsiClass aClass) { + // do not go inside class declaration + } + + @Override + public void visitMethodCallExpression(PsiMethodCallExpression expression) { + PsiReferenceExpression methodRef = expression.getMethodExpression(); + JavaResolveResult resolveResult = methodRef.advancedResolve(false); + PsiMethod method = (PsiMethod)resolveResult.getElement(); + if (method != null) { + addExceptions(result, getExceptionsByMethod(method, resolveResult.getSubstitutor(), element)); + } + super.visitMethodCallExpression(expression); + } + + @Override + public void visitNewExpression(PsiNewExpression expression) { + JavaResolveResult resolveResult = expression.resolveMethodGenerics(); + PsiMethod method = (PsiMethod)resolveResult.getElement(); + if (method != null) { + addExceptions(result, getExceptionsByMethod(method, resolveResult.getSubstitutor(), element)); + } + super.visitNewExpression(expression); + } + + @Override + public void visitThrowStatement(PsiThrowStatement statement) { + final PsiExpression expr = statement.getException(); + if (expr != null) { + addExceptions( + result, + ContainerUtil.mapNotNull(getPreciseThrowTypes(expr), (it) -> it instanceof PsiClassType ? (PsiClassType)it : null) + ); + } + super.visitThrowStatement(statement); + } + + @Override + public void visitLambdaExpression(PsiLambdaExpression expression) { + // do not go inside lambda + } + + @Override + public void visitResourceList(PsiResourceList resourceList) { + for (PsiResourceListElement listElement : resourceList) { + addExceptions(result, getCloserExceptions(listElement)); + } + super.visitResourceList(resourceList); + } + + @Override + public void visitTryStatement(PsiTryStatement statement) { + addExceptions(result, getTryExceptions(statement)); + // do not call super: try exception goes into try body recursively + } + }); + return result; + } + + @Nonnull + private static List getTryExceptions(@Nonnull PsiTryStatement tryStatement) { + List array = ContainerUtil.newArrayList(); + + PsiResourceList resourceList = tryStatement.getResourceList(); + if (resourceList != null) { + for (PsiResourceListElement resource : resourceList) { + addExceptions(array, getUnhandledCloserExceptions(resource, resourceList)); + } } - super.visitNewExpression(expression); - } - @Override - public void visitThrowStatement(PsiThrowStatement statement) { - final PsiExpression expr = statement.getException(); - if (expr != null) { - addExceptions(result, ContainerUtil.mapNotNull(getPreciseThrowTypes(expr), (it) -> it instanceof PsiClassType ? (PsiClassType) it : null)); + PsiCodeBlock tryBlock = tryStatement.getTryBlock(); + if (tryBlock != null) { + addExceptions(array, getThrownExceptions(tryBlock)); } - super.visitThrowStatement(statement); - } - @Override - public void visitLambdaExpression(PsiLambdaExpression expression) { - // do not go inside lambda - } + for (PsiParameter parameter : tryStatement.getCatchBlockParameters()) { + PsiType exception = parameter.getType(); + for (int j = array.size() - 1; j >= 0; j--) { + PsiClassType exception1 = array.get(j); + if (exception.isAssignableFrom(exception1)) { + array.remove(exception1); + } + } + } + + for (PsiCodeBlock catchBlock : tryStatement.getCatchBlocks()) { + addExceptions(array, getThrownExceptions(catchBlock)); + } - @Override - public void visitResourceList(PsiResourceList resourceList) { - for (PsiResourceListElement listElement : resourceList) { - addExceptions(result, getCloserExceptions(listElement)); + PsiCodeBlock finallyBlock = tryStatement.getFinallyBlock(); + if (finallyBlock != null) { + // if finally block completes normally, exception not caught + // if finally block completes abruptly, exception gets lost + try { + ControlFlow flow = ControlFlowFactory.getInstance(finallyBlock.getProject()) + .getControlFlow(finallyBlock, LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance(), false); + int completionReasons = ControlFlowUtil.getCompletionReasons(flow, 0, flow.getSize()); + List thrownExceptions = getThrownExceptions(finallyBlock); + if (!BitUtil.isSet(completionReasons, ControlFlowUtil.NORMAL_COMPLETION_REASON)) { + array = ContainerUtil.newArrayList(thrownExceptions); + } + else { + addExceptions(array, thrownExceptions); + } + } + catch (AnalysisCanceledException e) { + // incomplete code + } } - super.visitResourceList(resourceList); - } - @Override - public void visitTryStatement(PsiTryStatement statement) { - addExceptions(result, getTryExceptions(statement)); - // do not call super: try exception goes into try body recursively - } - }); - return result; - } + return array; + } + + @Nonnull + private static List getExceptionsByMethodAndChildren( + @Nonnull PsiElement element, + @Nonnull JavaResolveResult resolveResult + ) { + List result = ContainerUtil.newArrayList(); - @Nonnull - private static List getTryExceptions(@Nonnull PsiTryStatement tryStatement) { - List array = ContainerUtil.newArrayList(); + PsiMethod method = (PsiMethod)resolveResult.getElement(); + if (method != null) { + addExceptions(result, getExceptionsByMethod(method, resolveResult.getSubstitutor(), element)); + } - PsiResourceList resourceList = tryStatement.getResourceList(); - if (resourceList != null) { - for (PsiResourceListElement resource : resourceList) { - addExceptions(array, getUnhandledCloserExceptions(resource, resourceList)); - } + addExceptions(result, getThrownExceptions(element.getChildren())); + + return result; } - PsiCodeBlock tryBlock = tryStatement.getTryBlock(); - if (tryBlock != null) { - addExceptions(array, getThrownExceptions(tryBlock)); + @Nonnull + private static List getExceptionsByMethod( + @Nonnull PsiMethod method, + @Nonnull PsiSubstitutor substitutor, + @Nonnull PsiElement place + ) { + PsiClassType[] referenceTypes = method.getThrowsList().getReferencedTypes(); + if (referenceTypes.length == 0) { + return Collections.emptyList(); + } + + GlobalSearchScope scope = place.getResolveScope(); + + List result = ContainerUtil.newArrayList(); + for (PsiType type : referenceTypes) { + type = PsiClassImplUtil.correctType(substitutor.substitute(type), scope); + if (type instanceof PsiClassType) { + result.add((PsiClassType)type); + } + } + + return result; } - for (PsiParameter parameter : tryStatement.getCatchBlockParameters()) { - PsiType exception = parameter.getType(); - for (int j = array.size() - 1; j >= 0; j--) { - PsiClassType exception1 = array.get(j); - if (exception.isAssignableFrom(exception1)) { - array.remove(exception1); + private static void addExceptions(@Nonnull List array, @Nonnull Collection exceptions) { + for (PsiClassType exception : exceptions) { + addException(array, exception); } - } } - for (PsiCodeBlock catchBlock : tryStatement.getCatchBlocks()) { - addExceptions(array, getThrownExceptions(catchBlock)); - } - - PsiCodeBlock finallyBlock = tryStatement.getFinallyBlock(); - if (finallyBlock != null) { - // if finally block completes normally, exception not caught - // if finally block completes abruptly, exception gets lost - try { - ControlFlow flow = ControlFlowFactory.getInstance(finallyBlock.getProject()).getControlFlow(finallyBlock, LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance(), false); - int completionReasons = ControlFlowUtil.getCompletionReasons(flow, 0, flow.getSize()); - List thrownExceptions = getThrownExceptions(finallyBlock); - if (!BitUtil.isSet(completionReasons, ControlFlowUtil.NORMAL_COMPLETION_REASON)) { - array = ContainerUtil.newArrayList(thrownExceptions); - } else { - addExceptions(array, thrownExceptions); - } - } catch (AnalysisCanceledException e) { - // incomplete code - } - } - - return array; - } - - @Nonnull - private static List getExceptionsByMethodAndChildren(@Nonnull PsiElement element, @Nonnull JavaResolveResult resolveResult) { - List result = ContainerUtil.newArrayList(); - - PsiMethod method = (PsiMethod) resolveResult.getElement(); - if (method != null) { - addExceptions(result, getExceptionsByMethod(method, resolveResult.getSubstitutor(), element)); - } - - addExceptions(result, getThrownExceptions(element.getChildren())); - - return result; - } - - @Nonnull - private static List getExceptionsByMethod(@Nonnull PsiMethod method, @Nonnull PsiSubstitutor substitutor, @Nonnull PsiElement place) { - PsiClassType[] referenceTypes = method.getThrowsList().getReferencedTypes(); - if (referenceTypes.length == 0) { - return Collections.emptyList(); - } - - GlobalSearchScope scope = place.getResolveScope(); - - List result = ContainerUtil.newArrayList(); - for (PsiType type : referenceTypes) { - type = PsiClassImplUtil.correctType(substitutor.substitute(type), scope); - if (type instanceof PsiClassType) { - result.add((PsiClassType) type); - } - } - - return result; - } - - private static void addExceptions(@Nonnull List array, @Nonnull Collection exceptions) { - for (PsiClassType exception : exceptions) { - addException(array, exception); - } - } - - private static void addException(@Nonnull List array, @Nullable PsiClassType exception) { - if (exception == null) { - return; - } - for (int i = array.size() - 1; i >= 0; i--) { - PsiClassType exception1 = array.get(i); - if (exception1.isAssignableFrom(exception)) { - return; - } - if (exception.isAssignableFrom(exception1)) { - array.remove(i); - } - } - array.add(exception); - } - - @Nonnull - public static Collection collectUnhandledExceptions(@Nonnull PsiElement element, @Nullable PsiElement topElement) { - return collectUnhandledExceptions(element, topElement, true); - } - - @Nonnull - public static Collection collectUnhandledExceptions(@Nonnull PsiElement element, @Nullable PsiElement topElement, boolean includeSelfCalls) { - final Set set = collectUnhandledExceptions(element, topElement, null, includeSelfCalls); - return set == null ? Collections.emptyList() : set; - } - - @Nullable - private static Set collectUnhandledExceptions(@Nonnull PsiElement element, @Nullable PsiElement topElement, @Nullable Set foundExceptions, boolean includeSelfCalls) { - Collection unhandledExceptions = null; - if (element instanceof PsiCallExpression) { - PsiCallExpression expression = (PsiCallExpression) element; - unhandledExceptions = getUnhandledExceptions(expression, topElement, includeSelfCalls); - } else if (element instanceof PsiMethodReferenceExpression) { - PsiExpression qualifierExpression = ((PsiMethodReferenceExpression) element).getQualifierExpression(); - return qualifierExpression != null ? collectUnhandledExceptions(qualifierExpression, topElement, null, false) : null; - } else if (element instanceof PsiLambdaExpression) { - return null; - } else if (element instanceof PsiThrowStatement) { - PsiThrowStatement statement = (PsiThrowStatement) element; - unhandledExceptions = getUnhandledExceptions(statement, topElement); - } else if (element instanceof PsiCodeBlock && element.getParent() instanceof PsiMethod && ((PsiMethod) element.getParent()).isConstructor() && !firstStatementIsConstructorCall((PsiCodeBlock) - element)) { - // there is implicit parent constructor call - final PsiMethod constructor = (PsiMethod) element.getParent(); - final PsiClass aClass = constructor.getContainingClass(); - final PsiClass superClass = aClass == null ? null : aClass.getSuperClass(); - final PsiMethod[] superConstructors = superClass == null ? PsiMethod.EMPTY_ARRAY : superClass.getConstructors(); - Set unhandled = new HashSet<>(); - for (PsiMethod superConstructor : superConstructors) { - if (!superConstructor.hasModifierProperty(PsiModifier.PRIVATE) && superConstructor.getParameterList().getParametersCount() == 0) { - final PsiClassType[] exceptionTypes = superConstructor.getThrowsList().getReferencedTypes(); - for (PsiClassType exceptionType : exceptionTypes) { - if (!isUncheckedException(exceptionType) && !isHandled(element, exceptionType, topElement)) { - unhandled.add(exceptionType); - } - } - break; - } - } - - // plus all exceptions thrown in instance class initializers - if (aClass != null) { - final PsiClassInitializer[] initializers = aClass.getInitializers(); - final Set thrownByInitializer = new HashSet<>(); - for (PsiClassInitializer initializer : initializers) { - if (initializer.hasModifierProperty(PsiModifier.STATIC)) { - continue; - } - thrownByInitializer.clear(); - collectUnhandledExceptions(initializer.getBody(), initializer, thrownByInitializer, includeSelfCalls); - for (PsiClassType thrown : thrownByInitializer) { - if (!isHandled(constructor.getBody(), thrown, topElement)) { - unhandled.add(thrown); - } - } - } - } - unhandledExceptions = unhandled; - } else if (element instanceof PsiResourceListElement) { - final List unhandled = getUnhandledCloserExceptions((PsiResourceListElement) element, topElement); - if (!unhandled.isEmpty()) { - unhandledExceptions = ContainerUtil.newArrayList(unhandled); - } - } - - if (unhandledExceptions != null) { - if (foundExceptions == null) { - foundExceptions = new HashSet<>(); - } - foundExceptions.addAll(unhandledExceptions); - } - - for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) { - Set foundInChild = collectUnhandledExceptions(child, topElement, foundExceptions, includeSelfCalls); - if (foundExceptions == null) { - foundExceptions = foundInChild; - } else if (foundInChild != null) { - foundExceptions.addAll(foundInChild); - } - } - - return foundExceptions; - } - - @Nonnull - private static Collection getUnhandledExceptions(@Nonnull PsiMethodReferenceExpression methodReferenceExpression, PsiElement topElement) { - final JavaResolveResult resolveResult = methodReferenceExpression.advancedResolve(false); - final PsiElement resolve = resolveResult.getElement(); - if (resolve instanceof PsiMethod) { - final PsiElement referenceNameElement = methodReferenceExpression.getReferenceNameElement(); - return getUnhandledExceptions((PsiMethod) resolve, referenceNameElement, topElement, resolveResult.getSubstitutor()); - } - return Collections.emptyList(); - } - - private static boolean firstStatementIsConstructorCall(@Nonnull PsiCodeBlock constructorBody) { - final PsiStatement[] statements = constructorBody.getStatements(); - if (statements.length == 0) { - return false; - } - if (!(statements[0] instanceof PsiExpressionStatement)) { - return false; - } - - final PsiExpression expression = ((PsiExpressionStatement) statements[0]).getExpression(); - if (!(expression instanceof PsiMethodCallExpression)) { - return false; - } - final PsiMethod method = (PsiMethod) ((PsiMethodCallExpression) expression).getMethodExpression().resolve(); - return method != null && method.isConstructor(); - } - - @Nonnull - public static List getUnhandledExceptions(final @Nonnull PsiElement[] elements) { - final List array = ContainerUtil.newArrayList(); - - final PsiElementVisitor visitor = new JavaRecursiveElementWalkingVisitor() { - @Override - public void visitEnumConstant(PsiEnumConstant enumConstant) { - final PsiMethod method = enumConstant.resolveMethod(); - if (method != null) { - addExceptions(array, getUnhandledExceptions(method, enumConstant, null, PsiSubstitutor.EMPTY)); - } - visitElement(enumConstant); - } - - @Override - public void visitCallExpression(@Nonnull PsiCallExpression expression) { - addExceptions(array, getUnhandledExceptions(expression, null)); - visitElement(expression); - } - - @Override - public void visitThrowStatement(@Nonnull PsiThrowStatement statement) { - addExceptions(array, getUnhandledExceptions(statement, null)); - visitElement(statement); - } - - @Override - public void visitLambdaExpression(PsiLambdaExpression expression) { - if (ArrayUtil.find(elements, expression) >= 0) { - visitElement(expression); - } - } - - @Override - public void visitMethodReferenceExpression(@Nonnull PsiMethodReferenceExpression expression) { - if (ArrayUtil.find(elements, expression) >= 0) { - addExceptions(array, getUnhandledExceptions(expression, null)); - visitElement(expression); - } - } - - @Override - public void visitResourceVariable(@Nonnull PsiResourceVariable resource) { - addExceptions(array, getUnhandledCloserExceptions(resource, null)); - visitElement(resource); - } - - @Override - public void visitResourceExpression(@Nonnull PsiResourceExpression resource) { - addExceptions(array, getUnhandledCloserExceptions(resource, null)); - visitElement(resource); - } - - @Override - public void visitClass(PsiClass aClass) { - } - }; - - for (PsiElement element : elements) { - element.accept(visitor); - } - - return array; - } - - @Nonnull - public static List getUnhandledExceptions(@Nonnull PsiElement element) { - return getUnhandledExceptions(new PsiElement[]{element}); - } - - @Nonnull - public static List getUnhandledExceptions(@Nonnull final PsiCallExpression methodCall, @Nullable final PsiElement topElement) { - return getUnhandledExceptions(methodCall, topElement, true); - } - - @Nonnull - public static List getUnhandledExceptions(@Nonnull final PsiCallExpression methodCall, @Nullable final PsiElement topElement, final boolean includeSelfCalls) { - //exceptions only influence the invocation type after overload resolution is complete - if (MethodCandidateInfo.isOverloadCheck()) { - return Collections.emptyList(); - } - final MethodCandidateInfo.CurrentCandidateProperties properties = MethodCandidateInfo.getCurrentMethod(methodCall.getArgumentList()); - final JavaResolveResult result = properties != null ? properties.getInfo() : PsiDiamondType.getDiamondsAwareResolveResult(methodCall); - final PsiElement element = result.getElement(); - final PsiMethod method = element instanceof PsiMethod ? (PsiMethod) element : null; - if (method == null) { - return Collections.emptyList(); - } - final PsiMethod containingMethod = PsiTreeUtil.getParentOfType(methodCall, PsiMethod.class); - if (!includeSelfCalls && method == containingMethod) { - return Collections.emptyList(); - } - - if (properties != null) { - PsiUtilCore.ensureValid(method); - } - - final PsiClassType[] thrownExceptions = method.getThrowsList().getReferencedTypes(); - if (thrownExceptions.length == 0) { - return Collections.emptyList(); - } - - final PsiSubstitutor substitutor = result.getSubstitutor(); - if (!isArrayClone(method, methodCall) && methodCall instanceof PsiMethodCallExpression) { - final PsiFile containingFile = (containingMethod == null ? methodCall : containingMethod).getContainingFile(); - final MethodResolverProcessor processor = new MethodResolverProcessor((PsiMethodCallExpression) methodCall, containingFile); - try { - PsiScopesUtil.setupAndRunProcessor(processor, methodCall, false); - final List> candidates = ContainerUtil.mapNotNull(processor.getResults(), info -> - { - PsiElement element1 = info.getElement(); - if (info instanceof MethodCandidateInfo && element1 != method && //don't check self - MethodSignatureUtil.areSignaturesEqual(method, (PsiMethod) element1) && !MethodSignatureUtil.isSuperMethod((PsiMethod) element1, method) && !(((MethodCandidateInfo) info) - .isToInferApplicability() && !((MethodCandidateInfo) info).isApplicable())) { - return Pair.create((PsiMethod) element1, ((MethodCandidateInfo) info).getSubstitutor(false)); - } - return null; - }); - if (!candidates.isEmpty()) { - GlobalSearchScope scope = methodCall.getResolveScope(); - final List ex = collectSubstituted(substitutor, thrownExceptions, scope); - for (Pair pair : candidates) { - final PsiClassType[] exceptions = pair.first.getThrowsList().getReferencedTypes(); - if (exceptions.length == 0) { - return getUnhandledExceptions(methodCall, topElement, PsiSubstitutor.EMPTY, PsiClassType.EMPTY_ARRAY); - } - retainExceptions(ex, collectSubstituted(pair.second, exceptions, scope)); - } - return getUnhandledExceptions(methodCall, topElement, PsiSubstitutor.EMPTY, ex.toArray(new PsiClassType[ex.size()])); - } - } catch (MethodProcessorSetupFailedException ignore) { + private static void addException(@Nonnull List array, @Nullable PsiClassType exception) { + if (exception == null) { + return; + } + for (int i = array.size() - 1; i >= 0; i--) { + PsiClassType exception1 = array.get(i); + if (exception1.isAssignableFrom(exception)) { + return; + } + if (exception.isAssignableFrom(exception1)) { + array.remove(i); + } + } + array.add(exception); + } + + @Nonnull + public static Collection collectUnhandledExceptions(@Nonnull PsiElement element, @Nullable PsiElement topElement) { + return collectUnhandledExceptions(element, topElement, true); + } + + @Nonnull + public static Collection collectUnhandledExceptions( + @Nonnull PsiElement element, + @Nullable PsiElement topElement, + boolean includeSelfCalls + ) { + final Set set = collectUnhandledExceptions(element, topElement, null, includeSelfCalls); + return set == null ? Collections.emptyList() : set; + } + + @Nullable + private static Set collectUnhandledExceptions( + @Nonnull PsiElement element, + @Nullable PsiElement topElement, + @Nullable Set foundExceptions, + boolean includeSelfCalls + ) { + Collection unhandledExceptions = null; + if (element instanceof PsiCallExpression) { + PsiCallExpression expression = (PsiCallExpression)element; + unhandledExceptions = getUnhandledExceptions(expression, topElement, includeSelfCalls); + } + else if (element instanceof PsiMethodReferenceExpression) { + PsiExpression qualifierExpression = ((PsiMethodReferenceExpression)element).getQualifierExpression(); + return qualifierExpression != null ? collectUnhandledExceptions(qualifierExpression, topElement, null, false) : null; + } + else if (element instanceof PsiLambdaExpression) { + return null; + } + else if (element instanceof PsiThrowStatement) { + PsiThrowStatement statement = (PsiThrowStatement)element; + unhandledExceptions = getUnhandledExceptions(statement, topElement); + } + else if (element instanceof PsiCodeBlock && element.getParent() instanceof PsiMethod && ((PsiMethod)element.getParent()).isConstructor() && !firstStatementIsConstructorCall( + (PsiCodeBlock) + element)) { + // there is implicit parent constructor call + final PsiMethod constructor = (PsiMethod)element.getParent(); + final PsiClass aClass = constructor.getContainingClass(); + final PsiClass superClass = aClass == null ? null : aClass.getSuperClass(); + final PsiMethod[] superConstructors = superClass == null ? PsiMethod.EMPTY_ARRAY : superClass.getConstructors(); + Set unhandled = new HashSet<>(); + for (PsiMethod superConstructor : superConstructors) { + if (!superConstructor.hasModifierProperty(PsiModifier.PRIVATE) && superConstructor.getParameterList() + .getParametersCount() == 0) { + final PsiClassType[] exceptionTypes = superConstructor.getThrowsList().getReferencedTypes(); + for (PsiClassType exceptionType : exceptionTypes) { + if (!isUncheckedException(exceptionType) && !isHandled(element, exceptionType, topElement)) { + unhandled.add(exceptionType); + } + } + break; + } + } + + // plus all exceptions thrown in instance class initializers + if (aClass != null) { + final PsiClassInitializer[] initializers = aClass.getInitializers(); + final Set thrownByInitializer = new HashSet<>(); + for (PsiClassInitializer initializer : initializers) { + if (initializer.hasModifierProperty(PsiModifier.STATIC)) { + continue; + } + thrownByInitializer.clear(); + collectUnhandledExceptions(initializer.getBody(), initializer, thrownByInitializer, includeSelfCalls); + for (PsiClassType thrown : thrownByInitializer) { + if (!isHandled(constructor.getBody(), thrown, topElement)) { + unhandled.add(thrown); + } + } + } + } + unhandledExceptions = unhandled; + } + else if (element instanceof PsiResourceListElement) { + final List unhandled = getUnhandledCloserExceptions((PsiResourceListElement)element, topElement); + if (!unhandled.isEmpty()) { + unhandledExceptions = ContainerUtil.newArrayList(unhandled); + } + } + + if (unhandledExceptions != null) { + if (foundExceptions == null) { + foundExceptions = new HashSet<>(); + } + foundExceptions.addAll(unhandledExceptions); + } + + for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) { + Set foundInChild = collectUnhandledExceptions(child, topElement, foundExceptions, includeSelfCalls); + if (foundExceptions == null) { + foundExceptions = foundInChild; + } + else if (foundInChild != null) { + foundExceptions.addAll(foundInChild); + } + } + + return foundExceptions; + } + + @Nonnull + private static Collection getUnhandledExceptions( + @Nonnull PsiMethodReferenceExpression methodReferenceExpression, + PsiElement topElement + ) { + final JavaResolveResult resolveResult = methodReferenceExpression.advancedResolve(false); + final PsiElement resolve = resolveResult.getElement(); + if (resolve instanceof PsiMethod) { + final PsiElement referenceNameElement = methodReferenceExpression.getReferenceNameElement(); + return getUnhandledExceptions((PsiMethod)resolve, referenceNameElement, topElement, resolveResult.getSubstitutor()); + } return Collections.emptyList(); - } - } - - return getUnhandledExceptions(method, methodCall, topElement, substitutor); - } - - public static void retainExceptions(List ex, List thrownEx) { - final List replacement = new ArrayList<>(); - for (Iterator iterator = ex.iterator(); iterator.hasNext(); ) { - PsiClassType classType = iterator.next(); - boolean found = false; - for (PsiClassType psiClassType : thrownEx) { - if (psiClassType.isAssignableFrom(classType)) { - found = true; - break; - } else if (classType.isAssignableFrom(psiClassType)) { - if (isUncheckedException(classType) == isUncheckedException(psiClassType)) { - replacement.add(psiClassType); - } - } - } - if (!found) { - iterator.remove(); - } - } - ex.removeAll(replacement); - ex.addAll(replacement); - } - - public static List collectSubstituted(PsiSubstitutor substitutor, PsiClassType[] thrownExceptions, GlobalSearchScope scope) { - final List ex = new ArrayList<>(); - for (PsiClassType thrownException : thrownExceptions) { - final PsiType psiType = PsiClassImplUtil.correctType(substitutor.substitute(thrownException), scope); - if (psiType instanceof PsiClassType) { - ex.add((PsiClassType) psiType); - } else if (psiType instanceof PsiCapturedWildcardType) { - final PsiCapturedWildcardType capturedWildcardType = (PsiCapturedWildcardType) psiType; - final PsiType upperBound = capturedWildcardType.getUpperBound(); - if (upperBound instanceof PsiClassType) { - ex.add((PsiClassType) upperBound); - } - } - } - return ex; - } - - @Nonnull - public static List getCloserExceptions(@Nonnull PsiResourceListElement resource) { - List ex = getExceptionsFromClose(resource); - return ex != null ? ex : Collections.emptyList(); - } - - @Nonnull - public static List getUnhandledCloserExceptions(@Nonnull PsiResourceListElement resource, @Nullable PsiElement topElement) { - final PsiType type = resource.getType(); - return getUnhandledCloserExceptions(resource, topElement, type); - } - - @Nonnull - public static List getUnhandledCloserExceptions(PsiElement place, @Nullable PsiElement topElement, PsiType type) { - List ex = type instanceof PsiClassType ? getExceptionsFromClose(type, place.getResolveScope()) : null; - return ex != null ? getUnhandledExceptions(place, topElement, PsiSubstitutor.EMPTY, ex.toArray(new PsiClassType[ex.size()])) : Collections.emptyList(); - } - - private static List getExceptionsFromClose(PsiResourceListElement resource) { - final PsiType type = resource.getType(); - return type instanceof PsiClassType ? getExceptionsFromClose(type, resource.getResolveScope()) : null; - } - - private static List getExceptionsFromClose(PsiType type, GlobalSearchScope scope) { - PsiClassType.ClassResolveResult resourceType = PsiUtil.resolveGenericsClassInType(type); - PsiClass resourceClass = resourceType.getElement(); - if (resourceClass == null) { - return null; - } - - PsiMethod[] methods = PsiUtil.getResourceCloserMethodsForType((PsiClassType) type); - if (methods != null) { - List ex = null; - for (PsiMethod method : methods) { - PsiClass closerClass = method.getContainingClass(); - if (closerClass != null) { - PsiSubstitutor substitutor = TypeConversionUtil.getClassSubstitutor(closerClass, resourceClass, resourceType.getSubstitutor()); - if (substitutor != null) { - final PsiClassType[] exceptionTypes = method.getThrowsList().getReferencedTypes(); - if (exceptionTypes.length == 0) { - return Collections.emptyList(); - } - - if (ex == null) { - ex = collectSubstituted(substitutor, exceptionTypes, scope); - } else { - retainExceptions(ex, collectSubstituted(substitutor, exceptionTypes, scope)); - } - } - } - } - return ex; - } - - return null; - } - - @Nonnull - public static List getUnhandledExceptions(@Nonnull PsiThrowStatement throwStatement, @Nullable PsiElement topElement) { - List unhandled = new SmartList<>(); - for (PsiType type : getPreciseThrowTypes(throwStatement.getException())) { - List types = type instanceof PsiDisjunctionType ? ((PsiDisjunctionType) type).getDisjunctions() : Collections.singletonList(type); - for (PsiType subType : types) { - if (subType instanceof PsiClassType) { - PsiClassType classType = (PsiClassType) subType; - if (!isUncheckedException(classType) && !isHandled(throwStatement, classType, topElement)) { - unhandled.add(classType); - } - } - } - } - return unhandled; - } - - @Nonnull - private static List getPreciseThrowTypes(@Nullable PsiExpression expression) { - expression = PsiUtil.skipParenthesizedExprDown(expression); - if (expression instanceof PsiReferenceExpression) { - final PsiElement target = ((PsiReferenceExpression) expression).resolve(); - if (target != null && PsiUtil.isCatchParameter(target)) { - return ((PsiCatchSection) target.getParent()).getPreciseCatchTypes(); - } } - if (expression != null) { - final PsiType type = expression.getType(); - if (type != null) { - return Collections.singletonList(type); - } + private static boolean firstStatementIsConstructorCall(@Nonnull PsiCodeBlock constructorBody) { + final PsiStatement[] statements = constructorBody.getStatements(); + if (statements.length == 0) { + return false; + } + if (!(statements[0] instanceof PsiExpressionStatement)) { + return false; + } + + final PsiExpression expression = ((PsiExpressionStatement)statements[0]).getExpression(); + if (!(expression instanceof PsiMethodCallExpression)) { + return false; + } + final PsiMethod method = (PsiMethod)((PsiMethodCallExpression)expression).getMethodExpression().resolve(); + return method != null && method.isConstructor(); + } + + @Nonnull + public static List getUnhandledExceptions(final @Nonnull PsiElement[] elements) { + final List array = ContainerUtil.newArrayList(); + + final PsiElementVisitor visitor = new JavaRecursiveElementWalkingVisitor() { + @Override + public void visitEnumConstant(PsiEnumConstant enumConstant) { + final PsiMethod method = enumConstant.resolveMethod(); + if (method != null) { + addExceptions(array, getUnhandledExceptions(method, enumConstant, null, PsiSubstitutor.EMPTY)); + } + visitElement(enumConstant); + } + + @Override + public void visitCallExpression(@Nonnull PsiCallExpression expression) { + addExceptions(array, getUnhandledExceptions(expression, null)); + visitElement(expression); + } + + @Override + public void visitThrowStatement(@Nonnull PsiThrowStatement statement) { + addExceptions(array, getUnhandledExceptions(statement, null)); + visitElement(statement); + } + + @Override + public void visitLambdaExpression(PsiLambdaExpression expression) { + if (ArrayUtil.find(elements, expression) >= 0) { + visitElement(expression); + } + } + + @Override + public void visitMethodReferenceExpression(@Nonnull PsiMethodReferenceExpression expression) { + if (ArrayUtil.find(elements, expression) >= 0) { + addExceptions(array, getUnhandledExceptions(expression, null)); + visitElement(expression); + } + } + + @Override + public void visitResourceVariable(@Nonnull PsiResourceVariable resource) { + addExceptions(array, getUnhandledCloserExceptions(resource, null)); + visitElement(resource); + } + + @Override + public void visitResourceExpression(@Nonnull PsiResourceExpression resource) { + addExceptions(array, getUnhandledCloserExceptions(resource, null)); + visitElement(resource); + } + + @Override + public void visitClass(PsiClass aClass) { + } + }; + + for (PsiElement element : elements) { + element.accept(visitor); + } + + return array; } - return Collections.emptyList(); - } + @Nonnull + public static List getUnhandledExceptions(@Nonnull PsiElement element) { + return getUnhandledExceptions(new PsiElement[]{element}); + } - @Nonnull - public static List getUnhandledExceptions(@Nonnull PsiMethod method, PsiElement element, PsiElement topElement, @Nonnull PsiSubstitutor substitutor) { - if (isArrayClone(method, element)) { - return Collections.emptyList(); + @Nonnull + public static List getUnhandledExceptions( + @Nonnull final PsiCallExpression methodCall, + @Nullable final PsiElement topElement + ) { + return getUnhandledExceptions(methodCall, topElement, true); } - final PsiClassType[] referencedTypes = method.getThrowsList().getReferencedTypes(); - return getUnhandledExceptions(element, topElement, substitutor, referencedTypes); - } - private static List getUnhandledExceptions(PsiElement element, PsiElement topElement, PsiSubstitutor substitutor, PsiClassType[] referencedTypes) { - if (referencedTypes.length > 0) { - List result = ContainerUtil.newArrayList(); + @Nonnull + public static List getUnhandledExceptions( + @Nonnull final PsiCallExpression methodCall, + @Nullable final PsiElement topElement, + final boolean includeSelfCalls + ) { + //exceptions only influence the invocation type after overload resolution is complete + if (MethodCandidateInfo.isOverloadCheck()) { + return Collections.emptyList(); + } + final MethodCandidateInfo.CurrentCandidateProperties properties = + MethodCandidateInfo.getCurrentMethod(methodCall.getArgumentList()); + final JavaResolveResult result = + properties != null ? properties.getInfo() : PsiDiamondType.getDiamondsAwareResolveResult(methodCall); + final PsiElement element = result.getElement(); + final PsiMethod method = element instanceof PsiMethod ? (PsiMethod)element : null; + if (method == null) { + return Collections.emptyList(); + } + final PsiMethod containingMethod = PsiTreeUtil.getParentOfType(methodCall, PsiMethod.class); + if (!includeSelfCalls && method == containingMethod) { + return Collections.emptyList(); + } - for (PsiClassType referencedType : referencedTypes) { - final PsiType type = PsiClassImplUtil.correctType(GenericsUtil.eliminateWildcards(substitutor.substitute(referencedType), false), element.getResolveScope()); - if (!(type instanceof PsiClassType)) { - continue; + if (properties != null) { + PsiUtilCore.ensureValid(method); } - PsiClassType classType = (PsiClassType) type; - PsiClass exceptionClass = ((PsiClassType) type).resolve(); - if (exceptionClass == null) { - continue; + + final PsiClassType[] thrownExceptions = method.getThrowsList().getReferencedTypes(); + if (thrownExceptions.length == 0) { + return Collections.emptyList(); } - if (isUncheckedException(classType)) { - continue; + final PsiSubstitutor substitutor = result.getSubstitutor(); + if (!isArrayClone(method, methodCall) && methodCall instanceof PsiMethodCallExpression) { + final PsiFile containingFile = (containingMethod == null ? methodCall : containingMethod).getContainingFile(); + final MethodResolverProcessor processor = new MethodResolverProcessor((PsiMethodCallExpression)methodCall, containingFile); + try { + PsiScopesUtil.setupAndRunProcessor(processor, methodCall, false); + final List> candidates = ContainerUtil.mapNotNull(processor.getResults(), info -> + { + PsiElement element1 = info.getElement(); + if (info instanceof MethodCandidateInfo && element1 != method && //don't check self + MethodSignatureUtil.areSignaturesEqual(method, (PsiMethod)element1) + && !MethodSignatureUtil.isSuperMethod((PsiMethod)element1, method) && !(((MethodCandidateInfo)info) + .isToInferApplicability() && !((MethodCandidateInfo)info).isApplicable())) { + return Pair.create((PsiMethod)element1, ((MethodCandidateInfo)info).getSubstitutor(false)); + } + return null; + }); + if (!candidates.isEmpty()) { + GlobalSearchScope scope = methodCall.getResolveScope(); + final List ex = collectSubstituted(substitutor, thrownExceptions, scope); + for (Pair pair : candidates) { + final PsiClassType[] exceptions = pair.first.getThrowsList().getReferencedTypes(); + if (exceptions.length == 0) { + return getUnhandledExceptions(methodCall, topElement, PsiSubstitutor.EMPTY, PsiClassType.EMPTY_ARRAY); + } + retainExceptions(ex, collectSubstituted(pair.second, exceptions, scope)); + } + return getUnhandledExceptions(methodCall, topElement, PsiSubstitutor.EMPTY, ex.toArray(new PsiClassType[ex.size()])); + } + } + catch (MethodProcessorSetupFailedException ignore) { + return Collections.emptyList(); + } } - if (isHandled(element, classType, topElement)) { - continue; + + return getUnhandledExceptions(method, methodCall, topElement, substitutor); + } + + public static void retainExceptions(List ex, List thrownEx) { + final List replacement = new ArrayList<>(); + for (Iterator iterator = ex.iterator(); iterator.hasNext(); ) { + PsiClassType classType = iterator.next(); + boolean found = false; + for (PsiClassType psiClassType : thrownEx) { + if (psiClassType.isAssignableFrom(classType)) { + found = true; + break; + } + else if (classType.isAssignableFrom(psiClassType)) { + if (isUncheckedException(classType) == isUncheckedException(psiClassType)) { + replacement.add(psiClassType); + } + } + } + if (!found) { + iterator.remove(); + } + } + ex.removeAll(replacement); + ex.addAll(replacement); + } + + public static List collectSubstituted( + PsiSubstitutor substitutor, + PsiClassType[] thrownExceptions, + GlobalSearchScope scope + ) { + final List ex = new ArrayList<>(); + for (PsiClassType thrownException : thrownExceptions) { + final PsiType psiType = PsiClassImplUtil.correctType(substitutor.substitute(thrownException), scope); + if (psiType instanceof PsiClassType) { + ex.add((PsiClassType)psiType); + } + else if (psiType instanceof PsiCapturedWildcardType) { + final PsiCapturedWildcardType capturedWildcardType = (PsiCapturedWildcardType)psiType; + final PsiType upperBound = capturedWildcardType.getUpperBound(); + if (upperBound instanceof PsiClassType) { + ex.add((PsiClassType)upperBound); + } + } } + return ex; + } - result.add((PsiClassType) type); - } + @Nonnull + public static List getCloserExceptions(@Nonnull PsiResourceListElement resource) { + List ex = getExceptionsFromClose(resource); + return ex != null ? ex : Collections.emptyList(); + } - return result; + @Nonnull + public static List getUnhandledCloserExceptions( + @Nonnull PsiResourceListElement resource, + @Nullable PsiElement topElement + ) { + final PsiType type = resource.getType(); + return getUnhandledCloserExceptions(resource, topElement, type); } - return Collections.emptyList(); - } - private static boolean isArrayClone(@Nonnull PsiMethod method, PsiElement element) { - if (!method.getName().equals(CLONE_METHOD_NAME)) { - return false; + @Nonnull + public static List getUnhandledCloserExceptions(PsiElement place, @Nullable PsiElement topElement, PsiType type) { + List ex = type instanceof PsiClassType ? getExceptionsFromClose(type, place.getResolveScope()) : null; + return ex != null ? getUnhandledExceptions( + place, + topElement, + PsiSubstitutor.EMPTY, + ex.toArray(new PsiClassType[ex.size()]) + ) : Collections.emptyList(); } - PsiClass containingClass = method.getContainingClass(); - if (containingClass == null || !JavaClassNames.JAVA_LANG_OBJECT.equals(containingClass.getQualifiedName())) { - return false; + + private static List getExceptionsFromClose(PsiResourceListElement resource) { + final PsiType type = resource.getType(); + return type instanceof PsiClassType ? getExceptionsFromClose(type, resource.getResolveScope()) : null; } - if (element instanceof PsiMethodReferenceExpression) { - final PsiMethodReferenceExpression methodCallExpression = (PsiMethodReferenceExpression) element; - final PsiExpression qualifierExpression = methodCallExpression.getQualifierExpression(); - return qualifierExpression != null && qualifierExpression.getType() instanceof PsiArrayType; + + private static List getExceptionsFromClose(PsiType type, GlobalSearchScope scope) { + PsiClassType.ClassResolveResult resourceType = PsiUtil.resolveGenericsClassInType(type); + PsiClass resourceClass = resourceType.getElement(); + if (resourceClass == null) { + return null; + } + + PsiMethod[] methods = PsiUtil.getResourceCloserMethodsForType((PsiClassType)type); + if (methods != null) { + List ex = null; + for (PsiMethod method : methods) { + PsiClass closerClass = method.getContainingClass(); + if (closerClass != null) { + PsiSubstitutor substitutor = + TypeConversionUtil.getClassSubstitutor(closerClass, resourceClass, resourceType.getSubstitutor()); + if (substitutor != null) { + final PsiClassType[] exceptionTypes = method.getThrowsList().getReferencedTypes(); + if (exceptionTypes.length == 0) { + return Collections.emptyList(); + } + + if (ex == null) { + ex = collectSubstituted(substitutor, exceptionTypes, scope); + } + else { + retainExceptions(ex, collectSubstituted(substitutor, exceptionTypes, scope)); + } + } + } + } + return ex; + } + + return null; + } + + @Nonnull + public static List getUnhandledExceptions(@Nonnull PsiThrowStatement throwStatement, @Nullable PsiElement topElement) { + List unhandled = new SmartList<>(); + for (PsiType type : getPreciseThrowTypes(throwStatement.getException())) { + List types = + type instanceof PsiDisjunctionType ? ((PsiDisjunctionType)type).getDisjunctions() : Collections.singletonList(type); + for (PsiType subType : types) { + if (subType instanceof PsiClassType) { + PsiClassType classType = (PsiClassType)subType; + if (!isUncheckedException(classType) && !isHandled(throwStatement, classType, topElement)) { + unhandled.add(classType); + } + } + } + } + return unhandled; } - if (!(element instanceof PsiMethodCallExpression)) { - return false; + + @Nonnull + private static List getPreciseThrowTypes(@Nullable PsiExpression expression) { + expression = PsiUtil.skipParenthesizedExprDown(expression); + if (expression instanceof PsiReferenceExpression) { + final PsiElement target = ((PsiReferenceExpression)expression).resolve(); + if (target != null && PsiUtil.isCatchParameter(target)) { + return ((PsiCatchSection)target.getParent()).getPreciseCatchTypes(); + } + } + + if (expression != null) { + final PsiType type = expression.getType(); + if (type != null) { + return Collections.singletonList(type); + } + } + + return Collections.emptyList(); } - PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression) element; - final PsiExpression qualifierExpression = methodCallExpression.getMethodExpression().getQualifierExpression(); - return qualifierExpression != null && qualifierExpression.getType() instanceof PsiArrayType; - } + @Nonnull + public static List getUnhandledExceptions( + @Nonnull PsiMethod method, + PsiElement element, + PsiElement topElement, + @Nonnull PsiSubstitutor substitutor + ) { + if (isArrayClone(method, element)) { + return Collections.emptyList(); + } + final PsiClassType[] referencedTypes = method.getThrowsList().getReferencedTypes(); + return getUnhandledExceptions(element, topElement, substitutor, referencedTypes); + } + + private static List getUnhandledExceptions( + PsiElement element, + PsiElement topElement, + PsiSubstitutor substitutor, + PsiClassType[] referencedTypes + ) { + if (referencedTypes.length > 0) { + List result = ContainerUtil.newArrayList(); + + for (PsiClassType referencedType : referencedTypes) { + final PsiType type = PsiClassImplUtil.correctType( + GenericsUtil.eliminateWildcards(substitutor.substitute(referencedType), false), + element.getResolveScope() + ); + if (!(type instanceof PsiClassType)) { + continue; + } + PsiClassType classType = (PsiClassType)type; + PsiClass exceptionClass = ((PsiClassType)type).resolve(); + if (exceptionClass == null) { + continue; + } + + if (isUncheckedException(classType)) { + continue; + } + if (isHandled(element, classType, topElement)) { + continue; + } + + result.add((PsiClassType)type); + } - public static boolean isUncheckedException(@Nonnull PsiClassType type) { - return InheritanceUtil.isInheritor(type, JavaClassNames.JAVA_LANG_RUNTIME_EXCEPTION) || InheritanceUtil.isInheritor(type, JavaClassNames.JAVA_LANG_ERROR); - } + return result; + } + return Collections.emptyList(); + } - public static boolean isUncheckedException(@Nonnull PsiClass psiClass) { - return InheritanceUtil.isInheritor(psiClass, JavaClassNames.JAVA_LANG_RUNTIME_EXCEPTION) || InheritanceUtil.isInheritor(psiClass, JavaClassNames.JAVA_LANG_ERROR); - } + private static boolean isArrayClone(@Nonnull PsiMethod method, PsiElement element) { + if (!method.getName().equals(CLONE_METHOD_NAME)) { + return false; + } + PsiClass containingClass = method.getContainingClass(); + if (containingClass == null || !JavaClassNames.JAVA_LANG_OBJECT.equals(containingClass.getQualifiedName())) { + return false; + } + if (element instanceof PsiMethodReferenceExpression) { + final PsiMethodReferenceExpression methodCallExpression = (PsiMethodReferenceExpression)element; + final PsiExpression qualifierExpression = methodCallExpression.getQualifierExpression(); + return qualifierExpression != null && qualifierExpression.getType() instanceof PsiArrayType; + } + if (!(element instanceof PsiMethodCallExpression)) { + return false; + } - public static boolean isUncheckedExceptionOrSuperclass(@Nonnull final PsiClassType type) { - return isGeneralExceptionType(type) || isUncheckedException(type); - } + PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)element; + final PsiExpression qualifierExpression = methodCallExpression.getMethodExpression().getQualifierExpression(); + return qualifierExpression != null && qualifierExpression.getType() instanceof PsiArrayType; + } - public static boolean isGeneralExceptionType(@Nonnull final PsiType type) { - final String canonicalText = type.getCanonicalText(); - return JavaClassNames.JAVA_LANG_THROWABLE.equals(canonicalText) || JavaClassNames.JAVA_LANG_EXCEPTION.equals(canonicalText); - } + public static boolean isUncheckedException(@Nonnull PsiClassType type) { + return InheritanceUtil.isInheritor(type, JavaClassNames.JAVA_LANG_RUNTIME_EXCEPTION) + || InheritanceUtil.isInheritor(type, JavaClassNames.JAVA_LANG_ERROR); + } - public static boolean isHandled(@Nonnull PsiClassType exceptionType, @Nonnull PsiElement throwPlace) { - return isHandled(throwPlace, exceptionType, throwPlace.getContainingFile()); - } + public static boolean isUncheckedException(@Nonnull PsiClass psiClass) { + return InheritanceUtil.isInheritor(psiClass, JavaClassNames.JAVA_LANG_RUNTIME_EXCEPTION) + || InheritanceUtil.isInheritor(psiClass, JavaClassNames.JAVA_LANG_ERROR); + } - private static boolean isHandled(@Nullable PsiElement element, @Nonnull PsiClassType exceptionType, PsiElement topElement) { - if (element == null || element.getParent() == topElement || element.getParent() == null) { - return false; + public static boolean isUncheckedExceptionOrSuperclass(@Nonnull final PsiClassType type) { + return isGeneralExceptionType(type) || isUncheckedException(type); } - Project project = element.getProject(); + public static boolean isGeneralExceptionType(@Nonnull final PsiType type) { + final String canonicalText = type.getCanonicalText(); + return JavaClassNames.JAVA_LANG_THROWABLE.equals(canonicalText) || JavaClassNames.JAVA_LANG_EXCEPTION.equals(canonicalText); + } - CustomExceptionHandler handler = - project.getExtensionPoint(CustomExceptionHandler.class).findFirstSafe(it -> it.isHandled(element, exceptionType, topElement)); - if (handler != null) { - return true; + public static boolean isHandled(@Nonnull PsiClassType exceptionType, @Nonnull PsiElement throwPlace) { + return isHandled(throwPlace, exceptionType, throwPlace.getContainingFile()); } - final PsiElement parent = element.getParent(); + private static boolean isHandled(@Nullable PsiElement element, @Nonnull PsiClassType exceptionType, PsiElement topElement) { + if (element == null || element.getParent() == topElement || element.getParent() == null) { + return false; + } - if (parent instanceof PsiMethod) { - PsiMethod method = (PsiMethod) parent; - return isHandledByMethodThrowsClause(method, exceptionType); - } else if (parent instanceof PsiClass) { - // arguments to anon class constructor should be handled higher - // like in void f() throws XXX { new AA(methodThrowingXXX()) { ... }; } - return parent instanceof PsiAnonymousClass && isHandled(parent, exceptionType, topElement); - } else if (parent instanceof PsiLambdaExpression || parent instanceof PsiMethodReferenceExpression && element == ((PsiMethodReferenceExpression) parent).getReferenceNameElement()) { - final PsiType interfaceType = ((PsiFunctionalExpression) parent).getFunctionalInterfaceType(); - return isDeclaredBySAMMethod(exceptionType, interfaceType); - } else if (parent instanceof PsiClassInitializer) { - if (((PsiClassInitializer) parent).hasModifierProperty(PsiModifier.STATIC)) { - return false; - } - // anonymous class initializers can throw any exceptions - if (!(parent.getParent() instanceof PsiAnonymousClass)) { - // exception thrown from within class instance initializer must be handled in every class constructor - // check each constructor throws exception or superclass (there must be at least one) - final PsiClass aClass = ((PsiClassInitializer) parent).getContainingClass(); - return areAllConstructorsThrow(aClass, exceptionType); - } - } else if (parent instanceof PsiTryStatement) { - PsiTryStatement tryStatement = (PsiTryStatement) parent; - if (tryStatement.getTryBlock() == element && isCaught(tryStatement, exceptionType)) { - return true; - } - if (tryStatement.getResourceList() == element && isCaught(tryStatement, exceptionType)) { - return true; - } - PsiCodeBlock finallyBlock = tryStatement.getFinallyBlock(); - if (element instanceof PsiCatchSection && finallyBlock != null && blockCompletesAbruptly(finallyBlock)) { - // exception swallowed - return true; - } - } else if (parent instanceof JavaCodeFragment) { - JavaCodeFragment codeFragment = (JavaCodeFragment) parent; - JavaCodeFragment.ExceptionHandler exceptionHandler = codeFragment.getExceptionHandler(); - return exceptionHandler != null && exceptionHandler.isHandledException(exceptionType); - } else if (PsiImplUtil.isInServerPage(parent) && parent instanceof PsiFile) { - return true; - } else if (parent instanceof PsiFile) { - return false; - } else if (parent instanceof PsiField && ((PsiField) parent).getInitializer() == element) { - final PsiClass aClass = ((PsiField) parent).getContainingClass(); - if (aClass != null && !(aClass instanceof PsiAnonymousClass) && !((PsiField) parent).hasModifierProperty(PsiModifier.STATIC)) { - // exceptions thrown in field initializers should be thrown in all class constructors - return areAllConstructorsThrow(aClass, exceptionType); - } - } else { - for (ExtraExceptionHandler exceptionHandler : ExtraExceptionHandler.EP_NAME.getExtensionList()) { - if (exceptionHandler.isHandled(exceptionType, element)) { - return true; - } - } - } - return isHandled(parent, exceptionType, topElement); - } - - private static boolean isDeclaredBySAMMethod(@Nonnull PsiClassType exceptionType, @Nullable PsiType interfaceType) { - if (interfaceType != null) { - final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(interfaceType); - final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(resolveResult); - if (interfaceMethod != null) { - return isHandledByMethodThrowsClause(interfaceMethod, exceptionType, LambdaUtil.getSubstitutor(interfaceMethod, resolveResult)); - } - } - return true; - } - - private static boolean areAllConstructorsThrow(@Nullable final PsiClass aClass, @Nonnull PsiClassType exceptionType) { - if (aClass == null) { - return false; - } - final PsiMethod[] constructors = aClass.getConstructors(); - boolean thrown = constructors.length != 0; - for (PsiMethod constructor : constructors) { - if (!isHandledByMethodThrowsClause(constructor, exceptionType)) { - thrown = false; - break; - } - } - return thrown; - } - - private static boolean isCaught(@Nonnull PsiTryStatement tryStatement, @Nonnull PsiClassType exceptionType) { - // if finally block completes abruptly, exception gets lost - PsiCodeBlock finallyBlock = tryStatement.getFinallyBlock(); - if (finallyBlock != null && blockCompletesAbruptly(finallyBlock)) { - return true; - } - - final PsiParameter[] catchBlockParameters = tryStatement.getCatchBlockParameters(); - for (PsiParameter parameter : catchBlockParameters) { - PsiType paramType = parameter.getType(); - if (paramType.isAssignableFrom(exceptionType)) { + Project project = element.getProject(); + + CustomExceptionHandler handler = + project.getExtensionPoint(CustomExceptionHandler.class).findFirstSafe(it -> it.isHandled(element, exceptionType, topElement)); + if (handler != null) { + return true; + } + + final PsiElement parent = element.getParent(); + + if (parent instanceof PsiMethod) { + PsiMethod method = (PsiMethod)parent; + return isHandledByMethodThrowsClause(method, exceptionType); + } + else if (parent instanceof PsiClass) { + // arguments to anon class constructor should be handled higher + // like in void f() throws XXX { new AA(methodThrowingXXX()) { ... }; } + return parent instanceof PsiAnonymousClass && isHandled(parent, exceptionType, topElement); + } + else if (parent instanceof PsiLambdaExpression || parent instanceof PsiMethodReferenceExpression && element == ((PsiMethodReferenceExpression)parent).getReferenceNameElement()) { + final PsiType interfaceType = ((PsiFunctionalExpression)parent).getFunctionalInterfaceType(); + return isDeclaredBySAMMethod(exceptionType, interfaceType); + } + else if (parent instanceof PsiClassInitializer) { + if (((PsiClassInitializer)parent).hasModifierProperty(PsiModifier.STATIC)) { + return false; + } + // anonymous class initializers can throw any exceptions + if (!(parent.getParent() instanceof PsiAnonymousClass)) { + // exception thrown from within class instance initializer must be handled in every class constructor + // check each constructor throws exception or superclass (there must be at least one) + final PsiClass aClass = ((PsiClassInitializer)parent).getContainingClass(); + return areAllConstructorsThrow(aClass, exceptionType); + } + } + else if (parent instanceof PsiTryStatement) { + PsiTryStatement tryStatement = (PsiTryStatement)parent; + if (tryStatement.getTryBlock() == element && isCaught(tryStatement, exceptionType)) { + return true; + } + if (tryStatement.getResourceList() == element && isCaught(tryStatement, exceptionType)) { + return true; + } + PsiCodeBlock finallyBlock = tryStatement.getFinallyBlock(); + if (element instanceof PsiCatchSection && finallyBlock != null && blockCompletesAbruptly(finallyBlock)) { + // exception swallowed + return true; + } + } + else if (parent instanceof JavaCodeFragment) { + JavaCodeFragment codeFragment = (JavaCodeFragment)parent; + JavaCodeFragment.ExceptionHandler exceptionHandler = codeFragment.getExceptionHandler(); + return exceptionHandler != null && exceptionHandler.isHandledException(exceptionType); + } + else if (PsiImplUtil.isInServerPage(parent) && parent instanceof PsiFile) { + return true; + } + else if (parent instanceof PsiFile) { + return false; + } + else if (parent instanceof PsiField && ((PsiField)parent).getInitializer() == element) { + final PsiClass aClass = ((PsiField)parent).getContainingClass(); + if (aClass != null && !(aClass instanceof PsiAnonymousClass) && !((PsiField)parent).hasModifierProperty(PsiModifier.STATIC)) { + // exceptions thrown in field initializers should be thrown in all class constructors + return areAllConstructorsThrow(aClass, exceptionType); + } + } + else { + for (ExtraExceptionHandler exceptionHandler : ExtraExceptionHandler.EP_NAME.getExtensionList()) { + if (exceptionHandler.isHandled(exceptionType, element)) { + return true; + } + } + } + return isHandled(parent, exceptionType, topElement); + } + + private static boolean isDeclaredBySAMMethod(@Nonnull PsiClassType exceptionType, @Nullable PsiType interfaceType) { + if (interfaceType != null) { + final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(interfaceType); + final PsiMethod interfaceMethod = LambdaUtil.getFunctionalInterfaceMethod(resolveResult); + if (interfaceMethod != null) { + return isHandledByMethodThrowsClause( + interfaceMethod, + exceptionType, + LambdaUtil.getSubstitutor(interfaceMethod, resolveResult) + ); + } + } return true; - } } - return false; - } + private static boolean areAllConstructorsThrow(@Nullable final PsiClass aClass, @Nonnull PsiClassType exceptionType) { + if (aClass == null) { + return false; + } + final PsiMethod[] constructors = aClass.getConstructors(); + boolean thrown = constructors.length != 0; + for (PsiMethod constructor : constructors) { + if (!isHandledByMethodThrowsClause(constructor, exceptionType)) { + thrown = false; + break; + } + } + return thrown; + } - private static boolean blockCompletesAbruptly(@Nonnull final PsiCodeBlock finallyBlock) { - try { - ControlFlow flow = ControlFlowFactory.getInstance(finallyBlock.getProject()).getControlFlow(finallyBlock, LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance(), false); - int completionReasons = ControlFlowUtil.getCompletionReasons(flow, 0, flow.getSize()); - if (!BitUtil.isSet(completionReasons, ControlFlowUtil.NORMAL_COMPLETION_REASON)) { - return true; - } - } catch (AnalysisCanceledException e) { - return true; - } - return false; - } - - private static boolean isHandledByMethodThrowsClause(@Nonnull PsiMethod method, @Nonnull PsiClassType exceptionType) { - return isHandledByMethodThrowsClause(method, exceptionType, PsiSubstitutor.EMPTY); - } - - private static boolean isHandledByMethodThrowsClause(@Nonnull PsiMethod method, @Nonnull PsiClassType exceptionType, PsiSubstitutor substitutor) { - final PsiClassType[] referencedTypes = method.getThrowsList().getReferencedTypes(); - return isHandledBy(exceptionType, referencedTypes, substitutor); - } - - public static boolean isHandledBy(@Nonnull PsiClassType exceptionType, @Nonnull PsiClassType[] referencedTypes) { - return isHandledBy(exceptionType, referencedTypes, PsiSubstitutor.EMPTY); - } - - public static boolean isHandledBy(@Nonnull PsiClassType exceptionType, @Nonnull PsiClassType[] referencedTypes, PsiSubstitutor substitutor) { - for (PsiClassType classType : referencedTypes) { - PsiType psiType = substitutor.substitute(classType); - if (psiType != null && psiType.isAssignableFrom(exceptionType)) { - return true; - } + private static boolean isCaught(@Nonnull PsiTryStatement tryStatement, @Nonnull PsiClassType exceptionType) { + // if finally block completes abruptly, exception gets lost + PsiCodeBlock finallyBlock = tryStatement.getFinallyBlock(); + if (finallyBlock != null && blockCompletesAbruptly(finallyBlock)) { + return true; + } + + final PsiParameter[] catchBlockParameters = tryStatement.getCatchBlockParameters(); + for (PsiParameter parameter : catchBlockParameters) { + PsiType paramType = parameter.getType(); + if (paramType.isAssignableFrom(exceptionType)) { + return true; + } + } + + return false; } - return false; - } - public static void sortExceptionsByHierarchy(@Nonnull List exceptions) { - if (exceptions.size() <= 1) { - return; + private static boolean blockCompletesAbruptly(@Nonnull final PsiCodeBlock finallyBlock) { + try { + ControlFlow flow = ControlFlowFactory.getInstance(finallyBlock.getProject()) + .getControlFlow(finallyBlock, LocalsOrMyInstanceFieldsControlFlowPolicy.getInstance(), false); + int completionReasons = ControlFlowUtil.getCompletionReasons(flow, 0, flow.getSize()); + if (!BitUtil.isSet(completionReasons, ControlFlowUtil.NORMAL_COMPLETION_REASON)) { + return true; + } + } + catch (AnalysisCanceledException e) { + return true; + } + return false; } - sortExceptionsByHierarchy(exceptions.subList(1, exceptions.size())); - for (int i = 0; i < exceptions.size() - 1; i++) { - if (TypeConversionUtil.isAssignable(exceptions.get(i), exceptions.get(i + 1))) { - Collections.swap(exceptions, i, i + 1); - } + + private static boolean isHandledByMethodThrowsClause(@Nonnull PsiMethod method, @Nonnull PsiClassType exceptionType) { + return isHandledByMethodThrowsClause(method, exceptionType, PsiSubstitutor.EMPTY); + } + + private static boolean isHandledByMethodThrowsClause( + @Nonnull PsiMethod method, + @Nonnull PsiClassType exceptionType, + PsiSubstitutor substitutor + ) { + final PsiClassType[] referencedTypes = method.getThrowsList().getReferencedTypes(); + return isHandledBy(exceptionType, referencedTypes, substitutor); + } + + public static boolean isHandledBy(@Nonnull PsiClassType exceptionType, @Nonnull PsiClassType[] referencedTypes) { + return isHandledBy(exceptionType, referencedTypes, PsiSubstitutor.EMPTY); + } + + public static boolean isHandledBy( + @Nonnull PsiClassType exceptionType, + @Nonnull PsiClassType[] referencedTypes, + PsiSubstitutor substitutor + ) { + for (PsiClassType classType : referencedTypes) { + PsiType psiType = substitutor.substitute(classType); + if (psiType != null && psiType.isAssignableFrom(exceptionType)) { + return true; + } + } + return false; + } + + public static void sortExceptionsByHierarchy(@Nonnull List exceptions) { + if (exceptions.size() <= 1) { + return; + } + sortExceptionsByHierarchy(exceptions.subList(1, exceptions.size())); + for (int i = 0; i < exceptions.size() - 1; i++) { + if (TypeConversionUtil.isAssignable(exceptions.get(i), exceptions.get(i + 1))) { + Collections.swap(exceptions, i, i + 1); + } + } } - } } diff --git a/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/compiled/ClsClassImpl.java b/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/compiled/ClsClassImpl.java index be028524c8..c6601a7863 100644 --- a/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/compiled/ClsClassImpl.java +++ b/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/compiled/ClsClassImpl.java @@ -45,581 +45,599 @@ import static java.util.Arrays.asList; public class ClsClassImpl extends ClsMemberImpl> implements PsiExtensibleClass, Queryable { - public static final Key DELEGATE_KEY = Key.create("DELEGATE"); - - private final ClassInnerStuffCache myInnersCache = new ClassInnerStuffCache(this); - - public ClsClassImpl(final PsiClassStub stub) { - super(stub); - putUserData(JavaEnumAugmentProvider.FLAG, Boolean.TRUE); - } - - @RequiredReadAction - @Override - @Nonnull - public PsiElement[] getChildren() { - List children = ContainerUtil.newArrayList(); - ContainerUtil.addAll(children, getChildren(getDocComment(), getModifierListInternal(), getNameIdentifier(), getExtendsList(), getImplementsList())); - ContainerUtil.addAll(children, getOwnFields()); - ContainerUtil.addAll(children, getOwnMethods()); - ContainerUtil.addAll(children, getOwnInnerClasses()); - return PsiUtilCore.toPsiElementArray(children); - } - - @Override - @Nonnull - public PsiTypeParameterList getTypeParameterList() { - return assertNotNull(getStub().findChildStubByType(JavaStubElementTypes.TYPE_PARAMETER_LIST)).getPsi(); - } - - @Override - public boolean hasTypeParameters() { - return PsiImplUtil.hasTypeParameters(this); - } - - @Override - @Nullable - public String getQualifiedName() { - return getStub().getQualifiedName(); - } - - private boolean isLocalClass() { - PsiClassStub stub = getStub(); - return stub instanceof PsiClassStubImpl && ((PsiClassStubImpl) stub).isLocalClassInner(); - } - - private boolean isAnonymousClass() { - PsiClassStub stub = getStub(); - return stub instanceof PsiClassStubImpl && ((PsiClassStubImpl) stub).isAnonymousInner(); - } - - private boolean isAnonymousOrLocalClass() { - return isAnonymousClass() || isLocalClass(); - } - - @Override - @Nullable - public PsiModifierList getModifierList() { - if (isAnonymousClass()) { - return null; - } - return getModifierListInternal(); - } - - private PsiModifierList getModifierListInternal() { - return assertNotNull(getStub().findChildStubByType(JavaStubElementTypes.MODIFIER_LIST)).getPsi(); - } - - @Override - public boolean hasModifierProperty(@Nonnull String name) { - return getModifierListInternal().hasModifierProperty(name); - } - - @Override - @Nonnull - public PsiReferenceList getExtendsList() { - return assertNotNull(getStub().findChildStubByType(JavaStubElementTypes.EXTENDS_LIST)).getPsi(); - } - - @Override - @Nonnull - public PsiReferenceList getImplementsList() { - return assertNotNull(getStub().findChildStubByType(JavaStubElementTypes.IMPLEMENTS_LIST)).getPsi(); - } - - @Override - public @Nullable PsiReferenceList getPermitsList() { - PsiClassReferenceListStub type = getStub().findChildStubByType(JavaStubElementTypes.PERMITS_LIST); - return type == null ? null : type.getPsi(); - } - - @Override - @Nonnull - public PsiClassType[] getExtendsListTypes() { - return PsiClassImplUtil.getExtendsListTypes(this); - } - - @Override - @Nonnull - public PsiClassType[] getImplementsListTypes() { - return PsiClassImplUtil.getImplementsListTypes(this); - } - - @Override - public PsiClass getSuperClass() { - return PsiClassImplUtil.getSuperClass(this); - } - - @Nonnull - @Override - public PsiClass[] getInterfaces() { - return PsiClassImplUtil.getInterfaces(this); - } - - @Override - @Nonnull - public PsiClass[] getSupers() { - if (JavaClassNames.JAVA_LANG_OBJECT.equals(getQualifiedName())) { - return PsiClass.EMPTY_ARRAY; - } - return PsiClassImplUtil.getSupers(this); - } - - @Override - @Nonnull - public PsiClassType[] getSuperTypes() { - if (JavaClassNames.JAVA_LANG_OBJECT.equals(getQualifiedName())) { - return PsiClassType.EMPTY_ARRAY; - } - return PsiClassImplUtil.getSuperTypes(this); - } - - @Override - public PsiClass getContainingClass() { - PsiElement parent = getParent(); - return parent instanceof PsiClass ? (PsiClass) parent : null; - } - - @Override - @Nonnull - public Collection getVisibleSignatures() { - return PsiSuperMethodImplUtil.getVisibleSignatures(this); - } - - @Override - @Nonnull - public PsiField[] getFields() { - return myInnersCache.getFields(); - } - - @Override - @Nonnull - public PsiMethod[] getMethods() { - return myInnersCache.getMethods(); - } - - @Override - @Nonnull - public PsiMethod[] getConstructors() { - return myInnersCache.getConstructors(); - } - - @Override - @Nonnull - public PsiClass[] getInnerClasses() { - return myInnersCache.getInnerClasses(); - } - - @Nonnull - @Override - public List getOwnFields() { - return asList(getStub().getChildrenByType(Constants.FIELD_BIT_SET, PsiField.ARRAY_FACTORY)); - } - - @Nonnull - @Override - public List getOwnMethods() { - return asList(getStub().getChildrenByType(Constants.METHOD_BIT_SET, PsiMethod.ARRAY_FACTORY)); - } - - @Nonnull - @Override - public List getOwnInnerClasses() { - PsiClass[] classes = getStub().getChildrenByType(JavaStubElementTypes.CLASS, PsiClass.ARRAY_FACTORY); - if (classes.length == 0) { - return Collections.emptyList(); - } - - int anonymousOrLocalClassesCount = 0; - for (PsiClass aClass : classes) { - if (aClass instanceof ClsClassImpl && ((ClsClassImpl) aClass).isAnonymousOrLocalClass()) { - ++anonymousOrLocalClassesCount; - } - } - if (anonymousOrLocalClassesCount == 0) { - return asList(classes); - } - - ArrayList result = new ArrayList<>(classes.length - anonymousOrLocalClassesCount); - for (PsiClass aClass : classes) { - if (!(aClass instanceof ClsClassImpl) || !((ClsClassImpl) aClass).isAnonymousOrLocalClass()) { - result.add(aClass); - } - } - return result; - } - - @Override - @Nonnull - public PsiClassInitializer[] getInitializers() { - return PsiClassInitializer.EMPTY_ARRAY; - } - - @Override - @Nonnull - public PsiTypeParameter[] getTypeParameters() { - return PsiImplUtil.getTypeParameters(this); - } - - @Override - @Nonnull - public PsiField[] getAllFields() { - return PsiClassImplUtil.getAllFields(this); - } - - @Override - @Nonnull - public PsiMethod[] getAllMethods() { - return PsiClassImplUtil.getAllMethods(this); - } - - @Override - @Nonnull - public PsiClass[] getAllInnerClasses() { - return PsiClassImplUtil.getAllInnerClasses(this); - } - - @Override - public PsiField findFieldByName(String name, boolean checkBases) { - return myInnersCache.findFieldByName(name, checkBases); - } - - @Override - public PsiMethod findMethodBySignature(PsiMethod patternMethod, boolean checkBases) { - return PsiClassImplUtil.findMethodBySignature(this, patternMethod, checkBases); - } - - @Override - @Nonnull - public PsiMethod[] findMethodsBySignature(PsiMethod patternMethod, boolean checkBases) { - return PsiClassImplUtil.findMethodsBySignature(this, patternMethod, checkBases); - } - - @Override - @Nonnull - public PsiMethod[] findMethodsByName(String name, boolean checkBases) { - return myInnersCache.findMethodsByName(name, checkBases); - } - - @Override - @Nonnull - public List> findMethodsAndTheirSubstitutorsByName(String name, boolean checkBases) { - return PsiClassImplUtil.findMethodsAndTheirSubstitutorsByName(this, name, checkBases); - } - - @Override - @Nonnull - public List> getAllMethodsAndTheirSubstitutors() { - return PsiClassImplUtil.getAllWithSubstitutorsByMap(this, PsiClassImplUtil.MemberType.METHOD); - } - - @Override - public PsiClass findInnerClassByName(String name, boolean checkBases) { - return myInnersCache.findInnerClassByName(name, checkBases); - } - - @Override - public boolean isDeprecated() { - return getStub().isDeprecated() || PsiImplUtil.isDeprecatedByAnnotation(this); - } - - public String getSourceFileName() { - final String sfn = getStub().getSourceFileName(); - return sfn != null ? sfn : obtainSourceFileNameFromClassFileName(); - } - - @NonNls - private String obtainSourceFileNameFromClassFileName() { - final String name = getContainingFile().getName(); - int i = name.indexOf('$'); - if (i < 0) { - i = name.indexOf('.'); - if (i < 0) { - i = name.length(); - } - } - return name.substring(0, i) + ".java"; - } - - @Override - public PsiJavaToken getLBrace() { - return null; - } - - @Override - public PsiJavaToken getRBrace() { - return null; - } - - @Override - public boolean isInterface() { - return getStub().isInterface(); - } - - @Override - public boolean isAnnotationType() { - return getStub().isAnnotationType(); - } - - @Override - public boolean isEnum() { - return getStub().isEnum(); - } - - @Override - public void appendMirrorText(final int indentLevel, @Nonnull @NonNls final StringBuilder buffer) { - appendText(getDocComment(), indentLevel, buffer, NEXT_LINE); - - appendText(getModifierListInternal(), indentLevel, buffer); - buffer.append(isEnum() ? "enum " : isAnnotationType() ? "@interface " : isInterface() ? "interface " : "class "); - appendText(getNameIdentifier(), indentLevel, buffer, " "); - appendText(getTypeParameterList(), indentLevel, buffer, " "); - appendText(getExtendsList(), indentLevel, buffer, " "); - appendText(getImplementsList(), indentLevel, buffer, " "); - - buffer.append('{'); - - int newIndentLevel = indentLevel + getIndentSize(); - List fields = getOwnFields(); - List methods = getOwnMethods(); - List classes = getOwnInnerClasses(); - - if (!fields.isEmpty()) { - goNextLine(newIndentLevel, buffer); - - for (int i = 0; i < fields.size(); i++) { - PsiField field = fields.get(i); - appendText(field, newIndentLevel, buffer); - - if (field instanceof ClsEnumConstantImpl) { - if (i < fields.size() - 1 && fields.get(i + 1) instanceof ClsEnumConstantImpl) { - buffer.append(", "); - } else { - buffer.append(';'); - if (i < fields.size() - 1) { - buffer.append('\n'); - goNextLine(newIndentLevel, buffer); - } - } - } else if (i < fields.size() - 1) { - goNextLine(newIndentLevel, buffer); + public static final Key DELEGATE_KEY = Key.create("DELEGATE"); + + private final ClassInnerStuffCache myInnersCache = new ClassInnerStuffCache(this); + + public ClsClassImpl(final PsiClassStub stub) { + super(stub); + putUserData(JavaEnumAugmentProvider.FLAG, Boolean.TRUE); + } + + @RequiredReadAction + @Override + @Nonnull + public PsiElement[] getChildren() { + List children = ContainerUtil.newArrayList(); + ContainerUtil.addAll( + children, + getChildren(getDocComment(), getModifierListInternal(), getNameIdentifier(), getExtendsList(), getImplementsList()) + ); + ContainerUtil.addAll(children, getOwnFields()); + ContainerUtil.addAll(children, getOwnMethods()); + ContainerUtil.addAll(children, getOwnInnerClasses()); + return PsiUtilCore.toPsiElementArray(children); + } + + @Override + @Nonnull + public PsiTypeParameterList getTypeParameterList() { + return assertNotNull(getStub().findChildStubByType(JavaStubElementTypes.TYPE_PARAMETER_LIST)).getPsi(); + } + + @Override + public boolean hasTypeParameters() { + return PsiImplUtil.hasTypeParameters(this); + } + + @Override + @Nullable + public String getQualifiedName() { + return getStub().getQualifiedName(); + } + + private boolean isLocalClass() { + PsiClassStub stub = getStub(); + return stub instanceof PsiClassStubImpl && ((PsiClassStubImpl)stub).isLocalClassInner(); + } + + private boolean isAnonymousClass() { + PsiClassStub stub = getStub(); + return stub instanceof PsiClassStubImpl && ((PsiClassStubImpl)stub).isAnonymousInner(); + } + + private boolean isAnonymousOrLocalClass() { + return isAnonymousClass() || isLocalClass(); + } + + @Override + @Nullable + public PsiModifierList getModifierList() { + if (isAnonymousClass()) { + return null; + } + return getModifierListInternal(); + } + + private PsiModifierList getModifierListInternal() { + return assertNotNull(getStub().findChildStubByType(JavaStubElementTypes.MODIFIER_LIST)).getPsi(); + } + + @Override + public boolean hasModifierProperty(@Nonnull String name) { + return getModifierListInternal().hasModifierProperty(name); + } + + @Override + @Nonnull + public PsiReferenceList getExtendsList() { + return assertNotNull(getStub().findChildStubByType(JavaStubElementTypes.EXTENDS_LIST)).getPsi(); + } + + @Override + @Nonnull + public PsiReferenceList getImplementsList() { + return assertNotNull(getStub().findChildStubByType(JavaStubElementTypes.IMPLEMENTS_LIST)).getPsi(); + } + + @Override + public @Nullable PsiReferenceList getPermitsList() { + PsiClassReferenceListStub type = getStub().findChildStubByType(JavaStubElementTypes.PERMITS_LIST); + return type == null ? null : type.getPsi(); + } + + @Override + @Nonnull + public PsiClassType[] getExtendsListTypes() { + return PsiClassImplUtil.getExtendsListTypes(this); + } + + @Override + @Nonnull + public PsiClassType[] getImplementsListTypes() { + return PsiClassImplUtil.getImplementsListTypes(this); + } + + @Override + public PsiClass getSuperClass() { + return PsiClassImplUtil.getSuperClass(this); + } + + @Nonnull + @Override + public PsiClass[] getInterfaces() { + return PsiClassImplUtil.getInterfaces(this); + } + + @Override + @Nonnull + public PsiClass[] getSupers() { + if (JavaClassNames.JAVA_LANG_OBJECT.equals(getQualifiedName())) { + return PsiClass.EMPTY_ARRAY; } - } - } else if (isEnum() && methods.size() + classes.size() > 0) { - goNextLine(newIndentLevel, buffer); - buffer.append(";"); + return PsiClassImplUtil.getSupers(this); + } + + @Override + @Nonnull + public PsiClassType[] getSuperTypes() { + if (JavaClassNames.JAVA_LANG_OBJECT.equals(getQualifiedName())) { + return PsiClassType.EMPTY_ARRAY; + } + return PsiClassImplUtil.getSuperTypes(this); + } + + @Override + public PsiClass getContainingClass() { + PsiElement parent = getParent(); + return parent instanceof PsiClass ? (PsiClass)parent : null; + } + + @Override + @Nonnull + public Collection getVisibleSignatures() { + return PsiSuperMethodImplUtil.getVisibleSignatures(this); + } + + @Override + @Nonnull + public PsiField[] getFields() { + return myInnersCache.getFields(); + } + + @Override + @Nonnull + public PsiMethod[] getMethods() { + return myInnersCache.getMethods(); + } + + @Override + @Nonnull + public PsiMethod[] getConstructors() { + return myInnersCache.getConstructors(); + } + + @Override + @Nonnull + public PsiClass[] getInnerClasses() { + return myInnersCache.getInnerClasses(); + } + + @Nonnull + @Override + public List getOwnFields() { + return asList(getStub().getChildrenByType(Constants.FIELD_BIT_SET, PsiField.ARRAY_FACTORY)); } - if (!methods.isEmpty()) { - if (isEnum() || !fields.isEmpty()) { - buffer.append('\n'); - } - goNextLine(newIndentLevel, buffer); + @Nonnull + @Override + public List getOwnMethods() { + return asList(getStub().getChildrenByType(Constants.METHOD_BIT_SET, PsiMethod.ARRAY_FACTORY)); + } - for (int i = 0; i < methods.size(); i++) { - appendText(methods.get(i), newIndentLevel, buffer); + @Nonnull + @Override + public List getOwnInnerClasses() { + PsiClass[] classes = getStub().getChildrenByType(JavaStubElementTypes.CLASS, PsiClass.ARRAY_FACTORY); + if (classes.length == 0) { + return Collections.emptyList(); + } - if (i < methods.size() - 1) { - buffer.append('\n'); - goNextLine(newIndentLevel, buffer); + int anonymousOrLocalClassesCount = 0; + for (PsiClass aClass : classes) { + if (aClass instanceof ClsClassImpl && ((ClsClassImpl)aClass).isAnonymousOrLocalClass()) { + ++anonymousOrLocalClassesCount; + } } - } + if (anonymousOrLocalClassesCount == 0) { + return asList(classes); + } + + ArrayList result = new ArrayList<>(classes.length - anonymousOrLocalClassesCount); + for (PsiClass aClass : classes) { + if (!(aClass instanceof ClsClassImpl) || !((ClsClassImpl)aClass).isAnonymousOrLocalClass()) { + result.add(aClass); + } + } + return result; + } + + @Override + @Nonnull + public PsiClassInitializer[] getInitializers() { + return PsiClassInitializer.EMPTY_ARRAY; + } + + @Override + @Nonnull + public PsiTypeParameter[] getTypeParameters() { + return PsiImplUtil.getTypeParameters(this); + } + + @Override + @Nonnull + public PsiField[] getAllFields() { + return PsiClassImplUtil.getAllFields(this); + } + + @Override + @Nonnull + public PsiMethod[] getAllMethods() { + return PsiClassImplUtil.getAllMethods(this); + } + + @Override + @Nonnull + public PsiClass[] getAllInnerClasses() { + return PsiClassImplUtil.getAllInnerClasses(this); + } + + @Override + public PsiField findFieldByName(String name, boolean checkBases) { + return myInnersCache.findFieldByName(name, checkBases); + } + + @Override + public PsiMethod findMethodBySignature(PsiMethod patternMethod, boolean checkBases) { + return PsiClassImplUtil.findMethodBySignature(this, patternMethod, checkBases); + } + + @Override + @Nonnull + public PsiMethod[] findMethodsBySignature(PsiMethod patternMethod, boolean checkBases) { + return PsiClassImplUtil.findMethodsBySignature(this, patternMethod, checkBases); + } + + @Override + @Nonnull + public PsiMethod[] findMethodsByName(String name, boolean checkBases) { + return myInnersCache.findMethodsByName(name, checkBases); + } + + @Override + @Nonnull + public List> findMethodsAndTheirSubstitutorsByName(String name, boolean checkBases) { + return PsiClassImplUtil.findMethodsAndTheirSubstitutorsByName(this, name, checkBases); + } + + @Override + @Nonnull + public List> getAllMethodsAndTheirSubstitutors() { + return PsiClassImplUtil.getAllWithSubstitutorsByMap(this, PsiClassImplUtil.MemberType.METHOD); + } + + @Override + public PsiClass findInnerClassByName(String name, boolean checkBases) { + return myInnersCache.findInnerClassByName(name, checkBases); } - if (!classes.isEmpty()) { - if (fields.size() + methods.size() > 0) { - buffer.append('\n'); - } - goNextLine(newIndentLevel, buffer); + @Override + public boolean isDeprecated() { + return getStub().isDeprecated() || PsiImplUtil.isDeprecatedByAnnotation(this); + } - for (int i = 0; i < classes.size(); i++) { - appendText(classes.get(i), newIndentLevel, buffer); + public String getSourceFileName() { + final String sfn = getStub().getSourceFileName(); + return sfn != null ? sfn : obtainSourceFileNameFromClassFileName(); + } - if (i < classes.size() - 1) { - buffer.append('\n'); - goNextLine(newIndentLevel, buffer); + @NonNls + private String obtainSourceFileNameFromClassFileName() { + final String name = getContainingFile().getName(); + int i = name.indexOf('$'); + if (i < 0) { + i = name.indexOf('.'); + if (i < 0) { + i = name.length(); + } } - } - } - - goNextLine(indentLevel, buffer); - buffer.append('}'); - } - - @Override - public void setMirror(@Nonnull TreeElement element) throws InvalidMirrorException { - setMirrorCheckingType(element, null); - - PsiClass mirror = SourceTreeToPsiMap.treeToPsiNotNull(element); - - setMirrorIfPresent(getDocComment(), mirror.getDocComment()); - - PsiModifierList modifierList = getModifierList(); - if (modifierList != null) { - setMirror(modifierList, mirror.getModifierList()); - } - setMirror(getNameIdentifier(), mirror.getNameIdentifier()); - setMirror(getTypeParameterList(), mirror.getTypeParameterList()); - setMirror(getExtendsList(), mirror.getExtendsList()); - setMirror(getImplementsList(), mirror.getImplementsList()); - - if (mirror instanceof PsiExtensibleClass) { - PsiExtensibleClass extMirror = (PsiExtensibleClass) mirror; - setMirrors(getOwnFields(), extMirror.getOwnFields()); - setMirrors(getOwnMethods(), extMirror.getOwnMethods()); - setMirrors(getOwnInnerClasses(), extMirror.getOwnInnerClasses()); - } else { - setMirrors(getOwnFields(), asList(mirror.getFields())); - setMirrors(getOwnMethods(), asList(mirror.getMethods())); - setMirrors(getOwnInnerClasses(), asList(mirror.getInnerClasses())); - } - } - - @Override - public void accept(@Nonnull PsiElementVisitor visitor) { - if (visitor instanceof JavaElementVisitor) { - ((JavaElementVisitor) visitor).visitClass(this); - } else { - visitor.visitElement(this); - } - } - - @NonNls - public String toString() { - return "PsiClass:" + getName(); - } - - @Override - public boolean processDeclarations(@Nonnull PsiScopeProcessor processor, @Nonnull ResolveState state, PsiElement lastParent, @Nonnull PsiElement place) { - LanguageLevel level = processor instanceof MethodsProcessor ? ((MethodsProcessor) processor).getLanguageLevel() : PsiUtil.getLanguageLevel(place); - return PsiClassImplUtil.processDeclarationsInClass(this, processor, state, null, lastParent, place, level, false); - } - - @Override - public PsiElement getScope() { - return getParent(); - } - - @Override - public boolean isInheritorDeep(PsiClass baseClass, PsiClass classToByPass) { - return InheritanceImplUtil.isInheritorDeep(this, baseClass, classToByPass); - } - - @Override - public boolean isInheritor(@Nonnull PsiClass baseClass, boolean checkDeep) { - return InheritanceImplUtil.isInheritor(this, baseClass, checkDeep); - } - - @Nullable - public PsiClass getSourceMirrorClass() { - final PsiClass delegate = getUserData(DELEGATE_KEY); - if (delegate instanceof ClsClassImpl) { - return ((ClsClassImpl) delegate).getSourceMirrorClass(); - } - - final String name = getName(); - final PsiElement parent = getParent(); - if (parent instanceof PsiFile) { - if (!(parent instanceof PsiClassOwner)) { + return name.substring(0, i) + ".java"; + } + + @Override + public PsiJavaToken getLBrace() { return null; - } + } - PsiClassOwner fileNavigationElement = (PsiClassOwner) parent.getNavigationElement(); - if (fileNavigationElement == parent) { + @Override + public PsiJavaToken getRBrace() { return null; - } + } + + @Override + public boolean isInterface() { + return getStub().isInterface(); + } + + @Override + public boolean isAnnotationType() { + return getStub().isAnnotationType(); + } + + @Override + public boolean isEnum() { + return getStub().isEnum(); + } - for (PsiClass aClass : fileNavigationElement.getClasses()) { - if (name.equals(aClass.getName())) { - return aClass; + @Override + public void appendMirrorText(final int indentLevel, @Nonnull @NonNls final StringBuilder buffer) { + appendText(getDocComment(), indentLevel, buffer, NEXT_LINE); + + appendText(getModifierListInternal(), indentLevel, buffer); + buffer.append(isEnum() ? "enum " : isAnnotationType() ? "@interface " : isInterface() ? "interface " : "class "); + appendText(getNameIdentifier(), indentLevel, buffer, " "); + appendText(getTypeParameterList(), indentLevel, buffer, " "); + appendText(getExtendsList(), indentLevel, buffer, " "); + appendText(getImplementsList(), indentLevel, buffer, " "); + + buffer.append('{'); + + int newIndentLevel = indentLevel + getIndentSize(); + List fields = getOwnFields(); + List methods = getOwnMethods(); + List classes = getOwnInnerClasses(); + + if (!fields.isEmpty()) { + goNextLine(newIndentLevel, buffer); + + for (int i = 0; i < fields.size(); i++) { + PsiField field = fields.get(i); + appendText(field, newIndentLevel, buffer); + + if (field instanceof ClsEnumConstantImpl) { + if (i < fields.size() - 1 && fields.get(i + 1) instanceof ClsEnumConstantImpl) { + buffer.append(", "); + } + else { + buffer.append(';'); + if (i < fields.size() - 1) { + buffer.append('\n'); + goNextLine(newIndentLevel, buffer); + } + } + } + else if (i < fields.size() - 1) { + goNextLine(newIndentLevel, buffer); + } + } } - } - } else if (parent != null) { - ClsClassImpl parentClass = (ClsClassImpl) parent; - PsiClass parentSourceMirror = parentClass.getSourceMirrorClass(); - if (parentSourceMirror == null) { - return null; - } - PsiClass[] innerClasses = parentSourceMirror.getInnerClasses(); - for (PsiClass innerClass : innerClasses) { - if (name.equals(innerClass.getName())) { - return innerClass; + else if (isEnum() && methods.size() + classes.size() > 0) { + goNextLine(newIndentLevel, buffer); + buffer.append(";"); + } + + if (!methods.isEmpty()) { + if (isEnum() || !fields.isEmpty()) { + buffer.append('\n'); + } + goNextLine(newIndentLevel, buffer); + + for (int i = 0; i < methods.size(); i++) { + appendText(methods.get(i), newIndentLevel, buffer); + + if (i < methods.size() - 1) { + buffer.append('\n'); + goNextLine(newIndentLevel, buffer); + } + } } - } - } else { - throw new PsiInvalidElementAccessException(this); - } - - return null; - } - - @Override - @Nonnull - public PsiElement getNavigationElement() { - for (ClsCustomNavigationPolicy navigationPolicy : Extensions.getExtensions(ClsCustomNavigationPolicy.EP_NAME)) { - try { - PsiElement navigationElement = navigationPolicy.getNavigationElement(this); - if (navigationElement != null) { - return navigationElement; + + if (!classes.isEmpty()) { + if (fields.size() + methods.size() > 0) { + buffer.append('\n'); + } + goNextLine(newIndentLevel, buffer); + + for (int i = 0; i < classes.size(); i++) { + appendText(classes.get(i), newIndentLevel, buffer); + + if (i < classes.size() - 1) { + buffer.append('\n'); + goNextLine(newIndentLevel, buffer); + } + } } - } catch (IndexNotReadyException ignored) { - } + + goNextLine(indentLevel, buffer); + buffer.append('}'); } - try { - PsiClass aClass = getSourceMirrorClass(); - if (aClass != null) { - return aClass.getNavigationElement(); - } + @Override + public void setMirror(@Nonnull TreeElement element) throws InvalidMirrorException { + setMirrorCheckingType(element, null); - if ("package-info".equals(getName())) { - PsiElement parent = getParent(); - if (parent instanceof ClsFileImpl) { - PsiElement sourceFile = parent.getNavigationElement(); - if (sourceFile instanceof PsiJavaFile) { - return sourceFile; - } + PsiClass mirror = SourceTreeToPsiMap.treeToPsiNotNull(element); + + setMirrorIfPresent(getDocComment(), mirror.getDocComment()); + + PsiModifierList modifierList = getModifierList(); + if (modifierList != null) { + setMirror(modifierList, mirror.getModifierList()); + } + setMirror(getNameIdentifier(), mirror.getNameIdentifier()); + setMirror(getTypeParameterList(), mirror.getTypeParameterList()); + setMirror(getExtendsList(), mirror.getExtendsList()); + setMirror(getImplementsList(), mirror.getImplementsList()); + + if (mirror instanceof PsiExtensibleClass) { + PsiExtensibleClass extMirror = (PsiExtensibleClass)mirror; + setMirrors(getOwnFields(), extMirror.getOwnFields()); + setMirrors(getOwnMethods(), extMirror.getOwnMethods()); + setMirrors(getOwnInnerClasses(), extMirror.getOwnInnerClasses()); + } + else { + setMirrors(getOwnFields(), asList(mirror.getFields())); + setMirrors(getOwnMethods(), asList(mirror.getMethods())); + setMirrors(getOwnInnerClasses(), asList(mirror.getInnerClasses())); + } + } + + @Override + public void accept(@Nonnull PsiElementVisitor visitor) { + if (visitor instanceof JavaElementVisitor) { + ((JavaElementVisitor)visitor).visitClass(this); + } + else { + visitor.visitElement(this); + } + } + + @NonNls + public String toString() { + return "PsiClass:" + getName(); + } + + @Override + public boolean processDeclarations( + @Nonnull PsiScopeProcessor processor, + @Nonnull ResolveState state, + PsiElement lastParent, + @Nonnull PsiElement place + ) { + LanguageLevel level = + processor instanceof MethodsProcessor ? ((MethodsProcessor)processor).getLanguageLevel() : PsiUtil.getLanguageLevel(place); + return PsiClassImplUtil.processDeclarationsInClass(this, processor, state, null, lastParent, place, level, false); + } + + @Override + public PsiElement getScope() { + return getParent(); + } + + @Override + public boolean isInheritorDeep(PsiClass baseClass, PsiClass classToByPass) { + return InheritanceImplUtil.isInheritorDeep(this, baseClass, classToByPass); + } + + @Override + public boolean isInheritor(@Nonnull PsiClass baseClass, boolean checkDeep) { + return InheritanceImplUtil.isInheritor(this, baseClass, checkDeep); + } + + @Nullable + public PsiClass getSourceMirrorClass() { + final PsiClass delegate = getUserData(DELEGATE_KEY); + if (delegate instanceof ClsClassImpl) { + return ((ClsClassImpl)delegate).getSourceMirrorClass(); + } + + final String name = getName(); + final PsiElement parent = getParent(); + if (parent instanceof PsiFile) { + if (!(parent instanceof PsiClassOwner)) { + return null; + } + + PsiClassOwner fileNavigationElement = (PsiClassOwner)parent.getNavigationElement(); + if (fileNavigationElement == parent) { + return null; + } + + for (PsiClass aClass : fileNavigationElement.getClasses()) { + if (name.equals(aClass.getName())) { + return aClass; + } + } + } + else if (parent != null) { + ClsClassImpl parentClass = (ClsClassImpl)parent; + PsiClass parentSourceMirror = parentClass.getSourceMirrorClass(); + if (parentSourceMirror == null) { + return null; + } + PsiClass[] innerClasses = parentSourceMirror.getInnerClasses(); + for (PsiClass innerClass : innerClasses) { + if (name.equals(innerClass.getName())) { + return innerClass; + } + } + } + else { + throw new PsiInvalidElementAccessException(this); + } + + return null; + } + + @Override + @Nonnull + public PsiElement getNavigationElement() { + for (ClsCustomNavigationPolicy navigationPolicy : Extensions.getExtensions(ClsCustomNavigationPolicy.EP_NAME)) { + try { + PsiElement navigationElement = navigationPolicy.getNavigationElement(this); + if (navigationElement != null) { + return navigationElement; + } + } + catch (IndexNotReadyException ignored) { + } } - } - } catch (IndexNotReadyException ignore) { - } - - return this; - } - - @Override - public ItemPresentation getPresentation() { - return ItemPresentationProvider.getItemPresentation(this); - } - - @Override - public boolean isEquivalentTo(final PsiElement another) { - return PsiClassImplUtil.isClassEquivalentTo(this, another); - } - - @Override - @Nonnull - public SearchScope getUseScope() { - return PsiClassImplUtil.getClassUseScope(this); - } - - @Override - public void putInfo(@Nonnull Map info) { - PsiClassImpl.putInfo(this, info); - } - - @Override - @Nonnull - public PsiRecordComponent[] getRecordComponents() { - PsiRecordHeader header = getRecordHeader(); - return header == null ? PsiRecordComponent.EMPTY_ARRAY : header.getRecordComponents(); - } - - @Override - public - @Nullable - PsiRecordHeader getRecordHeader() { - PsiRecordHeaderStub headerStub = getStub().findChildStubByType(JavaStubElementTypes.RECORD_HEADER); - return headerStub == null ? null : headerStub.getPsi(); - } + + try { + PsiClass aClass = getSourceMirrorClass(); + if (aClass != null) { + return aClass.getNavigationElement(); + } + + if ("package-info".equals(getName())) { + PsiElement parent = getParent(); + if (parent instanceof ClsFileImpl) { + PsiElement sourceFile = parent.getNavigationElement(); + if (sourceFile instanceof PsiJavaFile) { + return sourceFile; + } + } + } + } + catch (IndexNotReadyException ignore) { + } + + return this; + } + + @Override + public ItemPresentation getPresentation() { + return ItemPresentationProvider.getItemPresentation(this); + } + + @Override + public boolean isEquivalentTo(final PsiElement another) { + return PsiClassImplUtil.isClassEquivalentTo(this, another); + } + + @Override + @Nonnull + public SearchScope getUseScope() { + return PsiClassImplUtil.getClassUseScope(this); + } + + @Override + public void putInfo(@Nonnull Map info) { + PsiClassImpl.putInfo(this, info); + } + + @Override + @Nonnull + public PsiRecordComponent[] getRecordComponents() { + PsiRecordHeader header = getRecordHeader(); + return header == null ? PsiRecordComponent.EMPTY_ARRAY : header.getRecordComponents(); + } + + @Override + public + @Nullable + PsiRecordHeader getRecordHeader() { + PsiRecordHeaderStub headerStub = getStub().findChildStubByType(JavaStubElementTypes.RECORD_HEADER); + return headerStub == null ? null : headerStub.getPsi(); + } } \ No newline at end of file diff --git a/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/compiled/ClsCustomNavigationPolicy.java b/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/compiled/ClsCustomNavigationPolicy.java index f604322e7d..af0037ae23 100644 --- a/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/compiled/ClsCustomNavigationPolicy.java +++ b/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/compiled/ClsCustomNavigationPolicy.java @@ -11,25 +11,25 @@ @ExtensionAPI(ComponentScope.APPLICATION) public interface ClsCustomNavigationPolicy { - ExtensionPointName EP_NAME = ExtensionPointName.create(ClsCustomNavigationPolicy.class); + ExtensionPointName EP_NAME = ExtensionPointName.create(ClsCustomNavigationPolicy.class); - @Nullable - default PsiElement getNavigationElement(@SuppressWarnings("unused") @Nonnull ClsFileImpl clsFile) { - return null; - } + @Nullable + default PsiElement getNavigationElement(@SuppressWarnings("unused") @Nonnull ClsFileImpl clsFile) { + return null; + } - @Nullable - default PsiElement getNavigationElement(@SuppressWarnings("unused") @Nonnull ClsClassImpl clsClass) { - return null; - } + @Nullable + default PsiElement getNavigationElement(@SuppressWarnings("unused") @Nonnull ClsClassImpl clsClass) { + return null; + } - @Nullable - default PsiElement getNavigationElement(@SuppressWarnings("unused") @Nonnull ClsMethodImpl clsMethod) { - return null; - } + @Nullable + default PsiElement getNavigationElement(@SuppressWarnings("unused") @Nonnull ClsMethodImpl clsMethod) { + return null; + } - @Nullable - default PsiElement getNavigationElement(@SuppressWarnings("unused") @Nonnull ClsFieldImpl clsField) { - return null; - } + @Nullable + default PsiElement getNavigationElement(@SuppressWarnings("unused") @Nonnull ClsFieldImpl clsField) { + return null; + } } \ No newline at end of file diff --git a/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/compiled/ClsFieldImpl.java b/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/compiled/ClsFieldImpl.java index 8d0e3c52b4..e7b38a2b84 100644 --- a/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/compiled/ClsFieldImpl.java +++ b/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/compiled/ClsFieldImpl.java @@ -37,6 +37,7 @@ import consulo.util.lang.lazy.LazyValue; import jakarta.annotation.Nonnull; + import java.util.HashSet; import java.util.Set; import java.util.function.Supplier; @@ -44,208 +45,213 @@ import static consulo.util.lang.ObjectUtil.assertNotNull; public class ClsFieldImpl extends ClsMemberImpl implements PsiField, PsiVariableEx, ClsModifierListOwner { - private final Supplier myTypeElement; - private final Supplier myInitializer; - - public ClsFieldImpl(@Nonnull PsiFieldStub stub) { - super(stub); - myTypeElement = LazyValue.atomicNotNull(() -> { - PsiFieldStub s = getStub(); - String typeText = TypeInfo.createTypeText(s.getType()); - assert typeText != null : s; - return new ClsTypeElementImpl(ClsFieldImpl.this, typeText, ClsTypeElementImpl.VARIANCE_NONE); - }); - - myInitializer = LazyValue.nullable(() -> { - String initializerText = getStub().getInitializerText(); - return initializerText != null && !Comparing.equal(PsiFieldStub.INITIALIZER_TOO_LONG, initializerText) ? ClsParsingUtil.createExpressionFromText(initializerText, getManager(), - ClsFieldImpl.this) : null; - }); - } - - @Override - @Nonnull - public PsiElement[] getChildren() { - return getChildren(getDocComment(), getModifierList(), getTypeElement(), getNameIdentifier()); - } - - @Override - public PsiClass getContainingClass() { - return (PsiClass) getParent(); - } - - @Override - @Nonnull - public PsiType getType() { - return assertNotNull(getTypeElement()).getType(); - } - - @Override - public PsiTypeElement getTypeElement() { - return myTypeElement.get(); - } - - @Override - public PsiModifierList getModifierList() { - return assertNotNull(getStub().findChildStubByType(JavaStubElementTypes.MODIFIER_LIST)).getPsi(); - } - - @Override - public boolean hasModifierProperty(@Nonnull String name) { - return assertNotNull(getModifierList()).hasModifierProperty(name); - } - - @Override - public PsiExpression getInitializer() { - return myInitializer.get(); - } - - @Override - public boolean hasInitializer() { - return getInitializer() != null; - } - - @Override - public Object computeConstantValue() { - return computeConstantValue(new HashSet<>()); - } - - @Override - public Object computeConstantValue(Set visitedVars) { - if (!hasModifierProperty(PsiModifier.FINAL)) { - return null; - } - - PsiExpression initializer = getInitializer(); - if (initializer == null) { - return null; - } - - PsiClass containingClass = getContainingClass(); - if (containingClass != null) { - String qName = containingClass.getQualifiedName(); - if ("java.lang.Float".equals(qName)) { - String name = getName(); - if ("POSITIVE_INFINITY".equals(name)) { - return Float.POSITIVE_INFINITY; + private final Supplier myTypeElement; + private final Supplier myInitializer; + + public ClsFieldImpl(@Nonnull PsiFieldStub stub) { + super(stub); + myTypeElement = LazyValue.atomicNotNull(() -> { + PsiFieldStub s = getStub(); + String typeText = TypeInfo.createTypeText(s.getType()); + assert typeText != null : s; + return new ClsTypeElementImpl(ClsFieldImpl.this, typeText, ClsTypeElementImpl.VARIANCE_NONE); + }); + + myInitializer = LazyValue.nullable(() -> { + String initializerText = getStub().getInitializerText(); + return initializerText != null && !Comparing.equal(PsiFieldStub.INITIALIZER_TOO_LONG, initializerText) + ? ClsParsingUtil.createExpressionFromText(initializerText, getManager(), ClsFieldImpl.this) + : null; + }); + } + + @Override + @Nonnull + public PsiElement[] getChildren() { + return getChildren(getDocComment(), getModifierList(), getTypeElement(), getNameIdentifier()); + } + + @Override + public PsiClass getContainingClass() { + return (PsiClass)getParent(); + } + + @Override + @Nonnull + public PsiType getType() { + return assertNotNull(getTypeElement()).getType(); + } + + @Override + public PsiTypeElement getTypeElement() { + return myTypeElement.get(); + } + + @Override + public PsiModifierList getModifierList() { + return assertNotNull(getStub().findChildStubByType(JavaStubElementTypes.MODIFIER_LIST)).getPsi(); + } + + @Override + public boolean hasModifierProperty(@Nonnull String name) { + return assertNotNull(getModifierList()).hasModifierProperty(name); + } + + @Override + public PsiExpression getInitializer() { + return myInitializer.get(); + } + + @Override + public boolean hasInitializer() { + return getInitializer() != null; + } + + @Override + public Object computeConstantValue() { + return computeConstantValue(new HashSet<>()); + } + + @Override + public Object computeConstantValue(Set visitedVars) { + if (!hasModifierProperty(PsiModifier.FINAL)) { + return null; + } + + PsiExpression initializer = getInitializer(); + if (initializer == null) { + return null; } - if ("NEGATIVE_INFINITY".equals(name)) { - return Float.NEGATIVE_INFINITY; + + PsiClass containingClass = getContainingClass(); + if (containingClass != null) { + String qName = containingClass.getQualifiedName(); + if ("java.lang.Float".equals(qName)) { + String name = getName(); + if ("POSITIVE_INFINITY".equals(name)) { + return Float.POSITIVE_INFINITY; + } + if ("NEGATIVE_INFINITY".equals(name)) { + return Float.NEGATIVE_INFINITY; + } + if ("NaN".equals(name)) { + return Float.NaN; + } + } + else if ("java.lang.Double".equals(qName)) { + String name = getName(); + if ("POSITIVE_INFINITY".equals(name)) { + return Double.POSITIVE_INFINITY; + } + if ("NEGATIVE_INFINITY".equals(name)) { + return Double.NEGATIVE_INFINITY; + } + if ("NaN".equals(name)) { + return Double.NaN; + } + } + } + + return PsiConstantEvaluationHelperImpl.computeCastTo(initializer, getType(), visitedVars); + } + + @Override + public boolean isDeprecated() { + return getStub().isDeprecated() || PsiImplUtil.isDeprecatedByAnnotation(this); + } + + @Override + public void normalizeDeclaration() throws IncorrectOperationException { + } + + @Override + public void appendMirrorText(int indentLevel, @Nonnull StringBuilder buffer) { + appendText(getDocComment(), indentLevel, buffer, NEXT_LINE); + appendText(getModifierList(), indentLevel, buffer, ""); + appendText(getTypeElement(), indentLevel, buffer, " "); + appendText(getNameIdentifier(), indentLevel, buffer); + + PsiExpression initializer = getInitializer(); + if (initializer != null) { + buffer.append(" = "); + buffer.append(initializer.getText()); } - if ("NaN".equals(name)) { - return Float.NaN; + + buffer.append(';'); + } + + @Override + public void setMirror(@Nonnull TreeElement element) throws InvalidMirrorException { + setMirrorCheckingType(element, null); + + PsiField mirror = SourceTreeToPsiMap.treeToPsiNotNull(element); + setMirrorIfPresent(getDocComment(), mirror.getDocComment()); + setMirror(getModifierList(), mirror.getModifierList()); + setMirror(getTypeElement(), mirror.getTypeElement()); + setMirror(getNameIdentifier(), mirror.getNameIdentifier()); + } + + @Override + public void accept(@Nonnull PsiElementVisitor visitor) { + if (visitor instanceof JavaElementVisitor) { + ((JavaElementVisitor)visitor).visitField(this); } - } else if ("java.lang.Double".equals(qName)) { - String name = getName(); - if ("POSITIVE_INFINITY".equals(name)) { - return Double.POSITIVE_INFINITY; + else { + visitor.visitElement(this); } - if ("NEGATIVE_INFINITY".equals(name)) { - return Double.NEGATIVE_INFINITY; + } + + @Override + @Nonnull + @SuppressWarnings({ + "Duplicates", + "deprecation" + }) + public PsiElement getNavigationElement() { + for (ClsCustomNavigationPolicy customNavigationPolicy : Extensions.getExtensions(ClsCustomNavigationPolicy.EP_NAME)) { + try { + PsiElement navigationElement = customNavigationPolicy.getNavigationElement(this); + if (navigationElement != null) { + return navigationElement; + } + } + catch (IndexNotReadyException ignore) { + } } - if ("NaN".equals(name)) { - return Double.NaN; + + try { + PsiClass sourceClassMirror = ((ClsClassImpl)getParent()).getSourceMirrorClass(); + PsiElement sourceFieldMirror = sourceClassMirror != null ? sourceClassMirror.findFieldByName(getName(), false) : null; + return sourceFieldMirror != null ? sourceFieldMirror.getNavigationElement() : this; } - } - } - - return PsiConstantEvaluationHelperImpl.computeCastTo(initializer, getType(), visitedVars); - } - - @Override - public boolean isDeprecated() { - return getStub().isDeprecated() || PsiImplUtil.isDeprecatedByAnnotation(this); - } - - @Override - public void normalizeDeclaration() throws IncorrectOperationException { - } - - @Override - public void appendMirrorText(int indentLevel, @Nonnull StringBuilder buffer) { - appendText(getDocComment(), indentLevel, buffer, NEXT_LINE); - appendText(getModifierList(), indentLevel, buffer, ""); - appendText(getTypeElement(), indentLevel, buffer, " "); - appendText(getNameIdentifier(), indentLevel, buffer); - - PsiExpression initializer = getInitializer(); - if (initializer != null) { - buffer.append(" = "); - buffer.append(initializer.getText()); - } - - buffer.append(';'); - } - - @Override - public void setMirror(@Nonnull TreeElement element) throws InvalidMirrorException { - setMirrorCheckingType(element, null); - - PsiField mirror = SourceTreeToPsiMap.treeToPsiNotNull(element); - setMirrorIfPresent(getDocComment(), mirror.getDocComment()); - setMirror(getModifierList(), mirror.getModifierList()); - setMirror(getTypeElement(), mirror.getTypeElement()); - setMirror(getNameIdentifier(), mirror.getNameIdentifier()); - } - - @Override - public void accept(@Nonnull PsiElementVisitor visitor) { - if (visitor instanceof JavaElementVisitor) { - ((JavaElementVisitor) visitor).visitField(this); - } else { - visitor.visitElement(this); - } - } - - @Override - @Nonnull - @SuppressWarnings({ - "Duplicates", - "deprecation" - }) - public PsiElement getNavigationElement() { - for (ClsCustomNavigationPolicy customNavigationPolicy : Extensions.getExtensions(ClsCustomNavigationPolicy.EP_NAME)) { - try { - PsiElement navigationElement = customNavigationPolicy.getNavigationElement(this); - if (navigationElement != null) { - return navigationElement; + catch (IndexNotReadyException e) { + return this; } - } catch (IndexNotReadyException ignore) { - } - } - - try { - PsiClass sourceClassMirror = ((ClsClassImpl) getParent()).getSourceMirrorClass(); - PsiElement sourceFieldMirror = sourceClassMirror != null ? sourceClassMirror.findFieldByName(getName(), false) : null; - return sourceFieldMirror != null ? sourceFieldMirror.getNavigationElement() : this; - } catch (IndexNotReadyException e) { - return this; - } - } - - @Override - public ItemPresentation getPresentation() { - return ItemPresentationProvider.getItemPresentation(this); - } - - @Override - public void setInitializer(PsiExpression initializer) throws IncorrectOperationException { - throw new IncorrectOperationException(); - } - - @Override - public boolean isEquivalentTo(final PsiElement another) { - return PsiClassImplUtil.isFieldEquivalentTo(this, another); - } - - @Override - @Nonnull - public SearchScope getUseScope() { - return PsiImplUtil.getMemberUseScope(this); - } - - @Override - public String toString() { - return "PsiField:" + getName(); - } + } + + @Override + public ItemPresentation getPresentation() { + return ItemPresentationProvider.getItemPresentation(this); + } + + @Override + public void setInitializer(PsiExpression initializer) throws IncorrectOperationException { + throw new IncorrectOperationException(); + } + + @Override + public boolean isEquivalentTo(final PsiElement another) { + return PsiClassImplUtil.isFieldEquivalentTo(this, another); + } + + @Override + @Nonnull + public SearchScope getUseScope() { + return PsiImplUtil.getMemberUseScope(this); + } + + @Override + public String toString() { + return "PsiField:" + getName(); + } } \ No newline at end of file diff --git a/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/compiled/ClsFileImpl.java b/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/compiled/ClsFileImpl.java index 6590ee83a7..040a9a9f8c 100644 --- a/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/compiled/ClsFileImpl.java +++ b/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/compiled/ClsFileImpl.java @@ -71,617 +71,641 @@ import java.util.Set; public class ClsFileImpl extends PsiBinaryFileImpl - implements PsiJavaFile, PsiFileWithStubSupport, PsiFileEx, Queryable, PsiClassOwnerEx, PsiCompiledFile { - private static final Logger LOG = Logger.getInstance(ClsFileImpl.class); - - private static final String BANNER = "\n" + - " // Consulo API Decompiler stub source generated from a class file\n" + - " // Implementation of methods is not available\n" + - "\n"; - - private static final Key CLS_DOCUMENT_LINK_KEY = Key.create("cls.document.link"); - - /** - * NOTE: you absolutely MUST NOT hold PsiLock under the mirror lock - */ - private final Object myMirrorLock = new Object(); - private final Object myStubLock = new Object(); - - private final boolean myIsForDecompiling; - private volatile SoftReference myStub; - private volatile Reference myMirrorFileElement; - private volatile ClsPackageStatementImpl myPackageStatement; - - public ClsFileImpl(@Nonnull FileViewProvider viewProvider) { - this(viewProvider, false); - } - - private ClsFileImpl(@Nonnull FileViewProvider viewProvider, boolean forDecompiling) { - super(viewProvider.getManager(), viewProvider); - myIsForDecompiling = forDecompiling; - //noinspection ResultOfMethodCallIgnored - JavaElementType.CLASS.getIndex(); // initialize Java stubs - } - - @Override - public PsiFile getContainingFile() { - if (!isValid()) { - throw new PsiInvalidElementAccessException(this); - } - return this; - } - - @Override - public boolean isValid() { - return super.isValid() && (myIsForDecompiling || getVirtualFile().isValid()); - } - - boolean isForDecompiling() { - return myIsForDecompiling; - } - - @RequiredReadAction - @Override - @Nonnull - public Language getLanguage() { - return JavaLanguage.INSTANCE; - } - - @RequiredReadAction - @Override - @Nonnull - public PsiElement[] getChildren() { - PsiJavaModule module = getModuleDeclaration(); - return module != null ? new PsiElement[]{module} : getClasses(); - } - - @Override - @Nonnull - public PsiClass[] getClasses() { - return getStub().getClasses(); - } - - @Override - public PsiPackageStatement getPackageStatement() { - ClsPackageStatementImpl statement = myPackageStatement; - if (statement == null) { - statement = ClsPackageStatementImpl.NULL_PACKAGE; - PsiClassHolderFileStub stub = getStub(); - if (!(stub instanceof PsiJavaFileStub) || stub.findChildStubByType(JavaStubElementTypes.MODULE) == null) { - String packageName = findPackageName(stub); - if (packageName != null) { - statement = new ClsPackageStatementImpl(this, packageName); - } - } - myPackageStatement = statement; - } - return statement != ClsPackageStatementImpl.NULL_PACKAGE ? statement : null; - } - - private static String findPackageName(PsiClassHolderFileStub stub) { - String packageName = null; - - if (stub instanceof PsiJavaFileStub) { - packageName = ((PsiJavaFileStub) stub).getPackageName(); - } else { - PsiClass[] psiClasses = stub.getClasses(); - if (psiClasses.length > 0) { - String className = psiClasses[0].getQualifiedName(); - if (className != null) { - int index = className.lastIndexOf('.'); - if (index >= 0) { - packageName = className.substring(0, index); - } + implements PsiJavaFile, PsiFileWithStubSupport, PsiFileEx, Queryable, PsiClassOwnerEx, PsiCompiledFile { + private static final Logger LOG = Logger.getInstance(ClsFileImpl.class); + + private static final String BANNER = "\n" + + " // Consulo API Decompiler stub source generated from a class file\n" + + " // Implementation of methods is not available\n" + + "\n"; + + private static final Key CLS_DOCUMENT_LINK_KEY = Key.create("cls.document.link"); + + /** + * NOTE: you absolutely MUST NOT hold PsiLock under the mirror lock + */ + private final Object myMirrorLock = new Object(); + private final Object myStubLock = new Object(); + + private final boolean myIsForDecompiling; + private volatile SoftReference myStub; + private volatile Reference myMirrorFileElement; + private volatile ClsPackageStatementImpl myPackageStatement; + + public ClsFileImpl(@Nonnull FileViewProvider viewProvider) { + this(viewProvider, false); + } + + private ClsFileImpl(@Nonnull FileViewProvider viewProvider, boolean forDecompiling) { + super(viewProvider.getManager(), viewProvider); + myIsForDecompiling = forDecompiling; + //noinspection ResultOfMethodCallIgnored + JavaElementType.CLASS.getIndex(); // initialize Java stubs + } + + @Override + public PsiFile getContainingFile() { + if (!isValid()) { + throw new PsiInvalidElementAccessException(this); } - } - } - - return !StringUtil.isEmpty(packageName) ? packageName : null; - } - - @Override - @Nonnull - public String getPackageName() { - PsiPackageStatement statement = getPackageStatement(); - return statement == null ? "" : statement.getPackageName(); - } - - @Override - public void setPackageName(final String packageName) throws IncorrectOperationException { - throw new IncorrectOperationException("Cannot set package name for compiled files"); - } - - @RequiredReadAction - @Override - public PsiImportList getImportList() { - return null; - } - - @Override - public boolean importClass(@Nonnull PsiClass aClass) { - throw new UnsupportedOperationException("Cannot add imports to compiled classes"); - } - - @Override - @Nonnull - public PsiElement[] getOnDemandImports(boolean includeImplicit, boolean checkIncludes) { - return PsiJavaCodeReferenceElement.EMPTY_ARRAY; - } - - @Override - @Nonnull - public PsiClass[] getSingleClassImports(boolean checkIncludes) { - return PsiClass.EMPTY_ARRAY; - } - - @Override - @Nonnull - public String[] getImplicitlyImportedPackages() { - return ArrayUtil.EMPTY_STRING_ARRAY; - } - - @Override - public Set getClassNames() { - return Collections.singleton(getVirtualFile().getNameWithoutExtension()); - } - - @Override - @Nonnull - public PsiJavaCodeReferenceElement[] getImplicitlyImportedPackageReferences() { - return PsiJavaCodeReferenceElement.EMPTY_ARRAY; - } - - @Override - public PsiJavaCodeReferenceElement findImportReferenceTo(PsiClass aClass) { - return null; - } - - @Override - @Nonnull - public LanguageLevel getLanguageLevel() { - PsiClassHolderFileStub stub = getStub(); - if (stub instanceof PsiJavaFileStub) { - LanguageLevel level = ((PsiJavaFileStub) stub).getLanguageLevel(); - if (level != null) { - return level; - } - } - return LanguageLevel.HIGHEST; - } - - @RequiredReadAction - @Nullable - @Override - public PsiJavaModule getModuleDeclaration() { - PsiClassHolderFileStub stub = getStub(); - return stub instanceof PsiJavaFileStub ? ((PsiJavaFileStub) stub).getModule() : null; - } - - @RequiredWriteAction - @Override - public PsiElement setName(@Nonnull String name) throws IncorrectOperationException { - throw ClsElementImpl.cannotModifyException(this); - } - - @Override - public void checkSetName(String name) throws IncorrectOperationException { - throw ClsElementImpl.cannotModifyException(this); - } - - /** - * Shouldn't be called from outside or overridden - */ - @RequiredReadAction - @Deprecated - public void appendMirrorText(@SuppressWarnings("unused") int indentLevel, @Nonnull StringBuilder buffer) { - appendMirrorText(buffer); - } - - @RequiredReadAction - private void appendMirrorText(@Nonnull StringBuilder buffer) { - buffer.append(BANNER); - - PsiJavaModule module = getModuleDeclaration(); - if (module != null) { - ClsElementImpl.appendText(module, 0, buffer); - } else { - ClsElementImpl.appendText(getPackageStatement(), 0, buffer, "\n\n"); - - PsiClass[] classes = getClasses(); - if (classes.length > 0) { - ClsElementImpl.appendText(classes[0], 0, buffer); - } - } - } - - /** - * Shouldn't be called from outside or overridden - */ - @RequiredReadAction - @Deprecated - public void setMirror(@Nonnull TreeElement element) throws InvalidMirrorException { - setFileMirror(element); - } - - @RequiredReadAction - private void setFileMirror(@Nonnull TreeElement element) { - PsiElement mirrorElement = SourceTreeToPsiMap.treeToPsiNotNull(element); - if (!(mirrorElement instanceof PsiJavaFile)) { - throw new InvalidMirrorException("Unexpected mirror file: " + mirrorElement); - } - - PsiJavaFile mirrorFile = (PsiJavaFile) mirrorElement; - PsiJavaModule module = getModuleDeclaration(); - if (module != null) { - ClsElementImpl.setMirror(module, mirrorFile.getModuleDeclaration()); - } else { - ClsElementImpl.setMirrorIfPresent(getPackageStatement(), mirrorFile.getPackageStatement()); - ClsElementImpl.setMirrors(getClasses(), mirrorFile.getClasses()); - } - } - - @Override - @Nonnull - public PsiElement getNavigationElement() { - for (ClsCustomNavigationPolicy navigationPolicy : ClsCustomNavigationPolicy.EP_NAME.getExtensionList()) { - try { - PsiElement navigationElement = navigationPolicy instanceof ClsCustomNavigationPolicyEx customNavigationPolicy - ? customNavigationPolicy.getFileNavigationElement(this) : navigationPolicy.getNavigationElement(this); - if (navigationElement != null) { - return navigationElement; + return this; + } + + @Override + public boolean isValid() { + return super.isValid() && (myIsForDecompiling || getVirtualFile().isValid()); + } + + boolean isForDecompiling() { + return myIsForDecompiling; + } + + @RequiredReadAction + @Override + @Nonnull + public Language getLanguage() { + return JavaLanguage.INSTANCE; + } + + @RequiredReadAction + @Override + @Nonnull + public PsiElement[] getChildren() { + PsiJavaModule module = getModuleDeclaration(); + return module != null ? new PsiElement[]{module} : getClasses(); + } + + @Override + @Nonnull + public PsiClass[] getClasses() { + return getStub().getClasses(); + } + + @Override + public PsiPackageStatement getPackageStatement() { + ClsPackageStatementImpl statement = myPackageStatement; + if (statement == null) { + statement = ClsPackageStatementImpl.NULL_PACKAGE; + PsiClassHolderFileStub stub = getStub(); + if (!(stub instanceof PsiJavaFileStub) || stub.findChildStubByType(JavaStubElementTypes.MODULE) == null) { + String packageName = findPackageName(stub); + if (packageName != null) { + statement = new ClsPackageStatementImpl(this, packageName); + } + } + myPackageStatement = statement; } - } catch (IndexNotReadyException ignore) { - } - } - - return LanguageCachedValueUtil.getCachedValue(this, () -> { - PsiElement target = JavaPsiImplementationHelper.getInstance(getProject()).getClsFileNavigationElement(this); - ModificationTracker tracker = FileIndexFacade.getInstance(getProject()).getRootModificationTracker(); - return CachedValueProvider.Result.create(target, this, target.getContainingFile(), tracker); - }); - } - - @Override - @Nonnull - @RequiredReadAction - public PsiElement getMirror() { - TreeElement mirrorTreeElement = SoftReference.dereference(myMirrorFileElement); - if (mirrorTreeElement == null) { - synchronized (myMirrorLock) { - mirrorTreeElement = SoftReference.dereference(myMirrorFileElement); - if (mirrorTreeElement == null) { - VirtualFile file = getVirtualFile(); - PsiClass[] classes = getClasses(); - String fileName = (classes.length > 0 ? classes[0].getName() : file.getNameWithoutExtension()) + - JavaFileType.DOT_DEFAULT_EXTENSION; - - final Document document = FileDocumentManager.getInstance().getDocument(file); - assert document != null : file.getUrl(); - - CharSequence mirrorText = document.getImmutableCharSequence(); - boolean internalDecompiler = StringUtil.startsWith(mirrorText, BANNER); - PsiFileFactory factory = PsiFileFactory.getInstance(getManager().getProject()); - PsiFile mirror = factory.createFileFromText(fileName, JavaLanguage.INSTANCE, mirrorText, false, false); - mirror.putUserData(PsiUtil.FILE_LANGUAGE_LEVEL_KEY, getLanguageLevel()); - - mirrorTreeElement = SourceTreeToPsiMap.psiToTreeNotNull(mirror); - try { - final TreeElement finalMirrorTreeElement = mirrorTreeElement; - ProgressManager.getInstance().executeNonCancelableSection(() -> { - setFileMirror(finalMirrorTreeElement); - putUserData(CLS_DOCUMENT_LINK_KEY, document); - }); - } catch (InvalidMirrorException e) { - //noinspection ThrowableResultOfMethodCallIgnored - LOG.error(file.getUrl(), internalDecompiler ? e : wrapException(e, file)); - } - - ((PsiFileImpl) mirror).setOriginalFile(this); - myMirrorFileElement = new SoftReference<>(mirrorTreeElement); + return statement != ClsPackageStatementImpl.NULL_PACKAGE ? statement : null; + } + + private static String findPackageName(PsiClassHolderFileStub stub) { + String packageName = null; + + if (stub instanceof PsiJavaFileStub) { + packageName = ((PsiJavaFileStub)stub).getPackageName(); } - } - } - return mirrorTreeElement.getPsi(); - } - - @RequiredReadAction - @Override - public String getText() { - VirtualFile file = getVirtualFile(); - Document document = FileDocumentManager.getInstance().getDocument(file); - assert document != null : file.getUrl(); - return document.getText(); - } - - @RequiredReadAction - @Override - public int getTextLength() { - VirtualFile file = getVirtualFile(); - Document document = FileDocumentManager.getInstance().getDocument(file); - assert document != null : file.getUrl(); - return document.getTextLength(); - } - - private static Exception wrapException(InvalidMirrorException e, VirtualFile file) { - ClassFileDecompiler decompiler = ClassFileDecompilers.find(file); - if (decompiler instanceof ClassFileDecompiler.Light) { - PluginId pluginId = PluginManager.getPluginId(decompiler.getClass()); - if (pluginId != null) { - return new PluginException(e, pluginId); - } - } - - return e; - } - - @RequiredReadAction - @Override - public PsiFile getDecompiledPsiFile() { - return (PsiFile) getMirror(); - } - - @Override - public void accept(@Nonnull PsiElementVisitor visitor) { - if (visitor instanceof JavaElementVisitor) { - ((JavaElementVisitor) visitor).visitJavaFile(this); - } else { - visitor.visitFile(this); - } - } - - @RequiredReadAction - @Override - @NonNls - public String toString() { - return "PsiFile:" + getName(); - } - - @RequiredReadAction - @Override - public final TextRange getTextRange() { - return TextRange.create(0, getTextLength()); - } - - @RequiredReadAction - @Override - public final int getStartOffsetInParent() { - return 0; - } - - @RequiredReadAction - @Override - public final PsiElement findElementAt(int offset) { - return getMirror().findElementAt(offset); - } - - @RequiredReadAction - @Override - public PsiReference findReferenceAt(int offset) { - return getMirror().findReferenceAt(offset); - } - - @Override - public final int getTextOffset() { - return 0; - } - - @RequiredReadAction - @Override - @Nonnull - public char[] textToCharArray() { - return getMirror().textToCharArray(); - } - - @RequiredReadAction - @Nonnull - public PsiClassHolderFileStub getStub() { - return (PsiClassHolderFileStub) getStubTree().getRoot(); - } - - @Override - public boolean processDeclarations( - @Nonnull PsiScopeProcessor processor, - @Nonnull ResolveState state, - PsiElement lastParent, - @Nonnull PsiElement place - ) { - processor.handleEvent(PsiScopeProcessor.Event.SET_DECLARATION_HOLDER, this); - final ElementClassHint classHint = processor.getHint(ElementClassHint.KEY); - if (classHint == null || classHint.shouldProcess(ElementClassHint.DeclarationKind.CLASS)) { - final PsiClass[] classes = getClasses(); - for (PsiClass aClass : classes) { - if (!processor.execute(aClass, state)) { - return false; + else { + PsiClass[] psiClasses = stub.getClasses(); + if (psiClasses.length > 0) { + String className = psiClasses[0].getQualifiedName(); + if (className != null) { + int index = className.lastIndexOf('.'); + if (index >= 0) { + packageName = className.substring(0, index); + } + } + } } - } + + return !StringUtil.isEmpty(packageName) ? packageName : null; } - return true; - } - @RequiredReadAction - @Override - @Nonnull - public StubTree getStubTree() { - getApplication().assertReadAccessAllowed(); + @Override + @Nonnull + public String getPackageName() { + PsiPackageStatement statement = getPackageStatement(); + return statement == null ? "" : statement.getPackageName(); + } - StubTree stubTree = SoftReference.dereference(myStub); - if (stubTree != null) { - return stubTree; + @Override + public void setPackageName(final String packageName) throws IncorrectOperationException { + throw new IncorrectOperationException("Cannot set package name for compiled files"); } - // build newStub out of lock to avoid deadlock - StubTree newStubTree = (StubTree) StubTreeLoader.getInstance().readOrBuild(getProject(), getVirtualFile(), this); - if (newStubTree == null) { - if (LOG.isDebugEnabled()) { - LOG.debug("No stub for class file in index: " + getVirtualFile().getPresentableUrl()); - } - newStubTree = new StubTree(new PsiJavaFileStubImpl("corrupted_class_files", true)); + @RequiredReadAction + @Override + public PsiImportList getImportList() { + return null; } - synchronized (myStubLock) { - stubTree = SoftReference.dereference(myStub); - if (stubTree != null) { - return stubTree; - } + @Override + public boolean importClass(@Nonnull PsiClass aClass) { + throw new UnsupportedOperationException("Cannot add imports to compiled classes"); + } + + @Override + @Nonnull + public PsiElement[] getOnDemandImports(boolean includeImplicit, boolean checkIncludes) { + return PsiJavaCodeReferenceElement.EMPTY_ARRAY; + } + + @Override + @Nonnull + public PsiClass[] getSingleClassImports(boolean checkIncludes) { + return PsiClass.EMPTY_ARRAY; + } - stubTree = newStubTree; + @Override + @Nonnull + public String[] getImplicitlyImportedPackages() { + return ArrayUtil.EMPTY_STRING_ARRAY; + } - @SuppressWarnings("unchecked") PsiFileStubImpl fileStub = (PsiFileStubImpl) stubTree.getRoot(); - fileStub.setPsi(this); + @Override + public Set getClassNames() { + return Collections.singleton(getVirtualFile().getNameWithoutExtension()); + } - myStub = new SoftReference<>(stubTree); + @Override + @Nonnull + public PsiJavaCodeReferenceElement[] getImplicitlyImportedPackageReferences() { + return PsiJavaCodeReferenceElement.EMPTY_ARRAY; } - return stubTree; - } + @Override + public PsiJavaCodeReferenceElement findImportReferenceTo(PsiClass aClass) { + return null; + } - @RequiredReadAction - @Nonnull - @Override - public StubbedSpine getStubbedSpine() { - return getStubTree().getSpine(); - } + @Override + @Nonnull + public LanguageLevel getLanguageLevel() { + PsiClassHolderFileStub stub = getStub(); + if (stub instanceof PsiJavaFileStub) { + LanguageLevel level = ((PsiJavaFileStub)stub).getLanguageLevel(); + if (level != null) { + return level; + } + } + return LanguageLevel.HIGHEST; + } - @Override - public boolean isContentsLoaded() { - return myStub != null; - } + @RequiredReadAction + @Nullable + @Override + public PsiJavaModule getModuleDeclaration() { + PsiClassHolderFileStub stub = getStub(); + return stub instanceof PsiJavaFileStub ? ((PsiJavaFileStub)stub).getModule() : null; + } - @Override - @RequiredWriteAction - public void onContentReload() { - getApplication().assertWriteAccessAllowed(); + @RequiredWriteAction + @Override + public PsiElement setName(@Nonnull String name) throws IncorrectOperationException { + throw ClsElementImpl.cannotModifyException(this); + } - synchronized (myStubLock) { - StubTree stubTree = SoftReference.dereference(myStub); - myStub = null; - if (stubTree != null) { - //noinspection unchecked - ((PsiFileStubImpl) stubTree.getRoot()).clearPsi("cls onContentReload"); - } + @Override + public void checkSetName(String name) throws IncorrectOperationException { + throw ClsElementImpl.cannotModifyException(this); } - synchronized (myMirrorLock) { - putUserData(CLS_DOCUMENT_LINK_KEY, null); - myMirrorFileElement = null; - myPackageStatement = null; + /** + * Shouldn't be called from outside or overridden + */ + @RequiredReadAction + @Deprecated + public void appendMirrorText(@SuppressWarnings("unused") int indentLevel, @Nonnull StringBuilder buffer) { + appendMirrorText(buffer); } - } - @Override - public void putInfo(@Nonnull Map info) { - PsiFileImpl.putInfo(this, info); - } + @RequiredReadAction + private void appendMirrorText(@Nonnull StringBuilder buffer) { + buffer.append(BANNER); + + PsiJavaModule module = getModuleDeclaration(); + if (module != null) { + ClsElementImpl.appendText(module, 0, buffer); + } + else { + ClsElementImpl.appendText(getPackageStatement(), 0, buffer, "\n\n"); - // default decompiler implementation + PsiClass[] classes = getClasses(); + if (classes.length > 0) { + ClsElementImpl.appendText(classes[0], 0, buffer); + } + } + } - @Nonnull - public static CharSequence decompile(@Nonnull VirtualFile file) { - PsiManager manager = PsiManager.getInstance(ProjectManager.getInstance().getDefaultProject()); - final ClsFileImpl clsFile = new ClsFileImpl(new ClassFileViewProvider(manager, file), true); - final StringBuilder buffer = new StringBuilder(); - Application.get().runReadAction(() -> clsFile.appendMirrorText(buffer)); - return buffer; - } + /** + * Shouldn't be called from outside or overridden + */ + @RequiredReadAction + @Deprecated + public void setMirror(@Nonnull TreeElement element) throws InvalidMirrorException { + setFileMirror(element); + } - @Nullable - public static PsiJavaFileStub buildFileStub(@Nonnull VirtualFile file, @Nonnull byte[] bytes) throws ClsFormatException { - try { - if (ClassFileViewProvider.isInnerClass(file, bytes)) { - return null; - } - - ClassReader reader = new ClassReader(bytes); - String className = file.getNameWithoutExtension(); - String internalName = reader.getClassName(); - boolean module = internalName.equals("module-info") && BitUtil.isSet(reader.getAccess(), Opcodes.ACC_MODULE); - JavaSdkVersion jdkVersion = ClsParsingUtil.getJdkVersionByBytecode(reader.readUnsignedShort(6)); - LanguageLevel level = jdkVersion != null ? jdkVersion.getMaxLanguageLevel() : null; - if (level != null && level.isAtLeast(LanguageLevel.JDK_11) && ClsParsingUtil.isPreviewLevel(reader.readUnsignedShort(4))) { - level = ObjectUtil.notNull(level.getPreviewLevel(), LanguageLevel.HIGHEST); - } - - if (module) { - PsiJavaFileStub stub = new PsiJavaFileStubImpl(null, "", level, true); - ModuleStubBuildingVisitor visitor = new ModuleStubBuildingVisitor(stub); - reader.accept(visitor, EMPTY_ATTRIBUTES, ClassReader.SKIP_FRAMES); - if (visitor.getResult() != null) return stub; - } - else { - PsiJavaFileStub stub = new PsiJavaFileStubImpl(null, getPackageName(internalName), level, true); - try { - FileContentPair source = new FileContentPair(file, reader); - StubBuildingVisitor visitor = new StubBuildingVisitor<>(source, STRATEGY, stub, 0, className); - reader.accept(visitor, EMPTY_ATTRIBUTES, ClassReader.SKIP_FRAMES | ClassReader.SKIP_CODE | ClassReader.VISIT_LOCAL_VARIABLES); - if (visitor.getResult() != null) return stub; + @RequiredReadAction + private void setFileMirror(@Nonnull TreeElement element) { + PsiElement mirrorElement = SourceTreeToPsiMap.treeToPsiNotNull(element); + if (!(mirrorElement instanceof PsiJavaFile)) { + throw new InvalidMirrorException("Unexpected mirror file: " + mirrorElement); } - catch (OutOfOrderInnerClassException e) { - if (LOG.isTraceEnabled()) LOG.trace(file.getPath()); + + PsiJavaFile mirrorFile = (PsiJavaFile)mirrorElement; + PsiJavaModule module = getModuleDeclaration(); + if (module != null) { + ClsElementImpl.setMirror(module, mirrorFile.getModuleDeclaration()); + } + else { + ClsElementImpl.setMirrorIfPresent(getPackageStatement(), mirrorFile.getPackageStatement()); + ClsElementImpl.setMirrors(getClasses(), mirrorFile.getClasses()); + } + } + + @Override + @Nonnull + public PsiElement getNavigationElement() { + for (ClsCustomNavigationPolicy navigationPolicy : ClsCustomNavigationPolicy.EP_NAME.getExtensionList()) { + try { + PsiElement navigationElement = navigationPolicy instanceof ClsCustomNavigationPolicyEx customNavigationPolicy + ? customNavigationPolicy.getFileNavigationElement(this) + : navigationPolicy.getNavigationElement(this); + if (navigationElement != null) { + return navigationElement; + } + } + catch (IndexNotReadyException ignore) { + } } - } - return null; + return LanguageCachedValueUtil.getCachedValue(this, () -> { + PsiElement target = JavaPsiImplementationHelper.getInstance(getProject()).getClsFileNavigationElement(this); + ModificationTracker tracker = FileIndexFacade.getInstance(getProject()).getRootModificationTracker(); + return CachedValueProvider.Result.create(target, this, target.getContainingFile(), tracker); + }); + } + + @Override + @Nonnull + @RequiredReadAction + public PsiElement getMirror() { + TreeElement mirrorTreeElement = SoftReference.dereference(myMirrorFileElement); + if (mirrorTreeElement == null) { + synchronized (myMirrorLock) { + mirrorTreeElement = SoftReference.dereference(myMirrorFileElement); + if (mirrorTreeElement == null) { + VirtualFile file = getVirtualFile(); + PsiClass[] classes = getClasses(); + String fileName = (classes.length > 0 ? classes[0].getName() : file.getNameWithoutExtension()) + + JavaFileType.DOT_DEFAULT_EXTENSION; + + final Document document = FileDocumentManager.getInstance().getDocument(file); + assert document != null : file.getUrl(); + + CharSequence mirrorText = document.getImmutableCharSequence(); + boolean internalDecompiler = StringUtil.startsWith(mirrorText, BANNER); + PsiFileFactory factory = PsiFileFactory.getInstance(getManager().getProject()); + PsiFile mirror = factory.createFileFromText(fileName, JavaLanguage.INSTANCE, mirrorText, false, false); + mirror.putUserData(PsiUtil.FILE_LANGUAGE_LEVEL_KEY, getLanguageLevel()); + + mirrorTreeElement = SourceTreeToPsiMap.psiToTreeNotNull(mirror); + try { + final TreeElement finalMirrorTreeElement = mirrorTreeElement; + ProgressManager.getInstance().executeNonCancelableSection(() -> { + setFileMirror(finalMirrorTreeElement); + putUserData(CLS_DOCUMENT_LINK_KEY, document); + }); + } + catch (InvalidMirrorException e) { + //noinspection ThrowableResultOfMethodCallIgnored + LOG.error(file.getUrl(), internalDecompiler ? e : wrapException(e, file)); + } + + ((PsiFileImpl)mirror).setOriginalFile(this); + myMirrorFileElement = new SoftReference<>(mirrorTreeElement); + } + } + } + return mirrorTreeElement.getPsi(); } - catch (ProcessCanceledException e) { - throw e; + + @RequiredReadAction + @Override + public String getText() { + VirtualFile file = getVirtualFile(); + Document document = FileDocumentManager.getInstance().getDocument(file); + assert document != null : file.getUrl(); + return document.getText(); } - catch (Throwable e) { - throw new ClsFormatException(file.getPath() + ": " + e.getMessage(), e); + + @RequiredReadAction + @Override + public int getTextLength() { + VirtualFile file = getVirtualFile(); + Document document = FileDocumentManager.getInstance().getDocument(file); + assert document != null : file.getUrl(); + return document.getTextLength(); } - } - private static String getPackageName(String internalName) { - int p = internalName.lastIndexOf('/'); - return p > 0 ? internalName.substring(0, p).replace('/', '.') : ""; - } + private static Exception wrapException(InvalidMirrorException e, VirtualFile file) { + ClassFileDecompiler decompiler = ClassFileDecompilers.find(file); + if (decompiler instanceof ClassFileDecompiler.Light) { + PluginId pluginId = PluginManager.getPluginId(decompiler.getClass()); + if (pluginId != null) { + return new PluginException(e, pluginId); + } + } - static class FileContentPair extends Pair { - FileContentPair(@Nonnull VirtualFile file, @Nonnull ClassReader content) { - super(file, content); + return e; } - public @Nonnull ClassReader getContent() { - return second; + @RequiredReadAction + @Override + public PsiFile getDecompiledPsiFile() { + return (PsiFile)getMirror(); + } + + @Override + public void accept(@Nonnull PsiElementVisitor visitor) { + if (visitor instanceof JavaElementVisitor) { + ((JavaElementVisitor)visitor).visitJavaFile(this); + } + else { + visitor.visitFile(this); + } } + @RequiredReadAction @Override + @NonNls public String toString() { - return first.toString(); + return "PsiFile:" + getName(); } - } - private static final InnerClassSourceStrategy STRATEGY = new InnerClassSourceStrategy<>() { + @RequiredReadAction @Override - public @Nullable FileContentPair findInnerClass(String innerName, FileContentPair outerClass) { - String baseName = outerClass.first.getNameWithoutExtension(); - VirtualFile dir = outerClass.first.getParent(); - assert dir != null : outerClass; - VirtualFile innerClass = dir.findChild(baseName + '$' + innerName + ".class"); - if (innerClass != null) { - try { - byte[] bytes = innerClass.contentsToByteArray(false); - return new FileContentPair(innerClass, new ClassReader(bytes)); + public final TextRange getTextRange() { + return TextRange.create(0, getTextLength()); + } + + @RequiredReadAction + @Override + public final int getStartOffsetInParent() { + return 0; + } + + @RequiredReadAction + @Override + public final PsiElement findElementAt(int offset) { + return getMirror().findElementAt(offset); + } + + @RequiredReadAction + @Override + public PsiReference findReferenceAt(int offset) { + return getMirror().findReferenceAt(offset); + } + + @Override + public final int getTextOffset() { + return 0; + } + + @RequiredReadAction + @Override + @Nonnull + public char[] textToCharArray() { + return getMirror().textToCharArray(); + } + + @RequiredReadAction + @Nonnull + public PsiClassHolderFileStub getStub() { + return (PsiClassHolderFileStub)getStubTree().getRoot(); + } + + @Override + public boolean processDeclarations( + @Nonnull PsiScopeProcessor processor, + @Nonnull ResolveState state, + PsiElement lastParent, + @Nonnull PsiElement place + ) { + processor.handleEvent(PsiScopeProcessor.Event.SET_DECLARATION_HOLDER, this); + final ElementClassHint classHint = processor.getHint(ElementClassHint.KEY); + if (classHint == null || classHint.shouldProcess(ElementClassHint.DeclarationKind.CLASS)) { + final PsiClass[] classes = getClasses(); + for (PsiClass aClass : classes) { + if (!processor.execute(aClass, state)) { + return false; + } + } + } + return true; + } + + @RequiredReadAction + @Override + @Nonnull + public StubTree getStubTree() { + getApplication().assertReadAccessAllowed(); + + StubTree stubTree = SoftReference.dereference(myStub); + if (stubTree != null) { + return stubTree; + } + + // build newStub out of lock to avoid deadlock + StubTree newStubTree = (StubTree)StubTreeLoader.getInstance().readOrBuild(getProject(), getVirtualFile(), this); + if (newStubTree == null) { + if (LOG.isDebugEnabled()) { + LOG.debug("No stub for class file in index: " + getVirtualFile().getPresentableUrl()); + } + newStubTree = new StubTree(new PsiJavaFileStubImpl("corrupted_class_files", true)); } - catch (IOException ignored) { + + synchronized (myStubLock) { + stubTree = SoftReference.dereference(myStub); + if (stubTree != null) { + return stubTree; + } + + stubTree = newStubTree; + + @SuppressWarnings("unchecked") PsiFileStubImpl fileStub = (PsiFileStubImpl)stubTree.getRoot(); + fileStub.setPsi(this); + + myStub = new SoftReference<>(stubTree); + } + + return stubTree; + } + + @RequiredReadAction + @Nonnull + @Override + public StubbedSpine getStubbedSpine() { + return getStubTree().getSpine(); + } + + @Override + public boolean isContentsLoaded() { + return myStub != null; + } + + @Override + @RequiredWriteAction + public void onContentReload() { + getApplication().assertWriteAccessAllowed(); + + synchronized (myStubLock) { + StubTree stubTree = SoftReference.dereference(myStub); + myStub = null; + if (stubTree != null) { + //noinspection unchecked + ((PsiFileStubImpl)stubTree.getRoot()).clearPsi("cls onContentReload"); + } + } + + synchronized (myMirrorLock) { + putUserData(CLS_DOCUMENT_LINK_KEY, null); + myMirrorFileElement = null; + myPackageStatement = null; } - } - return null; } @Override - public void accept(FileContentPair innerClass, StubBuildingVisitor visitor) { - try { - innerClass.second.accept(visitor, - EMPTY_ATTRIBUTES, - ClassReader.SKIP_FRAMES | ClassReader.SKIP_CODE | ClassReader.VISIT_LOCAL_VARIABLES); - } - catch (Exception e) { // workaround for bug in skipping annotations when a first parameter of inner class is dropped (IDEA-204145) - VirtualFile file = innerClass.first; - if (LOG.isDebugEnabled()) LOG.debug(String.valueOf(file), e); - else LOG.info(file + ": " + e.getMessage()); - } + public void putInfo(@Nonnull Map info) { + PsiFileImpl.putInfo(this, info); + } + + // default decompiler implementation + + @Nonnull + public static CharSequence decompile(@Nonnull VirtualFile file) { + PsiManager manager = PsiManager.getInstance(ProjectManager.getInstance().getDefaultProject()); + final ClsFileImpl clsFile = new ClsFileImpl(new ClassFileViewProvider(manager, file), true); + final StringBuilder buffer = new StringBuilder(); + Application.get().runReadAction(() -> clsFile.appendMirrorText(buffer)); + return buffer; + } + + @Nullable + public static PsiJavaFileStub buildFileStub(@Nonnull VirtualFile file, @Nonnull byte[] bytes) throws ClsFormatException { + try { + if (ClassFileViewProvider.isInnerClass(file, bytes)) { + return null; + } + + ClassReader reader = new ClassReader(bytes); + String className = file.getNameWithoutExtension(); + String internalName = reader.getClassName(); + boolean module = internalName.equals("module-info") && BitUtil.isSet(reader.getAccess(), Opcodes.ACC_MODULE); + JavaSdkVersion jdkVersion = ClsParsingUtil.getJdkVersionByBytecode(reader.readUnsignedShort(6)); + LanguageLevel level = jdkVersion != null ? jdkVersion.getMaxLanguageLevel() : null; + if (level != null && level.isAtLeast(LanguageLevel.JDK_11) && ClsParsingUtil.isPreviewLevel(reader.readUnsignedShort(4))) { + level = ObjectUtil.notNull(level.getPreviewLevel(), LanguageLevel.HIGHEST); + } + + if (module) { + PsiJavaFileStub stub = new PsiJavaFileStubImpl(null, "", level, true); + ModuleStubBuildingVisitor visitor = new ModuleStubBuildingVisitor(stub); + reader.accept(visitor, EMPTY_ATTRIBUTES, ClassReader.SKIP_FRAMES); + if (visitor.getResult() != null) { + return stub; + } + } + else { + PsiJavaFileStub stub = new PsiJavaFileStubImpl(null, getPackageName(internalName), level, true); + try { + FileContentPair source = new FileContentPair(file, reader); + StubBuildingVisitor visitor = new StubBuildingVisitor<>(source, STRATEGY, stub, 0, className); + reader.accept( + visitor, + EMPTY_ATTRIBUTES, + ClassReader.SKIP_FRAMES | ClassReader.SKIP_CODE | ClassReader.VISIT_LOCAL_VARIABLES + ); + if (visitor.getResult() != null) { + return stub; + } + } + catch (OutOfOrderInnerClassException e) { + if (LOG.isTraceEnabled()) { + LOG.trace(file.getPath()); + } + } + } + + return null; + } + catch (ProcessCanceledException e) { + throw e; + } + catch (Throwable e) { + throw new ClsFormatException(file.getPath() + ": " + e.getMessage(), e); + } + } + + private static String getPackageName(String internalName) { + int p = internalName.lastIndexOf('/'); + return p > 0 ? internalName.substring(0, p).replace('/', '.') : ""; + } + + static class FileContentPair extends Pair { + FileContentPair(@Nonnull VirtualFile file, @Nonnull ClassReader content) { + super(file, content); + } + + public @Nonnull ClassReader getContent() { + return second; + } + + @Override + public String toString() { + return first.toString(); + } } - }; - public static final Attribute[] EMPTY_ATTRIBUTES = new Attribute[0]; + private static final InnerClassSourceStrategy STRATEGY = new InnerClassSourceStrategy<>() { + @Override + public @Nullable FileContentPair findInnerClass(String innerName, FileContentPair outerClass) { + String baseName = outerClass.first.getNameWithoutExtension(); + VirtualFile dir = outerClass.first.getParent(); + assert dir != null : outerClass; + VirtualFile innerClass = dir.findChild(baseName + '$' + innerName + ".class"); + if (innerClass != null) { + try { + byte[] bytes = innerClass.contentsToByteArray(false); + return new FileContentPair(innerClass, new ClassReader(bytes)); + } + catch (IOException ignored) { + } + } + return null; + } + + @Override + public void accept(FileContentPair innerClass, StubBuildingVisitor visitor) { + try { + innerClass.second.accept( + visitor, + EMPTY_ATTRIBUTES, + ClassReader.SKIP_FRAMES | ClassReader.SKIP_CODE | ClassReader.VISIT_LOCAL_VARIABLES + ); + } + catch ( + Exception e) { // workaround for bug in skipping annotations when a first parameter of inner class is dropped (IDEA-204145) + VirtualFile file = innerClass.first; + if (LOG.isDebugEnabled()) { + LOG.debug(String.valueOf(file), e); + } + else { + LOG.info(file + ": " + e.getMessage()); + } + } + } + }; + + public static final Attribute[] EMPTY_ATTRIBUTES = new Attribute[0]; } \ No newline at end of file diff --git a/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/compiled/ClsMethodImpl.java b/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/compiled/ClsMethodImpl.java index 3205ed8587..a26e151dd5 100644 --- a/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/compiled/ClsMethodImpl.java +++ b/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/compiled/ClsMethodImpl.java @@ -46,311 +46,336 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.List; public class ClsMethodImpl extends ClsMemberImpl implements PsiAnnotationMethod { - private final NotNullLazyValue myReturnType; - private final NotNullLazyValue myDefaultValue; - - public ClsMethodImpl(final PsiMethodStub stub) { - super(stub); - - myReturnType = isConstructor() ? null : new AtomicNotNullLazyValue() { - @Nonnull - @Override - protected PsiTypeElement compute() { - PsiMethodStub stub = getStub(); - String typeText = TypeInfo.createTypeText(stub.getReturnTypeText()); - assert typeText != null : stub; - return new ClsTypeElementImpl(ClsMethodImpl.this, typeText, ClsTypeElementImpl.VARIANCE_NONE); - } - }; - - final String text = getStub().getDefaultValueText(); - myDefaultValue = StringUtil.isEmptyOrSpaces(text) ? null : new AtomicNotNullLazyValue() { - @Nonnull - @Override - protected PsiAnnotationMemberValue compute() { - return ClsParsingUtil.createMemberValueFromText(text, getManager(), ClsMethodImpl.this); - } - }; - } - - @Override - @Nonnull - public PsiElement[] getChildren() { - return getChildren(getDocComment(), getModifierList(), getReturnTypeElement(), getNameIdentifier(), getParameterList(), getThrowsList(), getDefaultValue()); - } - - @Override - public PsiClass getContainingClass() { - return (PsiClass) getParent(); - } - - @Override - @Nonnull - public PsiMethod[] findSuperMethods() { - return PsiSuperMethodImplUtil.findSuperMethods(this); - } - - @Override - @Nonnull - public PsiMethod[] findSuperMethods(boolean checkAccess) { - return PsiSuperMethodImplUtil.findSuperMethods(this, checkAccess); - } - - @Override - @Nonnull - public PsiMethod[] findSuperMethods(PsiClass parentClass) { - return PsiSuperMethodImplUtil.findSuperMethods(this, parentClass); - } - - @Override - @Nonnull - public List findSuperMethodSignaturesIncludingStatic(boolean checkAccess) { - return PsiSuperMethodImplUtil.findSuperMethodSignaturesIncludingStatic(this, checkAccess); - } - - @Override - public PsiMethod findDeepestSuperMethod() { - return PsiSuperMethodImplUtil.findDeepestSuperMethod(this); - } - - @Override - @Nonnull - public PsiMethod[] findDeepestSuperMethods() { - return PsiSuperMethodImplUtil.findDeepestSuperMethods(this); - } - - @Override - @Nonnull - public HierarchicalMethodSignature getHierarchicalMethodSignature() { - return PsiSuperMethodImplUtil.getHierarchicalMethodSignature(this); - } - - @Override - public PsiTypeElement getReturnTypeElement() { - return myReturnType != null ? myReturnType.getValue() : null; - } - - @Override - public PsiType getReturnType() { - PsiTypeElement typeElement = getReturnTypeElement(); - return typeElement == null ? null : typeElement.getType(); - } - - @Override - @Nonnull - public PsiModifierList getModifierList() { - return getStub().findChildStubByType(JavaStubElementTypes.MODIFIER_LIST).getPsi(); - } - - @Override - public boolean hasModifierProperty(@Nonnull String name) { - return getModifierList().hasModifierProperty(name); - } - - @Override - @Nonnull - public PsiParameterList getParameterList() { - return getStub().findChildStubByType(JavaStubElementTypes.PARAMETER_LIST).getPsi(); - } - - @Override - @Nonnull - public PsiReferenceList getThrowsList() { - return getStub().findChildStubByType(JavaStubElementTypes.THROWS_LIST).getPsi(); - } - - @Override - public PsiTypeParameterList getTypeParameterList() { - return getStub().findChildStubByType(JavaStubElementTypes.TYPE_PARAMETER_LIST).getPsi(); - } - - @Override - public PsiCodeBlock getBody() { - return null; - } - - @Override - public boolean isDeprecated() { - return getStub().isDeprecated() || PsiImplUtil.isDeprecatedByAnnotation(this); - } - - @Override - public PsiAnnotationMemberValue getDefaultValue() { - return myDefaultValue != null ? myDefaultValue.getValue() : null; - } - - @Override - public boolean isConstructor() { - return getStub().isConstructor(); - } - - @Override - public boolean isVarArgs() { - return getStub().isVarArgs(); - } - - @Override - @Nonnull - public MethodSignature getSignature(@Nonnull PsiSubstitutor substitutor) { - return MethodSignatureBackedByPsiMethod.create(this, substitutor); - } - - @Override - public void appendMirrorText(int indentLevel, @Nonnull StringBuilder buffer) { - appendText(getDocComment(), indentLevel, buffer, NEXT_LINE); - appendText(getModifierList(), indentLevel, buffer, ""); - appendText(getTypeParameterList(), indentLevel, buffer, " "); - if (!isConstructor()) { - appendText(getReturnTypeElement(), indentLevel, buffer, " "); - } - appendText(getNameIdentifier(), indentLevel, buffer, ""); - appendText(getParameterList(), indentLevel, buffer); - - PsiReferenceList throwsList = getThrowsList(); - if (throwsList.getReferencedTypes().length > 0) { - buffer.append(' '); - appendText(throwsList, indentLevel, buffer); - } - - PsiAnnotationMemberValue defaultValue = getDefaultValue(); - if (defaultValue != null) { - buffer.append(" default "); - appendText(defaultValue, indentLevel, buffer); - } - - if (hasModifierProperty(PsiModifier.ABSTRACT) || hasModifierProperty(PsiModifier.NATIVE)) { - buffer.append(";"); - } else { - buffer.append(" { /* compiled code */ }"); - } - } - - @Override - public void setMirror(@Nonnull TreeElement element) throws InvalidMirrorException { - setMirrorCheckingType(element, null); - - PsiMethod mirror = SourceTreeToPsiMap.treeToPsiNotNull(element); - - setMirrorIfPresent(getDocComment(), mirror.getDocComment()); - setMirror(getModifierList(), mirror.getModifierList()); - setMirror(getTypeParameterList(), mirror.getTypeParameterList()); - if (!isConstructor()) { - setMirror(getReturnTypeElement(), mirror.getReturnTypeElement()); - } - setMirror(getNameIdentifier(), mirror.getNameIdentifier()); - setMirror(getParameterList(), mirror.getParameterList()); - setMirror(getThrowsList(), mirror.getThrowsList()); - - PsiAnnotationMemberValue defaultValue = getDefaultValue(); - if (defaultValue != null) { - assert mirror instanceof PsiAnnotationMethod : this; - setMirror(defaultValue, ((PsiAnnotationMethod) mirror).getDefaultValue()); - } - } - - @Override - public void accept(@Nonnull PsiElementVisitor visitor) { - if (visitor instanceof JavaElementVisitor) { - ((JavaElementVisitor) visitor).visitMethod(this); - } else { - visitor.visitElement(this); - } - } - - @Override - public boolean processDeclarations(@Nonnull PsiScopeProcessor processor, @Nonnull ResolveState state, PsiElement lastParent, @Nonnull PsiElement place) { - processor.handleEvent(PsiScopeProcessor.Event.SET_DECLARATION_HOLDER, this); - if (lastParent == null) { - return true; - } - - if (!PsiScopesUtil.walkChildrenScopes(this, processor, state, lastParent, place)) { - return false; - } - - final PsiParameter[] parameters = getParameterList().getParameters(); - for (PsiParameter parameter : parameters) { - if (!processor.execute(parameter, state)) { - return false; - } - } - - return true; - } - - @Nullable - public PsiMethod getSourceMirrorMethod() { - return LanguageCachedValueUtil.getCachedValue(this, () -> CachedValueProvider.Result.create(calcSourceMirrorMethod(), getContainingFile(), getContainingFile().getNavigationElement(), - FileIndexFacade.getInstance(getProject()).getRootModificationTracker())); - } + private final NotNullLazyValue myReturnType; + private final NotNullLazyValue myDefaultValue; + + public ClsMethodImpl(final PsiMethodStub stub) { + super(stub); + + myReturnType = isConstructor() ? null : new AtomicNotNullLazyValue() { + @Nonnull + @Override + protected PsiTypeElement compute() { + PsiMethodStub stub = getStub(); + String typeText = TypeInfo.createTypeText(stub.getReturnTypeText()); + assert typeText != null : stub; + return new ClsTypeElementImpl(ClsMethodImpl.this, typeText, ClsTypeElementImpl.VARIANCE_NONE); + } + }; + + final String text = getStub().getDefaultValueText(); + myDefaultValue = StringUtil.isEmptyOrSpaces(text) ? null : new AtomicNotNullLazyValue() { + @Nonnull + @Override + protected PsiAnnotationMemberValue compute() { + return ClsParsingUtil.createMemberValueFromText(text, getManager(), ClsMethodImpl.this); + } + }; + } + + @Override + @Nonnull + public PsiElement[] getChildren() { + return getChildren( + getDocComment(), + getModifierList(), + getReturnTypeElement(), + getNameIdentifier(), + getParameterList(), + getThrowsList(), + getDefaultValue() + ); + } + + @Override + public PsiClass getContainingClass() { + return (PsiClass)getParent(); + } + + @Override + @Nonnull + public PsiMethod[] findSuperMethods() { + return PsiSuperMethodImplUtil.findSuperMethods(this); + } + + @Override + @Nonnull + public PsiMethod[] findSuperMethods(boolean checkAccess) { + return PsiSuperMethodImplUtil.findSuperMethods(this, checkAccess); + } + + @Override + @Nonnull + public PsiMethod[] findSuperMethods(PsiClass parentClass) { + return PsiSuperMethodImplUtil.findSuperMethods(this, parentClass); + } + + @Override + @Nonnull + public List findSuperMethodSignaturesIncludingStatic(boolean checkAccess) { + return PsiSuperMethodImplUtil.findSuperMethodSignaturesIncludingStatic(this, checkAccess); + } + + @Override + public PsiMethod findDeepestSuperMethod() { + return PsiSuperMethodImplUtil.findDeepestSuperMethod(this); + } + + @Override + @Nonnull + public PsiMethod[] findDeepestSuperMethods() { + return PsiSuperMethodImplUtil.findDeepestSuperMethods(this); + } + + @Override + @Nonnull + public HierarchicalMethodSignature getHierarchicalMethodSignature() { + return PsiSuperMethodImplUtil.getHierarchicalMethodSignature(this); + } + + @Override + public PsiTypeElement getReturnTypeElement() { + return myReturnType != null ? myReturnType.getValue() : null; + } + + @Override + public PsiType getReturnType() { + PsiTypeElement typeElement = getReturnTypeElement(); + return typeElement == null ? null : typeElement.getType(); + } + + @Override + @Nonnull + public PsiModifierList getModifierList() { + return getStub().findChildStubByType(JavaStubElementTypes.MODIFIER_LIST).getPsi(); + } + + @Override + public boolean hasModifierProperty(@Nonnull String name) { + return getModifierList().hasModifierProperty(name); + } + + @Override + @Nonnull + public PsiParameterList getParameterList() { + return getStub().findChildStubByType(JavaStubElementTypes.PARAMETER_LIST).getPsi(); + } + + @Override + @Nonnull + public PsiReferenceList getThrowsList() { + return getStub().findChildStubByType(JavaStubElementTypes.THROWS_LIST).getPsi(); + } + + @Override + public PsiTypeParameterList getTypeParameterList() { + return getStub().findChildStubByType(JavaStubElementTypes.TYPE_PARAMETER_LIST).getPsi(); + } + + @Override + public PsiCodeBlock getBody() { + return null; + } + + @Override + public boolean isDeprecated() { + return getStub().isDeprecated() || PsiImplUtil.isDeprecatedByAnnotation(this); + } + + @Override + public PsiAnnotationMemberValue getDefaultValue() { + return myDefaultValue != null ? myDefaultValue.getValue() : null; + } + + @Override + public boolean isConstructor() { + return getStub().isConstructor(); + } + + @Override + public boolean isVarArgs() { + return getStub().isVarArgs(); + } + + @Override + @Nonnull + public MethodSignature getSignature(@Nonnull PsiSubstitutor substitutor) { + return MethodSignatureBackedByPsiMethod.create(this, substitutor); + } + + @Override + public void appendMirrorText(int indentLevel, @Nonnull StringBuilder buffer) { + appendText(getDocComment(), indentLevel, buffer, NEXT_LINE); + appendText(getModifierList(), indentLevel, buffer, ""); + appendText(getTypeParameterList(), indentLevel, buffer, " "); + if (!isConstructor()) { + appendText(getReturnTypeElement(), indentLevel, buffer, " "); + } + appendText(getNameIdentifier(), indentLevel, buffer, ""); + appendText(getParameterList(), indentLevel, buffer); + + PsiReferenceList throwsList = getThrowsList(); + if (throwsList.getReferencedTypes().length > 0) { + buffer.append(' '); + appendText(throwsList, indentLevel, buffer); + } + + PsiAnnotationMemberValue defaultValue = getDefaultValue(); + if (defaultValue != null) { + buffer.append(" default "); + appendText(defaultValue, indentLevel, buffer); + } + + if (hasModifierProperty(PsiModifier.ABSTRACT) || hasModifierProperty(PsiModifier.NATIVE)) { + buffer.append(";"); + } + else { + buffer.append(" { /* compiled code */ }"); + } + } + + @Override + public void setMirror(@Nonnull TreeElement element) throws InvalidMirrorException { + setMirrorCheckingType(element, null); + + PsiMethod mirror = SourceTreeToPsiMap.treeToPsiNotNull(element); + + setMirrorIfPresent(getDocComment(), mirror.getDocComment()); + setMirror(getModifierList(), mirror.getModifierList()); + setMirror(getTypeParameterList(), mirror.getTypeParameterList()); + if (!isConstructor()) { + setMirror(getReturnTypeElement(), mirror.getReturnTypeElement()); + } + setMirror(getNameIdentifier(), mirror.getNameIdentifier()); + setMirror(getParameterList(), mirror.getParameterList()); + setMirror(getThrowsList(), mirror.getThrowsList()); + + PsiAnnotationMemberValue defaultValue = getDefaultValue(); + if (defaultValue != null) { + assert mirror instanceof PsiAnnotationMethod : this; + setMirror(defaultValue, ((PsiAnnotationMethod)mirror).getDefaultValue()); + } + } - @Nullable - private PsiMethod calcSourceMirrorMethod() { - PsiClass sourceClassMirror = ((ClsClassImpl) getParent()).getSourceMirrorClass(); - if (sourceClassMirror == null) { - return null; - } - for (PsiMethod sourceMethod : sourceClassMirror.findMethodsByName(getName(), false)) { - if (MethodSignatureUtil.areParametersErasureEqual(this, sourceMethod)) { - return sourceMethod; - } + @Override + public void accept(@Nonnull PsiElementVisitor visitor) { + if (visitor instanceof JavaElementVisitor) { + ((JavaElementVisitor)visitor).visitMethod(this); + } + else { + visitor.visitElement(this); + } } - return null; - } - @Override - @Nonnull - public PsiElement getNavigationElement() { - for (ClsCustomNavigationPolicy customNavigationPolicy : Extensions.getExtensions(ClsCustomNavigationPolicy.EP_NAME)) { - try { - PsiElement navigationElement = customNavigationPolicy.getNavigationElement(this); - if (navigationElement != null) { - return navigationElement; + @Override + public boolean processDeclarations( + @Nonnull PsiScopeProcessor processor, + @Nonnull ResolveState state, + PsiElement lastParent, + @Nonnull PsiElement place + ) { + processor.handleEvent(PsiScopeProcessor.Event.SET_DECLARATION_HOLDER, this); + if (lastParent == null) { + return true; + } + + if (!PsiScopesUtil.walkChildrenScopes(this, processor, state, lastParent, place)) { + return false; + } + + final PsiParameter[] parameters = getParameterList().getParameters(); + for (PsiParameter parameter : parameters) { + if (!processor.execute(parameter, state)) { + return false; + } } - } catch (IndexNotReadyException ignore) { - } - } - - try { - final PsiMethod method = getSourceMirrorMethod(); - return method != null ? method.getNavigationElement() : this; - } catch (IndexNotReadyException e) { - return this; - } - } - - @Override - public boolean hasTypeParameters() { - return PsiImplUtil.hasTypeParameters(this); - } - - @Override - @Nonnull - public PsiTypeParameter[] getTypeParameters() { - return PsiImplUtil.getTypeParameters(this); - } - - @Override - public ItemPresentation getPresentation() { - return ItemPresentationProvider.getItemPresentation(this); - } - - @Override - public boolean isEquivalentTo(final PsiElement another) { - return PsiClassImplUtil.isMethodEquivalentTo(this, another); - } - - @Override - @Nonnull - public SearchScope getUseScope() { - return PsiImplUtil.getMemberUseScope(this); - } - - @Override - public String toString() { - return "PsiMethod:" + getName(); - } + + return true; + } + + @Nullable + public PsiMethod getSourceMirrorMethod() { + return LanguageCachedValueUtil.getCachedValue( + this, + () -> CachedValueProvider.Result.create( + calcSourceMirrorMethod(), + getContainingFile(), + getContainingFile().getNavigationElement(), + FileIndexFacade.getInstance(getProject()).getRootModificationTracker() + ) + ); + } + + @Nullable + private PsiMethod calcSourceMirrorMethod() { + PsiClass sourceClassMirror = ((ClsClassImpl)getParent()).getSourceMirrorClass(); + if (sourceClassMirror == null) { + return null; + } + for (PsiMethod sourceMethod : sourceClassMirror.findMethodsByName(getName(), false)) { + if (MethodSignatureUtil.areParametersErasureEqual(this, sourceMethod)) { + return sourceMethod; + } + } + return null; + } + + @Override + @Nonnull + public PsiElement getNavigationElement() { + for (ClsCustomNavigationPolicy customNavigationPolicy : Extensions.getExtensions(ClsCustomNavigationPolicy.EP_NAME)) { + try { + PsiElement navigationElement = customNavigationPolicy.getNavigationElement(this); + if (navigationElement != null) { + return navigationElement; + } + } + catch (IndexNotReadyException ignore) { + } + } + + try { + final PsiMethod method = getSourceMirrorMethod(); + return method != null ? method.getNavigationElement() : this; + } + catch (IndexNotReadyException e) { + return this; + } + } + + @Override + public boolean hasTypeParameters() { + return PsiImplUtil.hasTypeParameters(this); + } + + @Override + @Nonnull + public PsiTypeParameter[] getTypeParameters() { + return PsiImplUtil.getTypeParameters(this); + } + + @Override + public ItemPresentation getPresentation() { + return ItemPresentationProvider.getItemPresentation(this); + } + + @Override + public boolean isEquivalentTo(final PsiElement another) { + return PsiClassImplUtil.isMethodEquivalentTo(this, another); + } + + @Override + @Nonnull + public SearchScope getUseScope() { + return PsiImplUtil.getMemberUseScope(this); + } + + @Override + public String toString() { + return "PsiMethod:" + getName(); + } } diff --git a/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/smartPointers/SmartTypePointerManagerImpl.java b/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/smartPointers/SmartTypePointerManagerImpl.java index 961d65d57c..1088688bf1 100644 --- a/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/smartPointers/SmartTypePointerManagerImpl.java +++ b/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/smartPointers/SmartTypePointerManagerImpl.java @@ -41,218 +41,229 @@ @Singleton @ServiceImpl public class SmartTypePointerManagerImpl extends SmartTypePointerManager { - private static final SmartTypePointer NULL_POINTER = new SmartTypePointer() { - @Override - public PsiType getType() { - return null; - } - }; - - private final SmartPointerManager myPsiPointerManager; - private final Project myProject; - - @Inject - public SmartTypePointerManagerImpl(final SmartPointerManager psiPointerManager, final Project project) { - myPsiPointerManager = psiPointerManager; - myProject = project; - } - - @Override - @Nonnull - public SmartTypePointer createSmartTypePointer(@Nonnull PsiType type) { - final SmartTypePointer pointer = type.accept(new SmartTypeCreatingVisitor()); - return pointer != null ? pointer : NULL_POINTER; - } + private static final SmartTypePointer NULL_POINTER = new SmartTypePointer() { + @Override + public PsiType getType() { + return null; + } + }; - private static class SimpleTypePointer implements SmartTypePointer { - private final PsiType myType; + private final SmartPointerManager myPsiPointerManager; + private final Project myProject; - private SimpleTypePointer(@Nonnull PsiType type) { - myType = type; + @Inject + public SmartTypePointerManagerImpl(final SmartPointerManager psiPointerManager, final Project project) { + myPsiPointerManager = psiPointerManager; + myProject = project; } @Override - public PsiType getType() { - return myType; + @Nonnull + public SmartTypePointer createSmartTypePointer(@Nonnull PsiType type) { + final SmartTypePointer pointer = type.accept(new SmartTypeCreatingVisitor()); + return pointer != null ? pointer : NULL_POINTER; } - } - private static class ArrayTypePointer extends TypePointerBase { - private final SmartTypePointer myComponentTypePointer; + private static class SimpleTypePointer implements SmartTypePointer { + private final PsiType myType; - public ArrayTypePointer(@Nonnull PsiArrayType type, @Nonnull SmartTypePointer componentTypePointer) { - super(type); - myComponentTypePointer = componentTypePointer; - } + private SimpleTypePointer(@Nonnull PsiType type) { + myType = type; + } - @Nullable - @Override - protected PsiArrayType calcType() { - final PsiType type = myComponentTypePointer.getType(); - return type == null ? null : new PsiArrayType(type); - } - } - - private static class WildcardTypePointer extends TypePointerBase { - private final PsiManager myManager; - private final SmartTypePointer myBoundPointer; - private final boolean myIsExtending; - - public WildcardTypePointer(@Nonnull PsiWildcardType type, @Nullable SmartTypePointer boundPointer) { - super(type); - myManager = type.getManager(); - myBoundPointer = boundPointer; - myIsExtending = type.isExtends(); + @Override + public PsiType getType() { + return myType; + } } - @Override - protected PsiWildcardType calcType() { - if (myBoundPointer == null) { - return PsiWildcardType.createUnbounded(myManager); - } else { - final PsiType type = myBoundPointer.getType(); - assert type != null : myBoundPointer; - if (myIsExtending) { - return PsiWildcardType.createExtends(myManager, type); + private static class ArrayTypePointer extends TypePointerBase { + private final SmartTypePointer myComponentTypePointer; + + public ArrayTypePointer(@Nonnull PsiArrayType type, @Nonnull SmartTypePointer componentTypePointer) { + super(type); + myComponentTypePointer = componentTypePointer; } - return PsiWildcardType.createSuper(myManager, type); - } - } - } - - private static class ClassTypePointer extends TypePointerBase { - private final SmartPsiElementPointer myClass; - private final LanguageLevel myLevel; - private final Map, SmartTypePointer> myMap; - private final SmartPsiElementPointer[] myAnnotations; - - ClassTypePointer(@Nonnull PsiClassType type, - @Nonnull SmartPsiElementPointer aClass, - @Nonnull LanguageLevel languageLevel, - @Nonnull Map, SmartTypePointer> map, - @Nonnull SmartPsiElementPointer[] annotations) { - super(type); - myClass = aClass; - myLevel = languageLevel; - myMap = map; - myAnnotations = annotations; - } - @Override - protected PsiClassType calcType() { - final PsiElement classElement = myClass.getElement(); - if (!(classElement instanceof PsiClass)) - return null; - Map resurrected = new HashMap<>(); - final Set, SmartTypePointer>> set = myMap.entrySet(); - for (Map.Entry, SmartTypePointer> entry : set) { - PsiTypeParameter element = entry.getKey().getElement(); - if (element != null) { - SmartTypePointer typePointer = entry.getValue(); - resurrected.put(element, typePointer == null ? null : typePointer.getType()); + @Nullable + @Override + protected PsiArrayType calcType() { + final PsiType type = myComponentTypePointer.getType(); + return type == null ? null : new PsiArrayType(type); } - } - for (PsiTypeParameter typeParameter : PsiUtil.typeParametersIterable((PsiClass) classElement)) { - if (!resurrected.containsKey(typeParameter)) { - resurrected.put(typeParameter, null); + } + + private static class WildcardTypePointer extends TypePointerBase { + private final PsiManager myManager; + private final SmartTypePointer myBoundPointer; + private final boolean myIsExtending; + + public WildcardTypePointer(@Nonnull PsiWildcardType type, @Nullable SmartTypePointer boundPointer) { + super(type); + myManager = type.getManager(); + myBoundPointer = boundPointer; + myIsExtending = type.isExtends(); } - } - final PsiSubstitutor resurrectedSubstitutor = PsiSubstitutor.createSubstitutor(resurrected); - PsiAnnotation[] resurrectedAnnotations = Stream.of(myAnnotations).map(SmartPsiElementPointer::getElement).filter(Objects::nonNull).toArray(PsiAnnotation[]::new); - return new PsiImmediateClassType((PsiClass) classElement, resurrectedSubstitutor, myLevel, resurrectedAnnotations); + @Override + protected PsiWildcardType calcType() { + if (myBoundPointer == null) { + return PsiWildcardType.createUnbounded(myManager); + } + else { + final PsiType type = myBoundPointer.getType(); + assert type != null : myBoundPointer; + if (myIsExtending) { + return PsiWildcardType.createExtends(myManager, type); + } + return PsiWildcardType.createSuper(myManager, type); + } + } } - } - private class DisjunctionTypePointer extends TypePointerBase { - private final List myPointers; + private static class ClassTypePointer extends TypePointerBase { + private final SmartPsiElementPointer myClass; + private final LanguageLevel myLevel; + private final Map, SmartTypePointer> myMap; + private final SmartPsiElementPointer[] myAnnotations; + + ClassTypePointer( + @Nonnull PsiClassType type, + @Nonnull SmartPsiElementPointer aClass, + @Nonnull LanguageLevel languageLevel, + @Nonnull Map, SmartTypePointer> map, + @Nonnull SmartPsiElementPointer[] annotations + ) { + super(type); + myClass = aClass; + myLevel = languageLevel; + myMap = map; + myAnnotations = annotations; + } - private DisjunctionTypePointer(@Nonnull PsiDisjunctionType type) { - super(type); - myPointers = ContainerUtil.map(type.getDisjunctions(), SmartTypePointerManagerImpl.this::createSmartTypePointer); + @Override + protected PsiClassType calcType() { + final PsiElement classElement = myClass.getElement(); + if (!(classElement instanceof PsiClass)) { + return null; + } + Map resurrected = new HashMap<>(); + final Set, SmartTypePointer>> set = myMap.entrySet(); + for (Map.Entry, SmartTypePointer> entry : set) { + PsiTypeParameter element = entry.getKey().getElement(); + if (element != null) { + SmartTypePointer typePointer = entry.getValue(); + resurrected.put(element, typePointer == null ? null : typePointer.getType()); + } + } + for (PsiTypeParameter typeParameter : PsiUtil.typeParametersIterable((PsiClass)classElement)) { + if (!resurrected.containsKey(typeParameter)) { + resurrected.put(typeParameter, null); + } + } + final PsiSubstitutor resurrectedSubstitutor = PsiSubstitutor.createSubstitutor(resurrected); + + PsiAnnotation[] resurrectedAnnotations = + Stream.of(myAnnotations).map(SmartPsiElementPointer::getElement).filter(Objects::nonNull).toArray(PsiAnnotation[]::new); + return new PsiImmediateClassType((PsiClass)classElement, resurrectedSubstitutor, myLevel, resurrectedAnnotations); + } } - @Override - protected PsiDisjunctionType calcType() { - final List types = ContainerUtil.map(myPointers, SmartTypePointer::getType); - return new PsiDisjunctionType(types, PsiManager.getInstance(myProject)); - } - } + private class DisjunctionTypePointer extends TypePointerBase { + private final List myPointers; - private class SmartTypeCreatingVisitor extends PsiTypeVisitor { - @Override - public SmartTypePointer visitPrimitiveType(PsiPrimitiveType primitiveType) { - return new SimpleTypePointer(primitiveType); - } + private DisjunctionTypePointer(@Nonnull PsiDisjunctionType type) { + super(type); + myPointers = ContainerUtil.map(type.getDisjunctions(), SmartTypePointerManagerImpl.this::createSmartTypePointer); + } - @Override - public SmartTypePointer visitArrayType(PsiArrayType arrayType) { - final SmartTypePointer componentTypePointer = arrayType.getComponentType().accept(this); - return componentTypePointer != null ? new ArrayTypePointer(arrayType, componentTypePointer) : null; + @Override + protected PsiDisjunctionType calcType() { + final List types = ContainerUtil.map(myPointers, SmartTypePointer::getType); + return new PsiDisjunctionType(types, PsiManager.getInstance(myProject)); + } } - @Override - public SmartTypePointer visitWildcardType(PsiWildcardType wildcardType) { - final PsiType bound = wildcardType.getBound(); - final SmartTypePointer boundPointer = bound == null ? null : bound.accept(this); - return new WildcardTypePointer(wildcardType, boundPointer); - } + private class SmartTypeCreatingVisitor extends PsiTypeVisitor { + @Override + public SmartTypePointer visitPrimitiveType(PsiPrimitiveType primitiveType) { + return new SimpleTypePointer(primitiveType); + } - @Override - public SmartTypePointer visitClassType(PsiClassType classType) { - final PsiClassType.ClassResolveResult resolveResult = classType.resolveGenerics(); - final PsiClass aClass = resolveResult.getElement(); - if (aClass == null) { - return createClassReferenceTypePointer(classType); - } - final PsiSubstitutor substitutor = resolveResult.getSubstitutor(); - final HashMap, SmartTypePointer> pointerMap = new HashMap<>(); - final Map map = new HashMap<>(); - for (PsiTypeParameter typeParameter : PsiUtil.typeParametersIterable(aClass)) { - final PsiType substitutionResult = substitutor.substitute(typeParameter); - if (substitutionResult != null) { - final SmartPsiElementPointer pointer = myPsiPointerManager.createSmartPsiElementPointer(typeParameter); - SmartTypePointer typePointer = substitutionResult.accept(this); - pointerMap.put(pointer, typePointer); - map.put(typeParameter, typePointer.getType()); - } else { - map.put(typeParameter, null); + @Override + public SmartTypePointer visitArrayType(PsiArrayType arrayType) { + final SmartTypePointer componentTypePointer = arrayType.getComponentType().accept(this); + return componentTypePointer != null ? new ArrayTypePointer(arrayType, componentTypePointer) : null; } - } - - SmartPsiElementPointer[] annotationPointers = - Stream - .of(classType.getAnnotations()) - .map(myPsiPointerManager::createSmartPsiElementPointer) - .toArray(SmartPsiElementPointer[]::new); - - LanguageLevel languageLevel = classType.getLanguageLevel(); - return new ClassTypePointer(new PsiImmediateClassType(aClass, - PsiSubstitutor.createSubstitutor(map), - languageLevel, - classType.getAnnotations()), - myPsiPointerManager.createSmartPsiElementPointer(aClass), - languageLevel, - pointerMap, - annotationPointers); - } - @Override - public SmartTypePointer visitDisjunctionType(PsiDisjunctionType disjunctionType) { - return new DisjunctionTypePointer(disjunctionType); - } - } - - @Nonnull - private SmartTypePointer createClassReferenceTypePointer(@Nonnull PsiClassType classType) { - for (ClassTypePointerFactory factory : ClassTypePointerFactory.EP_NAME.getExtensions()) { - SmartTypePointer pointer = factory.createClassTypePointer(classType, myProject); - if (pointer != null) { - return pointer; - } + @Override + public SmartTypePointer visitWildcardType(PsiWildcardType wildcardType) { + final PsiType bound = wildcardType.getBound(); + final SmartTypePointer boundPointer = bound == null ? null : bound.accept(this); + return new WildcardTypePointer(wildcardType, boundPointer); + } + + @Override + public SmartTypePointer visitClassType(PsiClassType classType) { + final PsiClassType.ClassResolveResult resolveResult = classType.resolveGenerics(); + final PsiClass aClass = resolveResult.getElement(); + if (aClass == null) { + return createClassReferenceTypePointer(classType); + } + final PsiSubstitutor substitutor = resolveResult.getSubstitutor(); + final HashMap, SmartTypePointer> pointerMap = new HashMap<>(); + final Map map = new HashMap<>(); + for (PsiTypeParameter typeParameter : PsiUtil.typeParametersIterable(aClass)) { + final PsiType substitutionResult = substitutor.substitute(typeParameter); + if (substitutionResult != null) { + final SmartPsiElementPointer pointer = + myPsiPointerManager.createSmartPsiElementPointer(typeParameter); + SmartTypePointer typePointer = substitutionResult.accept(this); + pointerMap.put(pointer, typePointer); + map.put(typeParameter, typePointer.getType()); + } + else { + map.put(typeParameter, null); + } + } + + SmartPsiElementPointer[] annotationPointers = + Stream + .of(classType.getAnnotations()) + .map(myPsiPointerManager::createSmartPsiElementPointer) + .toArray(SmartPsiElementPointer[]::new); + + LanguageLevel languageLevel = classType.getLanguageLevel(); + return new ClassTypePointer( + new PsiImmediateClassType( + aClass, + PsiSubstitutor.createSubstitutor(map), + languageLevel, + classType.getAnnotations() + ), + myPsiPointerManager.createSmartPsiElementPointer(aClass), + languageLevel, + pointerMap, + annotationPointers + ); + } + + @Override + public SmartTypePointer visitDisjunctionType(PsiDisjunctionType disjunctionType) { + return new DisjunctionTypePointer(disjunctionType); + } } - return new SimpleTypePointer(classType); - } + @Nonnull + private SmartTypePointer createClassReferenceTypePointer(@Nonnull PsiClassType classType) { + for (ClassTypePointerFactory factory : ClassTypePointerFactory.EP_NAME.getExtensions()) { + SmartTypePointer pointer = factory.createClassTypePointer(classType, myProject); + if (pointer != null) { + return pointer; + } + } + + return new SimpleTypePointer(classType); + } } diff --git a/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/source/PsiClassReferenceType.java b/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/source/PsiClassReferenceType.java index dce17da7a6..c016e483f7 100644 --- a/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/source/PsiClassReferenceType.java +++ b/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/source/PsiClassReferenceType.java @@ -25,277 +25,286 @@ import java.util.Objects; public class PsiClassReferenceType extends PsiClassType.Stub { - private final ClassReferencePointer myReference; - - public PsiClassReferenceType(@Nonnull PsiJavaCodeReferenceElement reference, LanguageLevel level) { - this(reference, level, collectAnnotations(reference)); - } - - public PsiClassReferenceType(@Nonnull PsiJavaCodeReferenceElement reference, LanguageLevel level, @Nonnull PsiAnnotation[] annotations) { - super(level, annotations); - myReference = ClassReferencePointer.constant(reference); - } - - public PsiClassReferenceType(@Nonnull PsiJavaCodeReferenceElement reference, - LanguageLevel level, - @Nonnull TypeAnnotationProvider provider) { - this(ClassReferencePointer.constant(reference), level, provider); - } - - PsiClassReferenceType(@Nonnull ClassReferencePointer reference, LanguageLevel level, @Nonnull TypeAnnotationProvider provider) { - super(level, provider); - myReference = reference; - } - - @Nonnull - private static PsiAnnotation[] collectAnnotations(PsiJavaCodeReferenceElement reference) { - List result = null; - for (PsiElement child = reference.getFirstChild(); child != null; child = child.getNextSibling()) { - if (child instanceof PsiAnnotation) { - if (result == null) { - result = new SmartList<>(); - } - result.add((PsiAnnotation)child); - } + private final ClassReferencePointer myReference; + + public PsiClassReferenceType(@Nonnull PsiJavaCodeReferenceElement reference, LanguageLevel level) { + this(reference, level, collectAnnotations(reference)); } - return result == null ? PsiAnnotation.EMPTY_ARRAY : result.toArray(PsiAnnotation.EMPTY_ARRAY); - } - - @Override - public boolean isValid() { - PsiJavaCodeReferenceElement reference = myReference.retrieveReference(); - if (reference != null && reference.isValid()) { - for (PsiAnnotation annotation : getAnnotations(false)) { - if (!annotation.isValid()) { - return false; - } - } - return true; + + public PsiClassReferenceType( + @Nonnull PsiJavaCodeReferenceElement reference, + LanguageLevel level, + @Nonnull PsiAnnotation[] annotations + ) { + super(level, annotations); + myReference = ClassReferencePointer.constant(reference); } - return false; - } - - @Override - public boolean equalsToText(@Nonnull String text) { - PsiJavaCodeReferenceElement reference = getReference(); - String name = reference.getReferenceName(); - return (name == null || text.contains(name)) && Objects.equals(text, getCanonicalText()); - } - - @Override - @Nonnull - public GlobalSearchScope getResolveScope() { - return getReference().getResolveScope(); - } - - @Override - @Nonnull - public PsiAnnotation[] getAnnotations() { - return getAnnotations(true); - } - - private PsiAnnotation[] getAnnotations(boolean merge) { - PsiAnnotation[] annotations = super.getAnnotations(); - - if (merge) { - PsiJavaCodeReferenceElement reference = myReference.retrieveReference(); - if (reference != null && reference.isValid() && reference.isQualified()) { - PsiAnnotation[] embedded = collectAnnotations(reference); - if (annotations.length > 0 && embedded.length > 0) { - LinkedHashSet set = new LinkedHashSet<>(); - ContainerUtil.addAll(set, annotations); - ContainerUtil.addAll(set, embedded); - annotations = set.toArray(PsiAnnotation.EMPTY_ARRAY); - } - else { - annotations = ArrayUtil.mergeArrays(annotations, embedded); + + public PsiClassReferenceType( + @Nonnull PsiJavaCodeReferenceElement reference, + LanguageLevel level, + @Nonnull TypeAnnotationProvider provider + ) { + this(ClassReferencePointer.constant(reference), level, provider); + } + + PsiClassReferenceType(@Nonnull ClassReferencePointer reference, LanguageLevel level, @Nonnull TypeAnnotationProvider provider) { + super(level, provider); + myReference = reference; + } + + @Nonnull + private static PsiAnnotation[] collectAnnotations(PsiJavaCodeReferenceElement reference) { + List result = null; + for (PsiElement child = reference.getFirstChild(); child != null; child = child.getNextSibling()) { + if (child instanceof PsiAnnotation) { + if (result == null) { + result = new SmartList<>(); + } + result.add((PsiAnnotation)child); + } } - } + return result == null ? PsiAnnotation.EMPTY_ARRAY : result.toArray(PsiAnnotation.EMPTY_ARRAY); } - return annotations; - } + @Override + public boolean isValid() { + PsiJavaCodeReferenceElement reference = myReference.retrieveReference(); + if (reference != null && reference.isValid()) { + for (PsiAnnotation annotation : getAnnotations(false)) { + if (!annotation.isValid()) { + return false; + } + } + return true; + } + return false; + } - @Override - public - @Nonnull - LanguageLevel getLanguageLevel() { - if (myLanguageLevel != null) { - return myLanguageLevel; + @Override + public boolean equalsToText(@Nonnull String text) { + PsiJavaCodeReferenceElement reference = getReference(); + String name = reference.getReferenceName(); + return (name == null || text.contains(name)) && Objects.equals(text, getCanonicalText()); } - return PsiUtil.getLanguageLevel(getReference()); - } - - @Override - public - @Nonnull - PsiClassType setLanguageLevel(final @Nonnull LanguageLevel languageLevel) { - if (languageLevel.equals(myLanguageLevel)) { - return this; + + @Override + @Nonnull + public GlobalSearchScope getResolveScope() { + return getReference().getResolveScope(); } - return new PsiClassReferenceType(getReference(), languageLevel, getAnnotationProvider()); - } - @Override - public PsiClass resolve() { - return resolveGenerics().getElement(); - } + @Override + @Nonnull + public PsiAnnotation[] getAnnotations() { + return getAnnotations(true); + } - private static final class DelegatingClassResolveResult implements PsiClassType.ClassResolveResult { - private final JavaResolveResult myDelegate; + private PsiAnnotation[] getAnnotations(boolean merge) { + PsiAnnotation[] annotations = super.getAnnotations(); + + if (merge) { + PsiJavaCodeReferenceElement reference = myReference.retrieveReference(); + if (reference != null && reference.isValid() && reference.isQualified()) { + PsiAnnotation[] embedded = collectAnnotations(reference); + if (annotations.length > 0 && embedded.length > 0) { + LinkedHashSet set = new LinkedHashSet<>(); + ContainerUtil.addAll(set, annotations); + ContainerUtil.addAll(set, embedded); + annotations = set.toArray(PsiAnnotation.EMPTY_ARRAY); + } + else { + annotations = ArrayUtil.mergeArrays(annotations, embedded); + } + } + } - private DelegatingClassResolveResult(@Nonnull JavaResolveResult delegate) { - myDelegate = delegate; + return annotations; } @Override public @Nonnull - PsiSubstitutor getSubstitutor() { - return myDelegate.getSubstitutor(); + LanguageLevel getLanguageLevel() { + if (myLanguageLevel != null) { + return myLanguageLevel; + } + return PsiUtil.getLanguageLevel(getReference()); } @Override - public boolean isValidResult() { - return myDelegate.isValidResult(); + public + @Nonnull + PsiClassType setLanguageLevel(final @Nonnull LanguageLevel languageLevel) { + if (languageLevel.equals(myLanguageLevel)) { + return this; + } + return new PsiClassReferenceType(getReference(), languageLevel, getAnnotationProvider()); } @Override - public boolean isAccessible() { - return myDelegate.isAccessible(); + public PsiClass resolve() { + return resolveGenerics().getElement(); } - @Override - public boolean isStaticsScopeCorrect() { - return myDelegate.isStaticsScopeCorrect(); + private static final class DelegatingClassResolveResult implements PsiClassType.ClassResolveResult { + private final JavaResolveResult myDelegate; + + private DelegatingClassResolveResult(@Nonnull JavaResolveResult delegate) { + myDelegate = delegate; + } + + @Override + public + @Nonnull + PsiSubstitutor getSubstitutor() { + return myDelegate.getSubstitutor(); + } + + @Override + public boolean isValidResult() { + return myDelegate.isValidResult(); + } + + @Override + public boolean isAccessible() { + return myDelegate.isAccessible(); + } + + @Override + public boolean isStaticsScopeCorrect() { + return myDelegate.isStaticsScopeCorrect(); + } + + @Override + public PsiElement getCurrentFileResolveScope() { + return myDelegate.getCurrentFileResolveScope(); + } + + @Override + public boolean isPackagePrefixPackageReference() { + return myDelegate.isPackagePrefixPackageReference(); + } + + @Override + @Nullable + public String getInferenceError() { + return myDelegate instanceof PatternCandidateInfo ? ((PatternCandidateInfo)myDelegate).getInferenceError() : null; + } + + @Override + public PsiClass getElement() { + final PsiElement element = myDelegate.getElement(); + return element instanceof PsiClass ? (PsiClass)element : null; + } } @Override - public PsiElement getCurrentFileResolveScope() { - return myDelegate.getCurrentFileResolveScope(); + @Nonnull + public ClassResolveResult resolveGenerics() { + PsiJavaCodeReferenceElement reference = getReference(); + if (!reference.isValid()) { + if (reference instanceof LightClassTypeReference) { + PsiUtil.ensureValidType(((LightClassTypeReference)reference).getType()); + } + throw new PsiInvalidElementAccessException( + reference, + myReference.toString() + "; augmenters=" + PsiAugmentProvider.EP_NAME.getExtensionList() + ); + } + final JavaResolveResult result = reference.advancedResolve(false); + return result.getElement() == null ? ClassResolveResult.EMPTY : new DelegatingClassResolveResult(result); } @Override - public boolean isPackagePrefixPackageReference() { - return myDelegate.isPackagePrefixPackageReference(); + @Nonnull + public PsiClassType rawType() { + PsiJavaCodeReferenceElement reference = getReference(); + PsiElement resolved = reference.resolve(); + if (resolved instanceof PsiClass) { + PsiClass aClass = (PsiClass)resolved; + if (!PsiUtil.typeParametersIterable(aClass).iterator().hasNext()) { + return this; + } + PsiManager manager = reference.getManager(); + final PsiElementFactory factory = JavaPsiFacade.getElementFactory(manager.getProject()); + final PsiSubstitutor rawSubstitutor = factory.createRawSubstitutor(aClass); + return new PsiImmediateClassType(aClass, rawSubstitutor, getLanguageLevel(), getAnnotationProvider()); + } + String qualifiedName = reference.getQualifiedName(); + String name = reference.getReferenceName(); + if (name == null) { + name = ""; + } + LightClassReference lightReference = + new LightClassReference(reference.getManager(), name, qualifiedName, reference.getResolveScope()); + return new PsiClassReferenceType(lightReference, null, getAnnotationProvider()); } @Override - @Nullable - public String getInferenceError() { - return myDelegate instanceof PatternCandidateInfo ? ((PatternCandidateInfo)myDelegate).getInferenceError() : null; + public String getClassName() { + return getReference().getReferenceName(); } @Override - public PsiClass getElement() { - final PsiElement element = myDelegate.getElement(); - return element instanceof PsiClass ? (PsiClass)element : null; - } - } - - @Override - @Nonnull - public ClassResolveResult resolveGenerics() { - PsiJavaCodeReferenceElement reference = getReference(); - if (!reference.isValid()) { - if (reference instanceof LightClassTypeReference) { - PsiUtil.ensureValidType(((LightClassTypeReference)reference).getType()); - } - throw new PsiInvalidElementAccessException(reference, - myReference.toString() + "; augmenters=" + PsiAugmentProvider.EP_NAME.getExtensionList()); + @Nonnull + public PsiType[] getParameters() { + return getReference().getTypeParameters(); } - final JavaResolveResult result = reference.advancedResolve(false); - return result.getElement() == null ? ClassResolveResult.EMPTY : new DelegatingClassResolveResult(result); - } - - @Override - @Nonnull - public PsiClassType rawType() { - PsiJavaCodeReferenceElement reference = getReference(); - PsiElement resolved = reference.resolve(); - if (resolved instanceof PsiClass) { - PsiClass aClass = (PsiClass)resolved; - if (!PsiUtil.typeParametersIterable(aClass).iterator().hasNext()) { - return this; - } - PsiManager manager = reference.getManager(); - final PsiElementFactory factory = JavaPsiFacade.getElementFactory(manager.getProject()); - final PsiSubstitutor rawSubstitutor = factory.createRawSubstitutor(aClass); - return new PsiImmediateClassType(aClass, rawSubstitutor, getLanguageLevel(), getAnnotationProvider()); + + @Override + public + @Nonnull + String getPresentableText(boolean annotated) { + PsiJavaCodeReferenceElement ref = getReference(); + if (!annotated) { + return PsiNameHelper.getPresentableText(ref); + } + PsiAnnotation[] annotations; + if (ref.getQualifier() != null) { + // like java.lang.@Anno String + annotations = ObjectUtil.notNull(PsiTreeUtil.getChildrenOfType(ref, PsiAnnotation.class), PsiAnnotation.EMPTY_ARRAY); + } + else { + annotations = getAnnotations(false); + } + + return PsiNameHelper.getPresentableText(ref.getReferenceName(), annotations, ref.getTypeParameters()); } - String qualifiedName = reference.getQualifiedName(); - String name = reference.getReferenceName(); - if (name == null) { - name = ""; + + @Override + public + @Nonnull + String getCanonicalText(boolean annotated) { + return getText(annotated); } - LightClassReference lightReference = new LightClassReference(reference.getManager(), name, qualifiedName, reference.getResolveScope()); - return new PsiClassReferenceType(lightReference, null, getAnnotationProvider()); - } - - @Override - public String getClassName() { - return getReference().getReferenceName(); - } - - @Override - @Nonnull - public PsiType[] getParameters() { - return getReference().getTypeParameters(); - } - - @Override - public - @Nonnull - String getPresentableText(boolean annotated) { - PsiJavaCodeReferenceElement ref = getReference(); - if (!annotated) { - return PsiNameHelper.getPresentableText(ref); + + @Override + public + @Nonnull + String getInternalCanonicalText() { + return getCanonicalText(true); } - PsiAnnotation[] annotations; - if (ref.getQualifier() != null) { - // like java.lang.@Anno String - annotations = ObjectUtil.notNull(PsiTreeUtil.getChildrenOfType(ref, PsiAnnotation.class), PsiAnnotation.EMPTY_ARRAY); + + private String getText(boolean annotated) { + PsiJavaCodeReferenceElement reference = getReference(); + if (reference instanceof PsiAnnotatedJavaCodeReferenceElement) { + PsiAnnotatedJavaCodeReferenceElement ref = (PsiAnnotatedJavaCodeReferenceElement)reference; + PsiAnnotation[] annotations = annotated ? getAnnotations(false) : PsiAnnotation.EMPTY_ARRAY; + return ref.getCanonicalText(annotated, annotations.length == 0 ? null : annotations); + } + return reference.getCanonicalText(); } - else { - annotations = getAnnotations(false); + + @Nonnull + public PsiJavaCodeReferenceElement getReference() { + return myReference.retrieveNonNullReference(); } - return PsiNameHelper.getPresentableText(ref.getReferenceName(), annotations, ref.getTypeParameters()); - } - - @Override - public - @Nonnull - String getCanonicalText(boolean annotated) { - return getText(annotated); - } - - @Override - public - @Nonnull - String getInternalCanonicalText() { - return getCanonicalText(true); - } - - private String getText(boolean annotated) { - PsiJavaCodeReferenceElement reference = getReference(); - if (reference instanceof PsiAnnotatedJavaCodeReferenceElement) { - PsiAnnotatedJavaCodeReferenceElement ref = (PsiAnnotatedJavaCodeReferenceElement)reference; - PsiAnnotation[] annotations = annotated ? getAnnotations(false) : PsiAnnotation.EMPTY_ARRAY; - return ref.getCanonicalText(annotated, annotations.length == 0 ? null : annotations); + @Override + public + @Nullable + PsiElement getPsiContext() { + return myReference.retrieveReference(); } - return reference.getCanonicalText(); - } - - @Nonnull - public PsiJavaCodeReferenceElement getReference() { - return myReference.retrieveNonNullReference(); - } - - @Override - public - @Nullable - PsiElement getPsiContext() { - return myReference.retrieveReference(); - } } \ No newline at end of file diff --git a/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/source/resolve/graphInference/InferenceVariable.java b/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/source/resolve/graphInference/InferenceVariable.java index a4b9ef9e39..4a76993b61 100644 --- a/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/source/resolve/graphInference/InferenceVariable.java +++ b/java-language-impl/src/main/java/com/intellij/java/language/impl/psi/impl/source/resolve/graphInference/InferenceVariable.java @@ -32,205 +32,207 @@ * User: anna */ public class InferenceVariable extends LightTypeParameter { - private final PsiElement myContext; - - public PsiTypeParameter getParameter() { - return getDelegate(); - } - - private boolean myThrownBound; - private final Map> myBounds = new HashMap>(); - private final String myName; - - private PsiType myInstantiation = PsiType.NULL; - - InferenceVariable(PsiElement context, PsiTypeParameter parameter, String name) { - super(parameter); - myName = name; - myContext = context; - TypeConversionUtil.markAsFreshVariable(this, context); - } - - public PsiType getInstantiation() { - return myInstantiation; - } - - public void setInstantiation(PsiType instantiation) { - myInstantiation = instantiation; - } - - @Nonnull - @Override - public PsiClassType[] getExtendsListTypes() { - final List result = new ArrayList(); - for (PsiType type : getBounds(InferenceBound.UPPER)) { - if (type instanceof PsiClassType) { - result.add((PsiClassType)type); - } - } - return result.toArray(new PsiClassType[result.size()]); - } - - public static void addBound(PsiType inferenceVariableType, PsiType boundType, InferenceBound inferenceBound, InferenceSession session) { - final InferenceVariable variable = session.getInferenceVariable(inferenceVariableType); - if (variable != null) { - for (TypeAnnotationModifier modifier : TypeAnnotationModifier.EP_NAME.getExtensions()) { - if (boundType instanceof PsiClassType) { - final TypeAnnotationProvider annotationProvider = modifier.modifyAnnotations(inferenceVariableType, (PsiClassType)boundType); - if (annotationProvider != null) { - boundType = boundType.annotate(annotationProvider); - } - } - } + private final PsiElement myContext; - variable.addBound(boundType, inferenceBound, session.myIncorporationPhase); + public PsiTypeParameter getParameter() { + return getDelegate(); } - } - public boolean addBound(PsiType classType, InferenceBound inferenceBound, @Nullable InferenceIncorporationPhase incorporationPhase) { - if (inferenceBound == InferenceBound.EQ && PsiUtil.resolveClassInClassTypeOnly(classType) == this) { - return false; + private boolean myThrownBound; + private final Map> myBounds = new HashMap>(); + private final String myName; + + private PsiType myInstantiation = PsiType.NULL; + + InferenceVariable(PsiElement context, PsiTypeParameter parameter, String name) { + super(parameter); + myName = name; + myContext = context; + TypeConversionUtil.markAsFreshVariable(this, context); } - List bounds = myBounds.get(inferenceBound); - if (bounds == null) { - bounds = new ArrayList(); - myBounds.put(inferenceBound, bounds); + + public PsiType getInstantiation() { + return myInstantiation; } - if (classType == null) { - classType = PsiType.NULL; + public void setInstantiation(PsiType instantiation) { + myInstantiation = instantiation; } - if (incorporationPhase == null || !bounds.contains(classType)) { - bounds.add(classType); - if (incorporationPhase != null) { - incorporationPhase.addBound(this, classType, inferenceBound); - } - return true; + @Nonnull + @Override + public PsiClassType[] getExtendsListTypes() { + final List result = new ArrayList(); + for (PsiType type : getBounds(InferenceBound.UPPER)) { + if (type instanceof PsiClassType) { + result.add((PsiClassType)type); + } + } + return result.toArray(new PsiClassType[result.size()]); } - return false; - } - public List getBounds(InferenceBound inferenceBound) { - final List bounds = myBounds.get(inferenceBound); - return bounds != null ? new ArrayList(bounds) : Collections.emptyList(); - } + public static void addBound(PsiType inferenceVariableType, PsiType boundType, InferenceBound inferenceBound, InferenceSession session) { + final InferenceVariable variable = session.getInferenceVariable(inferenceVariableType); + if (variable != null) { + for (TypeAnnotationModifier modifier : TypeAnnotationModifier.EP_NAME.getExtensions()) { + if (boundType instanceof PsiClassType) { + final TypeAnnotationProvider annotationProvider = + modifier.modifyAnnotations(inferenceVariableType, (PsiClassType)boundType); + if (annotationProvider != null) { + boundType = boundType.annotate(annotationProvider); + } + } + } + + variable.addBound(boundType, inferenceBound, session.myIncorporationPhase); + } + } - public List getReadOnlyBounds(InferenceBound inferenceBound) { - final List bounds = myBounds.get(inferenceBound); - return bounds != null ? bounds : Collections.emptyList(); - } + public boolean addBound(PsiType classType, InferenceBound inferenceBound, @Nullable InferenceIncorporationPhase incorporationPhase) { + if (inferenceBound == InferenceBound.EQ && PsiUtil.resolveClassInClassTypeOnly(classType) == this) { + return false; + } + List bounds = myBounds.get(inferenceBound); + if (bounds == null) { + bounds = new ArrayList(); + myBounds.put(inferenceBound, bounds); + } - public Set getDependencies(InferenceSession session) { - final Set dependencies = new LinkedHashSet(); - collectBoundDependencies(session, dependencies); - collectTransitiveDependencies(session, dependencies, dependencies); + if (classType == null) { + classType = PsiType.NULL; + } - if (!session.hasCapture(this) && dependencies.isEmpty()) { - return dependencies; + if (incorporationPhase == null || !bounds.contains(classType)) { + bounds.add(classType); + if (incorporationPhase != null) { + incorporationPhase.addBound(this, classType, inferenceBound); + } + return true; + } + return false; } - if (!session.hasCapture(this)) { - return dependencies; + public List getBounds(InferenceBound inferenceBound) { + final List bounds = myBounds.get(inferenceBound); + return bounds != null ? new ArrayList(bounds) : Collections.emptyList(); } - for (Iterator iterator = dependencies.iterator(); iterator.hasNext(); ) { - if (!session.hasCapture(iterator.next())) { - iterator.remove(); - } + public List getReadOnlyBounds(InferenceBound inferenceBound) { + final List bounds = myBounds.get(inferenceBound); + return bounds != null ? bounds : Collections.emptyList(); } - session.collectCaptureDependencies(this, dependencies); - return dependencies; - } - private void collectTransitiveDependencies(InferenceSession session, - Set dependencies, - Set rootDependencies) { - final LinkedHashSet newDependencies = new LinkedHashSet(); + public Set getDependencies(InferenceSession session) { + final Set dependencies = new LinkedHashSet(); + collectBoundDependencies(session, dependencies); + collectTransitiveDependencies(session, dependencies, dependencies); - for (InferenceVariable dependency : dependencies) { - dependency.collectBoundDependencies(session, newDependencies); - } - newDependencies.removeAll(rootDependencies); - newDependencies.remove(this); + if (!session.hasCapture(this) && dependencies.isEmpty()) { + return dependencies; + } - if (!newDependencies.isEmpty()) { - rootDependencies.addAll(newDependencies); - collectTransitiveDependencies(session, newDependencies, rootDependencies); - } - } + if (!session.hasCapture(this)) { + return dependencies; + } - private void collectBoundDependencies(InferenceSession session, Set dependencies) { - for (Collection boundTypes : myBounds.values()) { - if (boundTypes != null) { - for (PsiType bound : boundTypes) { - session.collectDependencies(bound, dependencies); + for (Iterator iterator = dependencies.iterator(); iterator.hasNext(); ) { + if (!session.hasCapture(iterator.next())) { + iterator.remove(); + } } - } + session.collectCaptureDependencies(this, dependencies); + return dependencies; } - } - public boolean isThrownBound() { - return myThrownBound; - } + private void collectTransitiveDependencies( + InferenceSession session, + Set dependencies, + Set rootDependencies + ) { + final LinkedHashSet newDependencies = new LinkedHashSet(); - public void setThrownBound() { - myThrownBound = true; - } + for (InferenceVariable dependency : dependencies) { + dependency.collectBoundDependencies(session, newDependencies); + } + newDependencies.removeAll(rootDependencies); + newDependencies.remove(this); - @Override - public boolean isInheritor(@Nonnull PsiClass baseClass, boolean checkDeep) { - for (PsiType type : getBounds(InferenceBound.UPPER)) { - PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(type); - if (psiClass != null) { - if (getManager().areElementsEquivalent(baseClass, psiClass)) { - return true; + if (!newDependencies.isEmpty()) { + rootDependencies.addAll(newDependencies); + collectTransitiveDependencies(session, newDependencies, rootDependencies); } - if (checkDeep && psiClass.isInheritor(baseClass, true)) { - return true; + } + + private void collectBoundDependencies(InferenceSession session, Set dependencies) { + for (Collection boundTypes : myBounds.values()) { + if (boundTypes != null) { + for (PsiType bound : boundTypes) { + session.collectDependencies(bound, dependencies); + } + } } - } } - return super.isInheritor(baseClass, checkDeep); - } + public boolean isThrownBound() { + return myThrownBound; + } - @Override - public boolean isEquivalentTo(PsiElement another) { - if (this == another) { - return true; + public void setThrownBound() { + myThrownBound = true; } - if (getDelegate() == another && myContext != null && !PsiTreeUtil.isAncestor(((PsiTypeParameter)another).getOwner(), - myContext, - false)) { - return true; + @Override + public boolean isInheritor(@Nonnull PsiClass baseClass, boolean checkDeep) { + for (PsiType type : getBounds(InferenceBound.UPPER)) { + PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(type); + if (psiClass != null) { + if (getManager().areElementsEquivalent(baseClass, psiClass)) { + return true; + } + if (checkDeep && psiClass.isInheritor(baseClass, true)) { + return true; + } + } + } + + return super.isInheritor(baseClass, checkDeep); } - return false; - } - @Override - public boolean useDelegateToSubstitute() { - return false; - } + @Override + public boolean isEquivalentTo(PsiElement another) { + if (this == another) { + return true; + } - @Override - public String toString() { - return getDelegate().toString(); - } + if (getDelegate() == another && myContext != null + && !PsiTreeUtil.isAncestor(((PsiTypeParameter)another).getOwner(), myContext, false)) { + return true; + } + return false; + } - @Override - public PsiTypeParameterListOwner getOwner() { - return null; - } + @Override + public boolean useDelegateToSubstitute() { + return false; + } + + @Override + public String toString() { + return getDelegate().toString(); + } - @Nullable - @Override - public String getName() { - return myName; - } + @Override + public PsiTypeParameterListOwner getOwner() { + return null; + } - public PsiElement getCallContext() { - return myContext; - } + @Nullable + @Override + public String getName() { + return myName; + } + + public PsiElement getCallContext() { + return myContext; + } } diff --git a/java-language-impl/src/main/java/consulo/java/language/impl/codeInsight/ExtraExceptionHandler.java b/java-language-impl/src/main/java/consulo/java/language/impl/codeInsight/ExtraExceptionHandler.java index 31396d4164..83ee2f41eb 100644 --- a/java-language-impl/src/main/java/consulo/java/language/impl/codeInsight/ExtraExceptionHandler.java +++ b/java-language-impl/src/main/java/consulo/java/language/impl/codeInsight/ExtraExceptionHandler.java @@ -31,7 +31,7 @@ @Deprecated(forRemoval = true) @DeprecationInfo("Use CustomExceptionHandler") public interface ExtraExceptionHandler { - ExtensionPointName EP_NAME = ExtensionPointName.create(ExtraExceptionHandler.class); + ExtensionPointName EP_NAME = ExtensionPointName.create(ExtraExceptionHandler.class); - boolean isHandled(@Nonnull PsiClassType type, @Nonnull PsiElement element); + boolean isHandled(@Nonnull PsiClassType type, @Nonnull PsiElement element); } diff --git a/java-properties-impl/src/main/java/consulo/java/properties/impl/i18n/I18nizeAction.java b/java-properties-impl/src/main/java/consulo/java/properties/impl/i18n/I18nizeAction.java index e723f49ad3..5a34795b70 100644 --- a/java-properties-impl/src/main/java/consulo/java/properties/impl/i18n/I18nizeAction.java +++ b/java-properties-impl/src/main/java/consulo/java/properties/impl/i18n/I18nizeAction.java @@ -46,129 +46,152 @@ @ActionImpl(id = "I18nize", parents = @ActionParentRef(@ActionRef(id = "RefactoringMenu"))) public class I18nizeAction extends AnAction { - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.i18n.I18nizeAction"); - - @Override - @RequiredUIAccess - public void update(@Nonnull AnActionEvent e) { - boolean active = getHandler(e) != null; - if (ActionPlaces.isPopupPlace(e.getPlace())) { - e.getPresentation().setVisible(active); - } - else { - e.getPresentation().setEnabled(active); + private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.i18n.I18nizeAction"); + + @Override + @RequiredUIAccess + public void update(@Nonnull AnActionEvent e) { + boolean active = getHandler(e) != null; + if (ActionPlaces.isPopupPlace(e.getPlace())) { + e.getPresentation().setVisible(active); + } + else { + e.getPresentation().setEnabled(active); + } } - } - @Nullable - @RequiredReadAction - public static I18nQuickFixHandler getHandler(final AnActionEvent e) { - final Editor editor = getEditor(e); - if (editor == null) return null; + @Nullable + @RequiredReadAction + public static I18nQuickFixHandler getHandler(final AnActionEvent e) { + final Editor editor = getEditor(e); + if (editor == null) { + return null; + } - PsiFile psiFile = e.getData(PsiFile.KEY); - if (psiFile == null) return null; + PsiFile psiFile = e.getData(PsiFile.KEY); + if (psiFile == null) { + return null; + } - TextRange range = JavaI18nUtil.getSelectedRange(editor, psiFile); - if (range == null) return null; + TextRange range = JavaI18nUtil.getSelectedRange(editor, psiFile); + if (range == null) { + return null; + } - final PsiLiteralExpression literalExpression = getEnclosingStringLiteral(psiFile, editor); - PsiElement element = psiFile.findElementAt(editor.getCaretModel().getOffset()); - if (element == null) return null; - if (I18nizeConcatenationQuickFix.getEnclosingLiteralConcatenation(element) != null) { - return new I18nizeConcatenationQuickFix(); - } - else if (literalExpression != null && literalExpression.getTextRange().contains(range)) { - return new I18nizeQuickFix(); - } + final PsiLiteralExpression literalExpression = getEnclosingStringLiteral(psiFile, editor); + PsiElement element = psiFile.findElementAt(editor.getCaretModel().getOffset()); + if (element == null) { + return null; + } + if (I18nizeConcatenationQuickFix.getEnclosingLiteralConcatenation(element) != null) { + return new I18nizeConcatenationQuickFix(); + } + else if (literalExpression != null && literalExpression.getTextRange().contains(range)) { + return new I18nizeQuickFix(); + } - for (I18nizeHandlerProvider handlerProvider : I18nizeHandlerProvider.EP_NAME.getExtensionList()) { - I18nQuickFixHandler handler = handlerProvider.getHandler(psiFile, editor, range); - if (handler != null) { - return handler; - } + for (I18nizeHandlerProvider handlerProvider : I18nizeHandlerProvider.EP_NAME.getExtensionList()) { + I18nQuickFixHandler handler = handlerProvider.getHandler(psiFile, editor, range); + if (handler != null) { + return handler; + } + } + + return null; } - return null; - } - - - @Nullable - @RequiredReadAction - public static PsiLiteralExpression getEnclosingStringLiteral(final PsiFile psiFile, final Editor editor) { - PsiElement psiElement = psiFile.findElementAt(editor.getCaretModel().getOffset()); - if (psiElement == null) return null; - PsiLiteralExpression expression = PsiTreeUtil.getParentOfType(psiElement, PsiLiteralExpression.class); - if (expression == null || !(expression.getValue() instanceof String)) return null; - return expression; - } - - private static Editor getEditor(final AnActionEvent e) { - return e.getData(Editor.KEY); - } - - @RequiredUIAccess - public static void doI18nSelectedString( - final @Nonnull Project project, - final @Nonnull Editor editor, - final @Nonnull PsiFile psiFile, - final @Nonnull I18nQuickFixHandler handler - ) { - try { - handler.checkApplicability(psiFile, editor); + @Nullable + @RequiredReadAction + public static PsiLiteralExpression getEnclosingStringLiteral(final PsiFile psiFile, final Editor editor) { + PsiElement psiElement = psiFile.findElementAt(editor.getCaretModel().getOffset()); + if (psiElement == null) { + return null; + } + PsiLiteralExpression expression = PsiTreeUtil.getParentOfType(psiElement, PsiLiteralExpression.class); + if (expression == null || !(expression.getValue() instanceof String)) { + return null; + } + return expression; } - catch (IncorrectOperationException ex) { - CommonRefactoringUtil.showErrorHint(project, editor, ex.getMessage(), CodeInsightLocalize.i18nizeErrorTitle().get(), null); - return; + + private static Editor getEditor(final AnActionEvent e) { + return e.getData(Editor.KEY); } - final JavaI18nizeQuickFixDialog dialog = handler.createDialog(project, editor, psiFile); - if (dialog == null) return; - dialog.show(); - if (!dialog.isOK()) return; + @RequiredUIAccess + public static void doI18nSelectedString( + final @Nonnull Project project, + final @Nonnull Editor editor, + final @Nonnull PsiFile psiFile, + final @Nonnull I18nQuickFixHandler handler + ) { + try { + handler.checkApplicability(psiFile, editor); + } + catch (IncorrectOperationException ex) { + CommonRefactoringUtil.showErrorHint(project, editor, ex.getMessage(), CodeInsightLocalize.i18nizeErrorTitle().get(), null); + return; + } + + final JavaI18nizeQuickFixDialog dialog = handler.createDialog(project, editor, psiFile); + if (dialog == null) { + return; + } + dialog.show(); + if (!dialog.isOK()) { + return; + } - if (!FileModificationService.getInstance().prepareFileForWrite(psiFile)) return; - final Collection propertiesFiles = dialog.getAllPropertiesFiles(); - for (PropertiesFile file : propertiesFiles) { - if (!FileModificationService.getInstance().prepareFileForWrite(file.getContainingFile())) return; + if (!FileModificationService.getInstance().prepareFileForWrite(psiFile)) { + return; + } + final Collection propertiesFiles = dialog.getAllPropertiesFiles(); + for (PropertiesFile file : propertiesFiles) { + if (!FileModificationService.getInstance().prepareFileForWrite(file.getContainingFile())) { + return; + } + } + + project.getApplication().runWriteAction(() -> CommandProcessor.getInstance().executeCommand( + project, + () -> { + try { + handler.performI18nization( + psiFile, + editor, + dialog.getLiteralExpression(), + propertiesFiles, + dialog.getKey(), + StringUtil.unescapeStringCharacters(dialog.getValue()), + dialog.getI18nizedText(), + dialog.getParameters(), + dialog.getPropertyCreationHandler() + ); + } + catch (IncorrectOperationException e) { + LOG.error(e); + } + }, + CodeInsightLocalize.quickfixI18nCommandName().get(), + project + )); } - project.getApplication().runWriteAction(() -> CommandProcessor.getInstance().executeCommand( - project, - () -> { - try { - handler.performI18nization( - psiFile, - editor, - dialog.getLiteralExpression(), - propertiesFiles, - dialog.getKey(), - StringUtil.unescapeStringCharacters(dialog.getValue()), - dialog.getI18nizedText(), - dialog.getParameters(), - dialog.getPropertyCreationHandler() - ); - } - catch (IncorrectOperationException e) { - LOG.error(e); - } - }, - CodeInsightLocalize.quickfixI18nCommandName().get(), - project - )); - } - - @Override - @RequiredUIAccess - public void actionPerformed(@Nonnull AnActionEvent e) { - final Editor editor = getEditor(e); - final Project project = editor.getProject(); - assert project != null; - final PsiFile psiFile = e.getData(PsiFile.KEY); - if (psiFile == null) return; - final I18nQuickFixHandler handler = getHandler(e); - if (handler == null) return; - - doI18nSelectedString(project, editor, psiFile, handler); - } + @Override + @RequiredUIAccess + public void actionPerformed(@Nonnull AnActionEvent e) { + final Editor editor = getEditor(e); + final Project project = editor.getProject(); + assert project != null; + final PsiFile psiFile = e.getData(PsiFile.KEY); + if (psiFile == null) { + return; + } + final I18nQuickFixHandler handler = getHandler(e); + if (handler == null) { + return; + } + + doI18nSelectedString(project, editor, psiFile, handler); + } } diff --git a/java-properties-impl/src/main/java/consulo/java/properties/impl/i18n/I18nizeHandlerProvider.java b/java-properties-impl/src/main/java/consulo/java/properties/impl/i18n/I18nizeHandlerProvider.java index 71489a7091..85117b639c 100644 --- a/java-properties-impl/src/main/java/consulo/java/properties/impl/i18n/I18nizeHandlerProvider.java +++ b/java-properties-impl/src/main/java/consulo/java/properties/impl/i18n/I18nizeHandlerProvider.java @@ -30,9 +30,8 @@ */ @ExtensionAPI(ComponentScope.APPLICATION) public abstract class I18nizeHandlerProvider { + public static final ExtensionPointName EP_NAME = ExtensionPointName.create(I18nizeHandlerProvider.class); - public static final ExtensionPointName EP_NAME = ExtensionPointName.create(I18nizeHandlerProvider.class); - - @Nullable - public abstract I18nQuickFixHandler getHandler(@Nonnull final PsiFile psiFile, @Nonnull final Editor editor, @Nonnull TextRange range); + @Nullable + public abstract I18nQuickFixHandler getHandler(@Nonnull final PsiFile psiFile, @Nonnull final Editor editor, @Nonnull TextRange range); } diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/InferredAnnotationsManagerImpl.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/InferredAnnotationsManagerImpl.java index 17a83c2e11..1ab385c1dd 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/InferredAnnotationsManagerImpl.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/InferredAnnotationsManagerImpl.java @@ -20,48 +20,48 @@ @Singleton @ServiceImpl public class InferredAnnotationsManagerImpl extends InferredAnnotationsManager { - private static final Key INFERRED_ANNOTATION = Key.create("INFERRED_ANNOTATION"); - private final Project myProject; + private static final Key INFERRED_ANNOTATION = Key.create("INFERRED_ANNOTATION"); + private final Project myProject; - @Inject - public InferredAnnotationsManagerImpl(Project project) { - myProject = project; - } + @Inject + public InferredAnnotationsManagerImpl(Project project) { + myProject = project; + } - @Nullable - @Override - public PsiAnnotation findInferredAnnotation(@Nonnull PsiModifierListOwner listOwner, @Nonnull String annotationFQN) { - for (InferredAnnotationProvider provider : InferredAnnotationProvider.EP_NAME.getExtensionList(myProject)) { - PsiAnnotation annotation = provider.findInferredAnnotation(listOwner, annotationFQN); - if (annotation != null) { - markInferred(annotation); - return annotation; - } + @Nullable + @Override + public PsiAnnotation findInferredAnnotation(@Nonnull PsiModifierListOwner listOwner, @Nonnull String annotationFQN) { + for (InferredAnnotationProvider provider : InferredAnnotationProvider.EP_NAME.getExtensionList(myProject)) { + PsiAnnotation annotation = provider.findInferredAnnotation(listOwner, annotationFQN); + if (annotation != null) { + markInferred(annotation); + return annotation; + } + } + return null; } - return null; - } - @Nonnull - @Override - public PsiAnnotation[] findInferredAnnotations(@Nonnull PsiModifierListOwner listOwner) { - List result = new ArrayList<>(); - for (InferredAnnotationProvider provider : InferredAnnotationProvider.EP_NAME.getExtensionList(myProject)) { - List annotations = provider.findInferredAnnotations(listOwner); - for (PsiAnnotation annotation : annotations) { - markInferred(annotation); - result.add(annotation); - } + @Nonnull + @Override + public PsiAnnotation[] findInferredAnnotations(@Nonnull PsiModifierListOwner listOwner) { + List result = new ArrayList<>(); + for (InferredAnnotationProvider provider : InferredAnnotationProvider.EP_NAME.getExtensionList(myProject)) { + List annotations = provider.findInferredAnnotations(listOwner); + for (PsiAnnotation annotation : annotations) { + markInferred(annotation); + result.add(annotation); + } + } + return result.toArray(PsiAnnotation.EMPTY_ARRAY); } - return result.toArray(PsiAnnotation.EMPTY_ARRAY); - } - @Override - public boolean isInferredAnnotation(@Nonnull PsiAnnotation annotation) { - return annotation.getUserData(INFERRED_ANNOTATION) != null; - } + @Override + public boolean isInferredAnnotation(@Nonnull PsiAnnotation annotation) { + return annotation.getUserData(INFERRED_ANNOTATION) != null; + } - private static void markInferred(@Nonnull PsiAnnotation annotation) { - annotation.putUserData(INFERRED_ANNOTATION, Boolean.TRUE); - } + private static void markInferred(@Nonnull PsiAnnotation annotation) { + annotation.putUserData(INFERRED_ANNOTATION, Boolean.TRUE); + } } diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/impl/quickfix/OrderEntryFix.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/impl/quickfix/OrderEntryFix.java index a71bef7fac..bbbf796b73 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/impl/quickfix/OrderEntryFix.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/impl/quickfix/OrderEntryFix.java @@ -57,6 +57,7 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.io.File; import java.util.*; import java.util.stream.Collectors; @@ -66,222 +67,264 @@ * @author cdr */ public abstract class OrderEntryFix implements SyntheticIntentionAction, LocalQuickFix { - protected OrderEntryFix() { - } - - @Override - public boolean startInWriteAction() { - return true; - } - - @Override - @Nonnull - public String getName() { - return getText(); - } - - @Override - public void applyFix(@Nonnull final Project project, @Nonnull final ProblemDescriptor descriptor) { - invoke(project, null, descriptor.getPsiElement().getContainingFile()); - } - - @Nullable - public static List registerFixes(@Nonnull PsiReference reference) { - PsiElement psiElement = reference.getElement(); - String shortReferenceName = reference.getRangeInElement().substring(psiElement.getText()); - - Project project = psiElement.getProject(); - PsiFile containingFile = psiElement.getContainingFile(); - if (containingFile == null) { - return null; + protected OrderEntryFix() { } - VirtualFile refVFile = containingFile.getVirtualFile(); - if (refVFile == null) { - return null; + + @Override + public boolean startInWriteAction() { + return true; } - ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex(); - Module currentModule = fileIndex.getModuleForFile(refVFile); - if (currentModule == null) { - return null; + @Override + @Nonnull + public String getName() { + return getText(); } - if (reference instanceof PsiJavaModuleReferenceImpl) { - List result = new ArrayList<>(); - createModuleFixes((PsiJavaModuleReferenceImpl) reference, currentModule, refVFile, result); - return result; + @Override + public void applyFix(@Nonnull final Project project, @Nonnull final ProblemDescriptor descriptor) { + invoke(project, null, descriptor.getPsiElement().getContainingFile()); } - List result = new ArrayList<>(); - JavaPsiFacade facade = JavaPsiFacade.getInstance(psiElement.getProject()); + @Nullable + public static List registerFixes(@Nonnull PsiReference reference) { + PsiElement psiElement = reference.getElement(); + String shortReferenceName = reference.getRangeInElement().substring(psiElement.getText()); - registerExternalFixes(reference, psiElement, shortReferenceName, facade, currentModule, result); - if (!result.isEmpty()) { - return result; - } + Project project = psiElement.getProject(); + PsiFile containingFile = psiElement.getContainingFile(); + if (containingFile == null) { + return null; + } + VirtualFile refVFile = containingFile.getVirtualFile(); + if (refVFile == null) { + return null; + } - PsiClass[] classes = PsiShortNamesCache.getInstance(project).getClassesByName(shortReferenceName, GlobalSearchScope.allScope(project)); - List allowedDependencies = filterAllowedDependencies(psiElement, classes); - if (allowedDependencies.isEmpty()) { - return result; - } + ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex(); + Module currentModule = fileIndex.getModuleForFile(refVFile); + if (currentModule == null) { + return null; + } - OrderEntryFix moduleDependencyFix = new AddModuleDependencyFix(currentModule, refVFile, allowedDependencies, reference); - result.add(moduleDependencyFix); + if (reference instanceof PsiJavaModuleReferenceImpl) { + List result = new ArrayList<>(); + createModuleFixes((PsiJavaModuleReferenceImpl)reference, currentModule, refVFile, result); + return result; + } - Set librariesToAdd = new HashSet<>(); - ModuleFileIndex moduleFileIndex = ModuleRootManager.getInstance(currentModule).getFileIndex(); - for (PsiClass aClass : allowedDependencies) { - if (!facade.getResolveHelper().isAccessible(aClass, psiElement, aClass)) { - continue; - } - PsiFile psiFile = aClass.getContainingFile(); - if (psiFile == null) { - continue; - } - VirtualFile virtualFile = psiFile.getVirtualFile(); - if (virtualFile == null) { - continue; - } - for (OrderEntry orderEntry : fileIndex.getOrderEntriesForFile(virtualFile)) { - if (orderEntry instanceof LibraryOrderEntry) { - final LibraryOrderEntry libraryEntry = (LibraryOrderEntry) orderEntry; - final Library library = libraryEntry.getLibrary(); - if (library == null) { - continue; - } - VirtualFile[] files = library.getFiles(BinariesOrderRootType.getInstance()); - if (files.length == 0) { - continue; - } - final VirtualFile jar = files[0]; + List result = new ArrayList<>(); + JavaPsiFacade facade = JavaPsiFacade.getInstance(psiElement.getProject()); - if (jar == null || libraryEntry.isModuleLevel() && !librariesToAdd.add(jar) || !librariesToAdd.add(library)) { - continue; - } - OrderEntry entryForFile = moduleFileIndex.getOrderEntryForFile(virtualFile); - if (entryForFile != null && !(entryForFile instanceof ExportableOrderEntry && ((ExportableOrderEntry) entryForFile).getScope() == DependencyScope.TEST && !moduleFileIndex - .isInTestSourceContent(refVFile))) { - continue; - } + registerExternalFixes(reference, psiElement, shortReferenceName, facade, currentModule, result); + if (!result.isEmpty()) { + return result; + } - OrderEntryFix platformFix = new AddLibraryToDependenciesFix(currentModule, library, reference, aClass.getQualifiedName()); - result.add(platformFix); + PsiClass[] classes = + PsiShortNamesCache.getInstance(project).getClassesByName(shortReferenceName, GlobalSearchScope.allScope(project)); + List allowedDependencies = filterAllowedDependencies(psiElement, classes); + if (allowedDependencies.isEmpty()) { + return result; } - } - } - return result; - } + OrderEntryFix moduleDependencyFix = new AddModuleDependencyFix(currentModule, refVFile, allowedDependencies, reference); + result.add(moduleDependencyFix); + + Set librariesToAdd = new HashSet<>(); + ModuleFileIndex moduleFileIndex = ModuleRootManager.getInstance(currentModule).getFileIndex(); + for (PsiClass aClass : allowedDependencies) { + if (!facade.getResolveHelper().isAccessible(aClass, psiElement, aClass)) { + continue; + } + PsiFile psiFile = aClass.getContainingFile(); + if (psiFile == null) { + continue; + } + VirtualFile virtualFile = psiFile.getVirtualFile(); + if (virtualFile == null) { + continue; + } + for (OrderEntry orderEntry : fileIndex.getOrderEntriesForFile(virtualFile)) { + if (orderEntry instanceof LibraryOrderEntry) { + final LibraryOrderEntry libraryEntry = (LibraryOrderEntry)orderEntry; + final Library library = libraryEntry.getLibrary(); + if (library == null) { + continue; + } + VirtualFile[] files = library.getFiles(BinariesOrderRootType.getInstance()); + if (files.length == 0) { + continue; + } + final VirtualFile jar = files[0]; - private static void createModuleFixes(PsiJavaModuleReferenceImpl reference, Module currentModule, VirtualFile refVFile, List result) { - ProjectFileIndex index = ProjectRootManager.getInstance(currentModule.getProject()).getFileIndex(); - List targets = Stream.of(reference.multiResolve(true)).map(ResolveResult::getElement).filter(Objects::nonNull).collect(Collectors.toList()); + if (jar == null || libraryEntry.isModuleLevel() && !librariesToAdd.add(jar) || !librariesToAdd.add(library)) { + continue; + } + OrderEntry entryForFile = moduleFileIndex.getOrderEntryForFile(virtualFile); + if (entryForFile != null && !(entryForFile instanceof ExportableOrderEntry && ((ExportableOrderEntry)entryForFile).getScope() == DependencyScope.TEST && !moduleFileIndex + .isInTestSourceContent(refVFile))) { + continue; + } - Set modules = targets.stream().map(e -> !(e instanceof PsiCompiledElement) ? e.getContainingFile() : null).map(f -> f != null ? f.getVirtualFile() : null).filter(vf -> vf != null && - index.isInSource(vf)).map(vf -> index.getModuleForFile(vf)).filter(m -> m != null && m != currentModule).collect(Collectors.toSet()); - if (!modules.isEmpty()) { - result.add(0, new AddModuleDependencyFix(currentModule, refVFile, modules, reference)); + OrderEntryFix platformFix = + new AddLibraryToDependenciesFix(currentModule, library, reference, aClass.getQualifiedName()); + result.add(platformFix); + } + } + } + + return result; } - targets.stream().map(e -> e instanceof PsiCompiledElement ? e.getContainingFile() : null).map(f -> f != null ? f.getVirtualFile() : null).flatMap(vf -> vf != null ? index - .getOrderEntriesForFile(vf).stream() : Stream.empty()).map(e -> e instanceof LibraryOrderEntry ? ((LibraryOrderEntry) e).getLibrary() : null).filter(Objects::nonNull).distinct() - .forEach(l -> result.add(new AddLibraryToDependenciesFix(currentModule, l, reference, null))); - } + private static void createModuleFixes( + PsiJavaModuleReferenceImpl reference, + Module currentModule, + VirtualFile refVFile, + List result + ) { + ProjectFileIndex index = ProjectRootManager.getInstance(currentModule.getProject()).getFileIndex(); + List targets = + Stream.of(reference.multiResolve(true)).map(ResolveResult::getElement).filter(Objects::nonNull).collect(Collectors.toList()); - private static void registerExternalFixes(@Nonnull PsiReference reference, - PsiElement psiElement, - String shortReferenceName, - JavaPsiFacade facade, - Module currentModule, - List result) { - String fullReferenceText = reference.getCanonicalText(); - for (ExternalLibraryResolver resolver : ExternalLibraryResolver.EP_NAME.getExtensionList()) { - ExternalClassResolveResult resolveResult = resolver.resolveClass(shortReferenceName, isReferenceToAnnotation(psiElement), currentModule); - OrderEntryFix fix = null; - if (resolveResult != null && facade.findClass(resolveResult.getQualifiedClassName(), GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(currentModule, true)) == null) { - fix = new AddExternalLibraryToDependenciesQuickFix(currentModule, resolveResult.getLibrary(), reference, resolveResult.getQualifiedClassName()); - } else if (!fullReferenceText.equals(shortReferenceName)) { - ExternalLibraryDescriptor descriptor = resolver.resolvePackage(fullReferenceText); - if (descriptor != null) { - fix = new AddExternalLibraryToDependenciesQuickFix(currentModule, descriptor, reference, null); + Set modules = targets.stream() + .map(e -> !(e instanceof PsiCompiledElement) ? e.getContainingFile() : null) + .map(f -> f != null ? f.getVirtualFile() : null) + .filter(vf -> vf != null && + index.isInSource(vf)) + .map(vf -> index.getModuleForFile(vf)) + .filter(m -> m != null && m != currentModule) + .collect(Collectors.toSet()); + if (!modules.isEmpty()) { + result.add(0, new AddModuleDependencyFix(currentModule, refVFile, modules, reference)); } - } - if (fix != null) { - result.add(fix); - } - } - } - private static List filterAllowedDependencies(PsiElement element, PsiClass[] classes) { - DependencyValidationManager dependencyValidationManager = DependencyValidationManager.getInstance(element.getProject()); - PsiFile fromFile = element.getContainingFile(); - List result = new ArrayList<>(); - for (PsiClass psiClass : classes) { - PsiFile containingFile = psiClass.getContainingFile(); - if (containingFile != null && dependencyValidationManager.getViolatorDependencyRule(fromFile, containingFile) == null) { - result.add(psiClass); - } + targets.stream() + .map(e -> e instanceof PsiCompiledElement ? e.getContainingFile() : null) + .map(f -> f != null ? f.getVirtualFile() : null) + .flatMap(vf -> vf != null ? index + .getOrderEntriesForFile(vf).stream() : Stream.empty()) + .map(e -> e instanceof LibraryOrderEntry ? ((LibraryOrderEntry)e).getLibrary() : null) + .filter(Objects::nonNull) + .distinct() + .forEach(l -> result.add(new AddLibraryToDependenciesFix(currentModule, l, reference, null))); } - return result; - } - private static ThreeState isReferenceToAnnotation(final PsiElement psiElement) { - if (psiElement.getLanguage() == JavaLanguage.INSTANCE && !PsiUtil.isLanguageLevel5OrHigher(psiElement)) { - return ThreeState.NO; + private static void registerExternalFixes( + @Nonnull PsiReference reference, + PsiElement psiElement, + String shortReferenceName, + JavaPsiFacade facade, + Module currentModule, + List result + ) { + String fullReferenceText = reference.getCanonicalText(); + for (ExternalLibraryResolver resolver : ExternalLibraryResolver.EP_NAME.getExtensionList()) { + ExternalClassResolveResult resolveResult = + resolver.resolveClass(shortReferenceName, isReferenceToAnnotation(psiElement), currentModule); + OrderEntryFix fix = null; + if (resolveResult != null && facade.findClass( + resolveResult.getQualifiedClassName(), + GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(currentModule, true) + ) == null) { + fix = new AddExternalLibraryToDependenciesQuickFix( + currentModule, + resolveResult.getLibrary(), + reference, + resolveResult.getQualifiedClassName() + ); + } + else if (!fullReferenceText.equals(shortReferenceName)) { + ExternalLibraryDescriptor descriptor = resolver.resolvePackage(fullReferenceText); + if (descriptor != null) { + fix = new AddExternalLibraryToDependenciesQuickFix(currentModule, descriptor, reference, null); + } + } + if (fix != null) { + result.add(fix); + } + } } - if (PsiTreeUtil.getParentOfType(psiElement, PsiAnnotation.class) != null) { - return ThreeState.YES; + + private static List filterAllowedDependencies(PsiElement element, PsiClass[] classes) { + DependencyValidationManager dependencyValidationManager = DependencyValidationManager.getInstance(element.getProject()); + PsiFile fromFile = element.getContainingFile(); + List result = new ArrayList<>(); + for (PsiClass psiClass : classes) { + PsiFile containingFile = psiClass.getContainingFile(); + if (containingFile != null && dependencyValidationManager.getViolatorDependencyRule(fromFile, containingFile) == null) { + result.add(psiClass); + } + } + return result; } - if (PsiTreeUtil.getParentOfType(psiElement, PsiImportStatement.class) != null) { - return ThreeState.UNSURE; + + private static ThreeState isReferenceToAnnotation(final PsiElement psiElement) { + if (psiElement.getLanguage() == JavaLanguage.INSTANCE && !PsiUtil.isLanguageLevel5OrHigher(psiElement)) { + return ThreeState.NO; + } + if (PsiTreeUtil.getParentOfType(psiElement, PsiAnnotation.class) != null) { + return ThreeState.YES; + } + if (PsiTreeUtil.getParentOfType(psiElement, PsiImportStatement.class) != null) { + return ThreeState.UNSURE; + } + return ThreeState.NO; } - return ThreeState.NO; - } - public static void importClass(@Nonnull Module currentModule, @Nullable Editor editor, @Nullable PsiReference reference, @Nullable String className) { - Project project = currentModule.getProject(); - if (editor != null && reference != null && className != null) { - DumbService.getInstance(project).withAlternativeResolveEnabled(() -> { - GlobalSearchScope scope = GlobalSearchScope.moduleWithLibrariesScope(currentModule); - PsiClass aClass = JavaPsiFacade.getInstance(project).findClass(className, scope); - if (aClass != null) { - new AddImportAction(project, reference, editor, aClass).execute(); + public static void importClass( + @Nonnull Module currentModule, + @Nullable Editor editor, + @Nullable PsiReference reference, + @Nullable String className + ) { + Project project = currentModule.getProject(); + if (editor != null && reference != null && className != null) { + DumbService.getInstance(project).withAlternativeResolveEnabled(() -> { + GlobalSearchScope scope = GlobalSearchScope.moduleWithLibrariesScope(currentModule); + PsiClass aClass = JavaPsiFacade.getInstance(project).findClass(className, scope); + if (aClass != null) { + new AddImportAction(project, reference, editor, aClass).execute(); + } + }); } - }); } - } - public static void addJarToRoots(@Nonnull String jarPath, final @Nonnull Module module, @Nullable PsiElement location) { - addJarsToRoots(Collections.singletonList(jarPath), null, module, location); - } + public static void addJarToRoots(@Nonnull String jarPath, final @Nonnull Module module, @Nullable PsiElement location) { + addJarsToRoots(Collections.singletonList(jarPath), null, module, location); + } - public static void addJarsToRoots(@Nonnull List jarPaths, @Nullable String libraryName, @Nonnull Module module, @Nullable PsiElement location) { - List urls = refreshAndConvertToUrls(jarPaths); - DependencyScope scope = suggestScopeByLocation(module, location); - ModuleRootModificationUtil.addModuleLibrary(module, libraryName, urls, Collections.emptyList(), scope); - } + public static void addJarsToRoots( + @Nonnull List jarPaths, + @Nullable String libraryName, + @Nonnull Module module, + @Nullable PsiElement location + ) { + List urls = refreshAndConvertToUrls(jarPaths); + DependencyScope scope = suggestScopeByLocation(module, location); + ModuleRootModificationUtil.addModuleLibrary(module, libraryName, urls, Collections.emptyList(), scope); + } - @Nonnull - public static List refreshAndConvertToUrls(@Nonnull List jarPaths) { - return ContainerUtil.map(jarPaths, OrderEntryFix::refreshAndConvertToUrl); - } + @Nonnull + public static List refreshAndConvertToUrls(@Nonnull List jarPaths) { + return ContainerUtil.map(jarPaths, OrderEntryFix::refreshAndConvertToUrl); + } - @Nonnull - public static DependencyScope suggestScopeByLocation(@Nonnull Module module, @Nullable PsiElement location) { - if (location != null) { - final VirtualFile vFile = location.getContainingFile().getVirtualFile(); - if (vFile != null && ModuleRootManager.getInstance(module).getFileIndex().isInTestSourceContent(vFile)) { - return DependencyScope.TEST; - } + @Nonnull + public static DependencyScope suggestScopeByLocation(@Nonnull Module module, @Nullable PsiElement location) { + if (location != null) { + final VirtualFile vFile = location.getContainingFile().getVirtualFile(); + if (vFile != null && ModuleRootManager.getInstance(module).getFileIndex().isInTestSourceContent(vFile)) { + return DependencyScope.TEST; + } + } + return DependencyScope.COMPILE; } - return DependencyScope.COMPILE; - } - @Nonnull - private static String refreshAndConvertToUrl(String jarPath) { - final File libraryRoot = new File(jarPath); - LocalFileSystem.getInstance().refreshAndFindFileByIoFile(libraryRoot); - return VirtualFileUtil.getUrlForLibraryRoot(libraryRoot); - } + @Nonnull + private static String refreshAndConvertToUrl(String jarPath) { + final File libraryRoot = new File(jarPath); + LocalFileSystem.getInstance().refreshAndFindFileByIoFile(libraryRoot); + return VirtualFileUtil.getUrlForLibraryRoot(libraryRoot); + } } \ No newline at end of file diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/quickFix/ExternalLibraryResolver.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/quickFix/ExternalLibraryResolver.java index 369c1fac1d..716a4f81f1 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/quickFix/ExternalLibraryResolver.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/quickFix/ExternalLibraryResolver.java @@ -30,34 +30,36 @@ */ @ExtensionAPI(ComponentScope.APPLICATION) public abstract class ExternalLibraryResolver { - public static final ExtensionPointName EP_NAME = - ExtensionPointName.create(ExternalLibraryResolver.class); - - @Nullable - public abstract ExternalClassResolveResult resolveClass(@Nonnull String shortClassName, - @Nonnull ThreeState isAnnotation, - @Nonnull Module contextModule); - - @Nullable - public ExternalLibraryDescriptor resolvePackage(@Nonnull String packageName) { - return null; - } - - public static class ExternalClassResolveResult { - private final String myQualifiedClassName; - private final ExternalLibraryDescriptor myLibrary; - - public ExternalClassResolveResult(String qualifiedClassName, ExternalLibraryDescriptor library) { - myQualifiedClassName = qualifiedClassName; - myLibrary = library; - } + public static final ExtensionPointName EP_NAME = + ExtensionPointName.create(ExternalLibraryResolver.class); + + @Nullable + public abstract ExternalClassResolveResult resolveClass( + @Nonnull String shortClassName, + @Nonnull ThreeState isAnnotation, + @Nonnull Module contextModule + ); - public String getQualifiedClassName() { - return myQualifiedClassName; + @Nullable + public ExternalLibraryDescriptor resolvePackage(@Nonnull String packageName) { + return null; } - public ExternalLibraryDescriptor getLibrary() { - return myLibrary; + public static class ExternalClassResolveResult { + private final String myQualifiedClassName; + private final ExternalLibraryDescriptor myLibrary; + + public ExternalClassResolveResult(String qualifiedClassName, ExternalLibraryDescriptor library) { + myQualifiedClassName = qualifiedClassName; + myLibrary = library; + } + + public String getQualifiedClassName() { + return myQualifiedClassName; + } + + public ExternalLibraryDescriptor getLibrary() { + return myLibrary; + } } - } } diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/GenerateMembersUtil.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/GenerateMembersUtil.java index 4e61e783bc..781cd934db 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/GenerateMembersUtil.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/GenerateMembersUtil.java @@ -66,783 +66,840 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.*; public class GenerateMembersUtil { - private static final Logger LOG = Logger.getInstance(GenerateMembersUtil.class); - - private GenerateMembersUtil() { - } - - @Nonnull - public static List insertMembersAtOffset(PsiFile file, - int offset, - @Nonnull List memberPrototypes) throws IncorrectOperationException { - if (memberPrototypes.isEmpty()) { - return memberPrototypes; - } - final PsiElement leaf = file.findElementAt(offset); - if (leaf == null) { - return Collections.emptyList(); - } - - PsiClass aClass = findClassAtOffset(file, leaf); - if (aClass == null) { - return Collections.emptyList(); - } - PsiElement anchor = memberPrototypes.get(0).findInsertionAnchor(aClass, leaf); - - if (anchor instanceof PsiWhiteSpace) { - final ASTNode spaceNode = anchor.getNode(); - anchor = anchor.getNextSibling(); - - assert spaceNode != null; - if (spaceNode.getStartOffset() <= offset && spaceNode.getStartOffset() + spaceNode.getTextLength() >= offset) { - String whiteSpace = spaceNode.getText().substring(0, offset - spaceNode.getStartOffset()); - if (!StringUtil.containsLineBreak(whiteSpace)) { - // There is a possible case that the caret is located at the end of the line that already contains expression, say, we - // want to override particular method while caret is located after the field. - // Example - consider that we want to override toString() method at the class below: - // class Test { - // int i; - // } - // We want to add line feed then in order to avoid situation like below: - // class Test { - // int i;@Override String toString() { - // super.toString(); - // } - // } - whiteSpace += "\n"; - } - final PsiParserFacade parserFacade = PsiParserFacade.SERVICE.getInstance(file.getProject()); - final ASTNode singleNewLineWhitespace = parserFacade.createWhiteSpaceFromText(whiteSpace).getNode(); - if (singleNewLineWhitespace != null) { - spaceNode.getTreeParent().replaceChild(spaceNode, singleNewLineWhitespace); // See http://jetbrains.net/jira/browse/IDEADEV-12837 - } - } - } - - // Q: shouldn't it be somewhere in PSI? - PsiElement element = anchor; - while (true) { - if (element == null) { - break; - } - if (element instanceof PsiField || element instanceof PsiMethod || element instanceof PsiClassInitializer) { - break; - } - element = element.getNextSibling(); - } - if (element instanceof PsiField) { - PsiField field = (PsiField)element; - PsiTypeElement typeElement = field.getTypeElement(); - if (typeElement != null && !field.equals(typeElement.getParent())) { - field.normalizeDeclaration(); - anchor = field; - } - } - - return insertMembersBeforeAnchor(aClass, anchor, memberPrototypes); - } - - @Nonnull - public static List insertMembersBeforeAnchor(PsiClass aClass, - @Nullable PsiElement anchor, - @Nonnull List memberPrototypes) throws IncorrectOperationException { - boolean before = true; - for (T memberPrototype : memberPrototypes) { - memberPrototype.insert(aClass, anchor, before); - before = false; - anchor = memberPrototype.getPsiMember(); - } - return memberPrototypes; - } - - /** - * @see GenerationInfo#positionCaret(Editor, boolean) - */ - public static void positionCaret(@Nonnull Editor editor, @Nonnull PsiElement firstMember, boolean toEditMethodBody) { - LOG.assertTrue(firstMember.isValid()); - Project project = firstMember.getProject(); - - if (toEditMethodBody) { - PsiMethod method = (PsiMethod)firstMember; - PsiCodeBlock body = method.getBody(); - if (body != null) { - PsiElement firstBodyElement = body.getFirstBodyElement(); - PsiElement l = firstBodyElement; - while (l instanceof PsiWhiteSpace) { - l = l.getNextSibling(); - } - if (l == null) { - l = body; - } - PsiElement lastBodyElement = body.getLastBodyElement(); - PsiElement r = lastBodyElement; - while (r instanceof PsiWhiteSpace) { - r = r.getPrevSibling(); - } - if (r == null) { - r = body; - } - - int start = l.getTextRange().getStartOffset(); - int end = r.getTextRange().getEndOffset(); - - boolean adjustLineIndent = false; - - // body is whitespace - if (start > end && - firstBodyElement == lastBodyElement && - firstBodyElement instanceof PsiWhiteSpaceImpl) { - CharSequence chars = ((PsiWhiteSpaceImpl)firstBodyElement).getChars(); - if (chars.length() > 1 && chars.charAt(0) == '\n' && chars.charAt(1) == '\n') { - start = end = firstBodyElement.getTextRange().getStartOffset() + 1; - adjustLineIndent = true; - } - } - - editor.getCaretModel().moveToOffset(Math.min(start, end)); - editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); - if (start < end) { - //Not an empty body - editor.getSelectionModel().setSelection(start, end); - } - else if (adjustLineIndent) { - Document document = editor.getDocument(); - RangeMarker marker = document.createRangeMarker(start, start); - PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(document); - if (marker.isValid()) { - CodeStyleManager.getInstance(project).adjustLineIndent(document, marker.getStartOffset()); - } - } - return; - } - } - - int offset; - if (firstMember instanceof PsiMethod) { - PsiMethod method = (PsiMethod)firstMember; - PsiCodeBlock body = method.getBody(); - if (body == null) { - offset = method.getTextRange().getStartOffset(); - } - else { - PsiJavaToken lBrace = body.getLBrace(); - assert lBrace != null : firstMember.getText(); - offset = lBrace.getTextRange().getEndOffset(); - } - } - else { - offset = firstMember.getTextRange().getStartOffset(); - } - - editor.getCaretModel().moveToOffset(offset); - editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); - editor.getSelectionModel().removeSelection(); - } - - public static PsiElement insert(@Nonnull PsiClass aClass, - @Nonnull PsiMember member, - @Nullable PsiElement anchor, - boolean before) throws IncorrectOperationException { - if (member instanceof PsiMethod) { - if (!aClass.isInterface()) { - final PsiParameter[] parameters = ((PsiMethod)member).getParameterList().getParameters(); - final boolean generateFinals = CodeStyleSettingsManager.getSettings(aClass.getProject()).GENERATE_FINAL_PARAMETERS; - for (final PsiParameter parameter : parameters) { - final PsiModifierList modifierList = parameter.getModifierList(); - assert modifierList != null; - modifierList.setModifierProperty(PsiModifier.FINAL, generateFinals); - } - } - } - - if (anchor != null) { - return before ? aClass.addBefore(member, anchor) : aClass.addAfter(member, anchor); - } - else { - return aClass.add(member); - } - } - - @Nullable - private static PsiClass findClassAtOffset(PsiFile file, PsiElement leaf) { - PsiElement element = leaf; - while (element != null && !(element instanceof PsiFile)) { - if (element instanceof PsiClass && !(element instanceof PsiTypeParameter)) { - final PsiClass psiClass = (PsiClass)element; - if (psiClass.isEnum()) { - PsiElement lastChild = null; - for (PsiElement child : psiClass.getChildren()) { - if (child instanceof PsiJavaToken && ";".equals(child.getText())) { - lastChild = child; - break; + private static final Logger LOG = Logger.getInstance(GenerateMembersUtil.class); + + private GenerateMembersUtil() { + } + + @Nonnull + public static List insertMembersAtOffset(PsiFile file, int offset, @Nonnull List memberPrototypes) + throws IncorrectOperationException { + if (memberPrototypes.isEmpty()) { + return memberPrototypes; + } + final PsiElement leaf = file.findElementAt(offset); + if (leaf == null) { + return Collections.emptyList(); + } + + PsiClass aClass = findClassAtOffset(file, leaf); + if (aClass == null) { + return Collections.emptyList(); + } + PsiElement anchor = memberPrototypes.get(0).findInsertionAnchor(aClass, leaf); + + if (anchor instanceof PsiWhiteSpace) { + final ASTNode spaceNode = anchor.getNode(); + anchor = anchor.getNextSibling(); + + assert spaceNode != null; + if (spaceNode.getStartOffset() <= offset && spaceNode.getStartOffset() + spaceNode.getTextLength() >= offset) { + String whiteSpace = spaceNode.getText().substring(0, offset - spaceNode.getStartOffset()); + if (!StringUtil.containsLineBreak(whiteSpace)) { + // There is a possible case that the caret is located at the end of the line that already contains expression, say, we + // want to override particular method while caret is located after the field. + // Example - consider that we want to override toString() method at the class below: + // class Test { + // int i; + // } + // We want to add line feed then in order to avoid situation like below: + // class Test { + // int i;@Override String toString() { + // super.toString(); + // } + // } + whiteSpace += "\n"; + } + final PsiParserFacade parserFacade = PsiParserFacade.SERVICE.getInstance(file.getProject()); + final ASTNode singleNewLineWhitespace = parserFacade.createWhiteSpaceFromText(whiteSpace).getNode(); + if (singleNewLineWhitespace != null) { + spaceNode.getTreeParent() + .replaceChild(spaceNode, singleNewLineWhitespace); // See http://jetbrains.net/jira/browse/IDEADEV-12837 + } + } + } + + // Q: shouldn't it be somewhere in PSI? + PsiElement element = anchor; + while (true) { + if (element == null) { + break; + } + if (element instanceof PsiField || element instanceof PsiMethod || element instanceof PsiClassInitializer) { + break; + } + element = element.getNextSibling(); + } + if (element instanceof PsiField) { + PsiField field = (PsiField)element; + PsiTypeElement typeElement = field.getTypeElement(); + if (typeElement != null && !field.equals(typeElement.getParent())) { + field.normalizeDeclaration(); + anchor = field; + } + } + + return insertMembersBeforeAnchor(aClass, anchor, memberPrototypes); + } + + @Nonnull + public static List insertMembersBeforeAnchor( + PsiClass aClass, + @Nullable PsiElement anchor, + @Nonnull List memberPrototypes + ) throws IncorrectOperationException { + boolean before = true; + for (T memberPrototype : memberPrototypes) { + memberPrototype.insert(aClass, anchor, before); + before = false; + anchor = memberPrototype.getPsiMember(); + } + return memberPrototypes; + } + + /** + * @see GenerationInfo#positionCaret(Editor, boolean) + */ + public static void positionCaret(@Nonnull Editor editor, @Nonnull PsiElement firstMember, boolean toEditMethodBody) { + LOG.assertTrue(firstMember.isValid()); + Project project = firstMember.getProject(); + + if (toEditMethodBody) { + PsiMethod method = (PsiMethod)firstMember; + PsiCodeBlock body = method.getBody(); + if (body != null) { + PsiElement firstBodyElement = body.getFirstBodyElement(); + PsiElement l = firstBodyElement; + while (l instanceof PsiWhiteSpace) { + l = l.getNextSibling(); + } + if (l == null) { + l = body; + } + PsiElement lastBodyElement = body.getLastBodyElement(); + PsiElement r = lastBodyElement; + while (r instanceof PsiWhiteSpace) { + r = r.getPrevSibling(); + } + if (r == null) { + r = body; + } + + int start = l.getTextRange().getStartOffset(); + int end = r.getTextRange().getEndOffset(); + + boolean adjustLineIndent = false; + + // body is whitespace + if (start > end && + firstBodyElement == lastBodyElement && + firstBodyElement instanceof PsiWhiteSpaceImpl) { + CharSequence chars = ((PsiWhiteSpaceImpl)firstBodyElement).getChars(); + if (chars.length() > 1 && chars.charAt(0) == '\n' && chars.charAt(1) == '\n') { + start = end = firstBodyElement.getTextRange().getStartOffset() + 1; + adjustLineIndent = true; + } + } + + editor.getCaretModel().moveToOffset(Math.min(start, end)); + editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); + if (start < end) { + //Not an empty body + editor.getSelectionModel().setSelection(start, end); + } + else if (adjustLineIndent) { + Document document = editor.getDocument(); + RangeMarker marker = document.createRangeMarker(start, start); + PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(document); + if (marker.isValid()) { + CodeStyleManager.getInstance(project).adjustLineIndent(document, marker.getStartOffset()); + } + } + return; } - else if (child instanceof PsiJavaToken && ",".equals(child.getText()) || child instanceof PsiEnumConstant) { - lastChild = child; + } + + int offset; + if (firstMember instanceof PsiMethod) { + PsiMethod method = (PsiMethod)firstMember; + PsiCodeBlock body = method.getBody(); + if (body == null) { + offset = method.getTextRange().getStartOffset(); } - } - if (lastChild != null) { - int adjustedOffset = lastChild.getTextRange().getEndOffset(); - if (leaf.getTextRange().getEndOffset() <= adjustedOffset) { - return findClassAtOffset(file, file.findElementAt(adjustedOffset)); + else { + PsiJavaToken lBrace = body.getLBrace(); + assert lBrace != null : firstMember.getText(); + offset = lBrace.getTextRange().getEndOffset(); } - } - } - return psiClass; - } - element = element.getParent(); - } - return null; - } - - public static PsiMethod substituteGenericMethod(PsiMethod method, final PsiSubstitutor substitutor) { - return substituteGenericMethod(method, substitutor, null); - } - - public static PsiMethod substituteGenericMethod(@Nonnull PsiMethod sourceMethod, - @Nonnull PsiSubstitutor substitutor, - @Nullable PsiElement target) { - final Project project = sourceMethod.getProject(); - final JVMElementFactory factory = getFactory(sourceMethod.getProject(), target); - final JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(project); - - try { - final PsiMethod resultMethod = createMethod(factory, sourceMethod, target); - copyModifiers(sourceMethod.getModifierList(), resultMethod.getModifierList()); - final PsiSubstitutor collisionResolvedSubstitutor = - substituteTypeParameters(factory, target, sourceMethod.getTypeParameterList(), resultMethod.getTypeParameterList(), substitutor, - sourceMethod); - substituteReturnType(PsiManager.getInstance(project), resultMethod, sourceMethod.getReturnType(), collisionResolvedSubstitutor); - substituteParameters(factory, - codeStyleManager, - sourceMethod.getParameterList(), - resultMethod.getParameterList(), - collisionResolvedSubstitutor, - target); - copyDocComment(sourceMethod, resultMethod, factory); - GlobalSearchScope scope = sourceMethod.getResolveScope(); - final List thrownTypes = - ExceptionUtil.collectSubstituted(collisionResolvedSubstitutor, sourceMethod.getThrowsList().getReferencedTypes(), scope); - if (target instanceof PsiClass) { - final PsiMethod[] methods = ((PsiClass)target).findMethodsBySignature(sourceMethod, true); - for (PsiMethod psiMethod : methods) { - if (psiMethod != null && psiMethod != sourceMethod) { - PsiClass aSuper = psiMethod.getContainingClass(); - if (aSuper != null && aSuper != target) { - PsiSubstitutor superClassSubstitutor = - TypeConversionUtil.getSuperClassSubstitutor(aSuper, (PsiClass)target, PsiSubstitutor.EMPTY); - ExceptionUtil.retainExceptions(thrownTypes, - ExceptionUtil.collectSubstituted(superClassSubstitutor, - psiMethod.getThrowsList().getReferencedTypes(), - scope)); + } + else { + offset = firstMember.getTextRange().getStartOffset(); + } + + editor.getCaretModel().moveToOffset(offset); + editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); + editor.getSelectionModel().removeSelection(); + } + + public static PsiElement insert( + @Nonnull PsiClass aClass, + @Nonnull PsiMember member, + @Nullable PsiElement anchor, + boolean before + ) throws IncorrectOperationException { + if (member instanceof PsiMethod) { + if (!aClass.isInterface()) { + final PsiParameter[] parameters = ((PsiMethod)member).getParameterList().getParameters(); + final boolean generateFinals = CodeStyleSettingsManager.getSettings(aClass.getProject()).GENERATE_FINAL_PARAMETERS; + for (final PsiParameter parameter : parameters) { + final PsiModifierList modifierList = parameter.getModifierList(); + assert modifierList != null; + modifierList.setModifierProperty(PsiModifier.FINAL, generateFinals); + } } - } - } - } - substituteThrows(factory, resultMethod.getThrowsList(), collisionResolvedSubstitutor, sourceMethod, thrownTypes); - return resultMethod; - } - catch (IncorrectOperationException e) { - LOG.error(e); - return sourceMethod; - } - } - - private static void copyModifiers(@Nonnull PsiModifierList sourceModifierList, @Nonnull PsiModifierList targetModifierList) { - VisibilityUtil.setVisibility(targetModifierList, VisibilityUtil.getVisibilityModifier(sourceModifierList)); - } - - @Nonnull - private static PsiSubstitutor substituteTypeParameters(@Nonnull JVMElementFactory factory, - @Nullable PsiElement target, - @Nullable PsiTypeParameterList sourceTypeParameterList, - @Nullable PsiTypeParameterList targetTypeParameterList, - @Nonnull PsiSubstitutor substitutor, - @Nonnull PsiMethod sourceMethod) { - if (sourceTypeParameterList == null || targetTypeParameterList == null || PsiUtil.isRawSubstitutor(sourceMethod, substitutor)) { - return substitutor; - } - - final Map substitutionMap = new HashMap<>(substitutor.getSubstitutionMap()); - for (PsiTypeParameter typeParam : sourceTypeParameterList.getTypeParameters()) { - final PsiTypeParameter substitutedTypeParam = substituteTypeParameter(factory, typeParam, substitutor, sourceMethod); - - final PsiTypeParameter resolvedTypeParam = resolveTypeParametersCollision(factory, sourceTypeParameterList, target, - substitutedTypeParam, substitutor); - targetTypeParameterList.add(resolvedTypeParam); - if (substitutedTypeParam != resolvedTypeParam) { - substitutionMap.put(typeParam, factory.createType(resolvedTypeParam)); - } - } - return substitutionMap.isEmpty() ? substitutor : factory.createSubstitutor(substitutionMap); - } - - @Nonnull - @RequiredReadAction - private static PsiTypeParameter resolveTypeParametersCollision(@Nonnull JVMElementFactory factory, - @Nonnull PsiTypeParameterList sourceTypeParameterList, - @Nullable PsiElement target, - @Nonnull PsiTypeParameter typeParam, - @Nonnull PsiSubstitutor substitutor) { - String typeParamName = typeParam.getName(); - for (PsiType type : substitutor.getSubstitutionMap().values()) { - if (type != null && Objects.equals(type.getCanonicalText(), typeParamName)) { - final String newName = suggestUniqueTypeParameterName(typeParamName, - sourceTypeParameterList, - PsiTreeUtil.getParentOfType(target, PsiClass.class, false)); - final PsiTypeParameter newTypeParameter = factory.createTypeParameter(newName, typeParam.getSuperTypes()); - substitutor.put(typeParam, factory.createType(newTypeParameter)); - return newTypeParameter; - } - } - return factory.createTypeParameter(typeParamName, typeParam.getSuperTypes()); - } - - @Nonnull - private static String suggestUniqueTypeParameterName(String baseName, - @Nonnull PsiTypeParameterList typeParameterList, - @Nullable PsiClass targetClass) { - int i = 0; - while (true) { - final String newName = baseName + ++i; - if (checkUniqueTypeParameterName(newName, typeParameterList) && (targetClass == null || checkUniqueTypeParameterName(newName, - targetClass.getTypeParameterList()))) { - return newName; - } - } - } - - - private static boolean checkUniqueTypeParameterName(@Nonnull String baseName, @Nullable PsiTypeParameterList typeParameterList) { - if (typeParameterList == null) { - return true; - } - - for (PsiTypeParameter typeParameter : typeParameterList.getTypeParameters()) { - if (Comparing.equal(typeParameter.getName(), baseName)) { - return false; - } - } - return true; - } - - - @Nonnull - private static PsiTypeParameter substituteTypeParameter(final @Nonnull JVMElementFactory factory, - @Nonnull PsiTypeParameter typeParameter, - final @Nonnull PsiSubstitutor substitutor, - @Nonnull final PsiMethod sourceMethod) { - if (typeParameter instanceof LightElement) { - List substitutedSupers = - ContainerUtil.map(typeParameter.getSuperTypes(), t -> ObjectUtil.notNull(toClassType(substitutor.substitute(t)), t)); - return factory.createTypeParameter(Objects.requireNonNull(typeParameter.getName()), - substitutedSupers.toArray(PsiClassType.EMPTY_ARRAY)); - } - final PsiElement copy = - ObjectUtil.notNull(typeParameter instanceof PsiCompiledElement ? ((PsiCompiledElement)typeParameter).getMirror() : typeParameter, - typeParameter).copy(); - LOG.assertTrue(copy != null, typeParameter); - final Map replacementMap = new HashMap<>(); - copy.accept(new JavaRecursiveElementVisitor() { - @Override - public void visitReferenceElement(@Nonnull PsiJavaCodeReferenceElement reference) { - super.visitReferenceElement(reference); - final PsiElement resolve = reference.resolve(); - if (resolve instanceof PsiTypeParameter) { - final PsiType type = factory.createType((PsiTypeParameter)resolve); - replacementMap.put(reference, - factory.createReferenceElementByType((PsiClassType)substituteType(substitutor, type, sourceMethod, null))); - } - } - }); - return (PsiTypeParameter)RefactoringUtil.replaceElementsWithMap(copy, replacementMap); - } - - private static PsiClassType toClassType(PsiType type) { - if (type instanceof PsiClassType) { - return (PsiClassType)type; - } - if (type instanceof PsiCapturedWildcardType) { - return toClassType(((PsiCapturedWildcardType)type).getUpperBound()); - } - if (type instanceof PsiWildcardType) { - return toClassType(((PsiWildcardType)type).getBound()); - } - return null; - } - - private static void substituteParameters(@Nonnull JVMElementFactory factory, - @Nonnull JavaCodeStyleManager codeStyleManager, - @Nonnull PsiParameterList sourceParameterList, - @Nonnull PsiParameterList targetParameterList, - @Nonnull PsiSubstitutor substitutor, - PsiElement target) { - final PsiParameter[] parameters = sourceParameterList.getParameters(); - final PsiParameter[] newParameters = overriddenParameters(parameters, factory, codeStyleManager, substitutor, target); - for (int i = 0; i < newParameters.length; i++) { - final PsiParameter newParameter = newParameters[i]; - copyOrReplaceModifierList(parameters[i], newParameter); - targetParameterList.add(newParameter); - } - } - - public static PsiParameter[] overriddenParameters(PsiParameter[] parameters, - @Nonnull JVMElementFactory factory, - @Nonnull JavaCodeStyleManager codeStyleManager, - @Nonnull PsiSubstitutor substitutor, - PsiElement target) { - PsiParameter[] result = new PsiParameter[parameters.length]; - UniqueNameGenerator generator = new UniqueNameGenerator(); - - for (int i = 0; i < parameters.length; i++) { - PsiParameter parameter = parameters[i]; - final PsiType parameterType = parameter.getType(); - PsiElement declarationScope = parameter.getDeclarationScope(); - PsiType substituted = declarationScope instanceof PsiTypeParameterListOwner ? substituteType(substitutor, - parameterType, - (PsiTypeParameterListOwner)declarationScope, - parameter.getModifierList()) - : parameterType; - String paramName = parameter.getName(); - boolean isBaseNameGenerated = true; - final boolean isSubstituted = substituted.equals(parameterType); - if (!isSubstituted && isBaseNameGenerated(codeStyleManager, TypeConversionUtil.erasure(parameterType), paramName)) { - isBaseNameGenerated = false; - } - - if (paramName == null || - isBaseNameGenerated && !isSubstituted && isBaseNameGenerated(codeStyleManager, parameterType, paramName) || - !factory.isValidParameterName(paramName)) { - String[] names = codeStyleManager.suggestVariableName(VariableKind.PARAMETER, null, null, substituted).names; - if (names.length > 0) { - paramName = generator.generateUniqueName(names[0]); + } + + if (anchor != null) { + return before ? aClass.addBefore(member, anchor) : aClass.addAfter(member, anchor); } else { - paramName = generator.generateUniqueName("p"); - } - } - else if (!generator.test(paramName)) { - paramName = generator.generateUniqueName(paramName); - } - generator.addExistingName(paramName); - result[i] = factory.createParameter(paramName, substituted, target); - } - return result; - } - - private static void substituteThrows(@Nonnull JVMElementFactory factory, - @Nonnull PsiReferenceList targetThrowsList, - @Nonnull PsiSubstitutor substitutor, - @Nonnull PsiMethod sourceMethod, - List thrownTypes) { - for (PsiClassType thrownType : thrownTypes) { - targetThrowsList.add(factory.createReferenceElementByType((PsiClassType)substituteType(substitutor, thrownType, sourceMethod, null))); - } - } - - @RequiredReadAction - private static void copyDocComment(PsiMethod source, PsiMethod target, JVMElementFactory factory) { - final PsiElement navigationElement = source.getNavigationElement(); - if (navigationElement instanceof PsiDocCommentOwner) { - final PsiDocComment docComment = ((PsiDocCommentOwner)navigationElement).getDocComment(); - if (docComment != null) { - target.addAfter(factory.createDocCommentFromText(docComment.getText()), null); - } - } - final PsiParameter[] sourceParameters = source.getParameterList().getParameters(); - final PsiParameterList targetParameterList = target.getParameterList(); - RefactoringUtil.fixJavadocsForParams(target, new HashSet<>(Arrays.asList(targetParameterList.getParameters())), new Condition<>() { - @Override - public boolean value(Pair pair) { - final int parameterIndex = targetParameterList.getParameterIndex(pair.first); - if (parameterIndex >= 0 && parameterIndex < sourceParameters.length) { - return Comparing.strEqual(pair.second, sourceParameters[parameterIndex].getName()); - } - return false; - } - }); - } - - @Nonnull - private static PsiMethod createMethod(@Nonnull JVMElementFactory factory, @Nonnull PsiMethod method, PsiElement target) { - if (method.isConstructor()) { - return factory.createConstructor(method.getName(), target); - } - return factory.createMethod(method.getName(), PsiType.VOID, target); - } - - private static void substituteReturnType(@Nonnull PsiManager manager, - @Nonnull PsiMethod method, - @Nullable PsiType returnType, - @Nonnull PsiSubstitutor substitutor) { - final PsiTypeElement returnTypeElement = method.getReturnTypeElement(); - if (returnTypeElement == null || returnType == null) { - return; - } - final PsiType substitutedReturnType = substituteType(substitutor, returnType, method, method.getModifierList()); - - returnTypeElement.replace(new LightTypeElement(manager, - substitutedReturnType instanceof PsiWildcardType ? TypeConversionUtil.erasure( - substitutedReturnType) : substitutedReturnType)); - } - - @Nonnull - private static JVMElementFactory getFactory(@Nonnull Project p, @Nullable PsiElement target) { - return target == null ? JavaPsiFacade.getInstance(p).getElementFactory() : JVMElementFactories.requireFactory(target.getLanguage(), p); - } - - private static boolean isBaseNameGenerated(JavaCodeStyleManager csManager, PsiType parameterType, String paramName) { - if (Arrays.asList(csManager.suggestVariableName(VariableKind.PARAMETER, null, null, parameterType).names).contains(paramName)) { - return true; - } - final String typeName = JavaCodeStyleManagerImpl.getTypeName(parameterType); - return typeName != null && NameUtil.getSuggestionsByName(typeName, "", "", false, false, parameterType instanceof PsiArrayType) - .contains(paramName); - } - - private static PsiType substituteType(final PsiSubstitutor substitutor, - final PsiType type, - @Nonnull PsiTypeParameterListOwner owner, - PsiModifierList modifierList) { - PsiType substitutedType = PsiUtil.isRawSubstitutor(owner, substitutor) - ? TypeConversionUtil.erasure(type) - : GenericsUtil.eliminateWildcards(substitutor.substitute(type), false, true); - return substitutedType != null ? AnnotationTargetUtil.keepStrictlyTypeUseAnnotations(modifierList, substitutedType) : null; - } - - public static boolean isChildInRange(PsiElement child, PsiElement first, PsiElement last) { - if (child.equals(first)) { - return true; - } - while (true) { - if (child.equals(first)) { - return false; // before first - } - if (child.equals(last)) { + return aClass.add(member); + } + } + + @Nullable + private static PsiClass findClassAtOffset(PsiFile file, PsiElement leaf) { + PsiElement element = leaf; + while (element != null && !(element instanceof PsiFile)) { + if (element instanceof PsiClass && !(element instanceof PsiTypeParameter)) { + final PsiClass psiClass = (PsiClass)element; + if (psiClass.isEnum()) { + PsiElement lastChild = null; + for (PsiElement child : psiClass.getChildren()) { + if (child instanceof PsiJavaToken && ";".equals(child.getText())) { + lastChild = child; + break; + } + else if (child instanceof PsiJavaToken && ",".equals(child.getText()) || child instanceof PsiEnumConstant) { + lastChild = child; + } + } + if (lastChild != null) { + int adjustedOffset = lastChild.getTextRange().getEndOffset(); + if (leaf.getTextRange().getEndOffset() <= adjustedOffset) { + return findClassAtOffset(file, file.findElementAt(adjustedOffset)); + } + } + } + return psiClass; + } + element = element.getParent(); + } + return null; + } + + public static PsiMethod substituteGenericMethod(PsiMethod method, final PsiSubstitutor substitutor) { + return substituteGenericMethod(method, substitutor, null); + } + + public static PsiMethod substituteGenericMethod( + @Nonnull PsiMethod sourceMethod, + @Nonnull PsiSubstitutor substitutor, + @Nullable PsiElement target + ) { + final Project project = sourceMethod.getProject(); + final JVMElementFactory factory = getFactory(sourceMethod.getProject(), target); + final JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(project); + + try { + final PsiMethod resultMethod = createMethod(factory, sourceMethod, target); + copyModifiers(sourceMethod.getModifierList(), resultMethod.getModifierList()); + final PsiSubstitutor collisionResolvedSubstitutor = substituteTypeParameters( + factory, + target, + sourceMethod.getTypeParameterList(), + resultMethod.getTypeParameterList(), + substitutor, + sourceMethod + ); + substituteReturnType(PsiManager.getInstance(project), resultMethod, sourceMethod.getReturnType(), collisionResolvedSubstitutor); + substituteParameters( + factory, + codeStyleManager, + sourceMethod.getParameterList(), + resultMethod.getParameterList(), + collisionResolvedSubstitutor, + target + ); + copyDocComment(sourceMethod, resultMethod, factory); + GlobalSearchScope scope = sourceMethod.getResolveScope(); + final List thrownTypes = + ExceptionUtil.collectSubstituted(collisionResolvedSubstitutor, sourceMethod.getThrowsList().getReferencedTypes(), scope); + if (target instanceof PsiClass) { + final PsiMethod[] methods = ((PsiClass)target).findMethodsBySignature(sourceMethod, true); + for (PsiMethod psiMethod : methods) { + if (psiMethod != null && psiMethod != sourceMethod) { + PsiClass aSuper = psiMethod.getContainingClass(); + if (aSuper != null && aSuper != target) { + PsiSubstitutor superClassSubstitutor = + TypeConversionUtil.getSuperClassSubstitutor(aSuper, (PsiClass)target, PsiSubstitutor.EMPTY); + ExceptionUtil.retainExceptions( + thrownTypes, + ExceptionUtil.collectSubstituted( + superClassSubstitutor, + psiMethod.getThrowsList().getReferencedTypes(), + scope + ) + ); + } + } + } + } + substituteThrows(factory, resultMethod.getThrowsList(), collisionResolvedSubstitutor, sourceMethod, thrownTypes); + return resultMethod; + } + catch (IncorrectOperationException e) { + LOG.error(e); + return sourceMethod; + } + } + + private static void copyModifiers(@Nonnull PsiModifierList sourceModifierList, @Nonnull PsiModifierList targetModifierList) { + VisibilityUtil.setVisibility(targetModifierList, VisibilityUtil.getVisibilityModifier(sourceModifierList)); + } + + @Nonnull + private static PsiSubstitutor substituteTypeParameters( + @Nonnull JVMElementFactory factory, + @Nullable PsiElement target, + @Nullable PsiTypeParameterList sourceTypeParameterList, + @Nullable PsiTypeParameterList targetTypeParameterList, + @Nonnull PsiSubstitutor substitutor, + @Nonnull PsiMethod sourceMethod + ) { + if (sourceTypeParameterList == null || targetTypeParameterList == null || PsiUtil.isRawSubstitutor(sourceMethod, substitutor)) { + return substitutor; + } + + final Map substitutionMap = new HashMap<>(substitutor.getSubstitutionMap()); + for (PsiTypeParameter typeParam : sourceTypeParameterList.getTypeParameters()) { + final PsiTypeParameter substitutedTypeParam = substituteTypeParameter(factory, typeParam, substitutor, sourceMethod); + + final PsiTypeParameter resolvedTypeParam = + resolveTypeParametersCollision(factory, sourceTypeParameterList, target, substitutedTypeParam, substitutor); + targetTypeParameterList.add(resolvedTypeParam); + if (substitutedTypeParam != resolvedTypeParam) { + substitutionMap.put(typeParam, factory.createType(resolvedTypeParam)); + } + } + return substitutionMap.isEmpty() ? substitutor : factory.createSubstitutor(substitutionMap); + } + + @Nonnull + @RequiredReadAction + private static PsiTypeParameter resolveTypeParametersCollision( + @Nonnull JVMElementFactory factory, + @Nonnull PsiTypeParameterList sourceTypeParameterList, + @Nullable PsiElement target, + @Nonnull PsiTypeParameter typeParam, + @Nonnull PsiSubstitutor substitutor + ) { + String typeParamName = typeParam.getName(); + for (PsiType type : substitutor.getSubstitutionMap().values()) { + if (type != null && Objects.equals(type.getCanonicalText(), typeParamName)) { + final String newName = suggestUniqueTypeParameterName( + typeParamName, + sourceTypeParameterList, + PsiTreeUtil.getParentOfType(target, PsiClass.class, false) + ); + final PsiTypeParameter newTypeParameter = factory.createTypeParameter(newName, typeParam.getSuperTypes()); + substitutor.put(typeParam, factory.createType(newTypeParameter)); + return newTypeParameter; + } + } + return factory.createTypeParameter(typeParamName, typeParam.getSuperTypes()); + } + + @Nonnull + private static String suggestUniqueTypeParameterName( + String baseName, + @Nonnull PsiTypeParameterList typeParameterList, + @Nullable PsiClass targetClass + ) { + int i = 0; + while (true) { + final String newName = baseName + ++i; + if (checkUniqueTypeParameterName(newName, typeParameterList) + && (targetClass == null || checkUniqueTypeParameterName(newName, targetClass.getTypeParameterList()))) { + return newName; + } + } + } + + + private static boolean checkUniqueTypeParameterName(@Nonnull String baseName, @Nullable PsiTypeParameterList typeParameterList) { + if (typeParameterList == null) { + return true; + } + + for (PsiTypeParameter typeParameter : typeParameterList.getTypeParameters()) { + if (Comparing.equal(typeParameter.getName(), baseName)) { + return false; + } + } return true; - } - child = child.getNextSibling(); - if (child == null) { - return false; - } - } - } - - public static void setupGeneratedMethod(PsiMethod method) { - PsiClass containingClass = method.getContainingClass(); - PsiClass base = containingClass == null ? null : containingClass.getSuperClass(); - PsiMethod overridden = base == null ? null : base.findMethodBySignature(method, true); - - boolean emptyTemplate = true; - PsiCodeBlock body = method.getBody(); - if (body != null) { - PsiJavaToken lBrace = body.getLBrace(); - int left = lBrace != null ? lBrace.getStartOffsetInParent() + 1 : 0; - PsiJavaToken rBrace = body.getRBrace(); - int right = rBrace != null ? rBrace.getStartOffsetInParent() : body.getTextLength(); - emptyTemplate = StringUtil.isEmptyOrSpaces(body.getText().substring(left, right)); - } - - if (overridden == null) { - if (emptyTemplate) { - CreateFromUsageUtils.setupMethodBody(method, containingClass); - } - return; - } - - if (emptyTemplate) { - OverrideImplementUtil.setupMethodBody(method, overridden, containingClass); - } - OverrideImplementUtil.annotateOnOverrideImplement(method, base, overridden); - } - - public static void copyOrReplaceModifierList(@Nonnull PsiModifierListOwner sourceParam, @Nonnull PsiModifierListOwner targetParam) { - PsiModifierList sourceModifierList = sourceParam.getModifierList(); - PsiModifierList targetModifierList = targetParam.getModifierList(); - - if (sourceModifierList != null && targetModifierList != null) { - final Module module = ModuleUtilCore.findModuleForPsiElement(targetModifierList); - final GlobalSearchScope moduleScope = module != null ? GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module) : null; - final Project project = targetModifierList.getProject(); - final JavaPsiFacade facade = JavaPsiFacade.getInstance(project); - JVMElementFactory factory = JVMElementFactories.requireFactory(targetParam.getLanguage(), targetParam.getProject()); - for (PsiAnnotation annotation : AnnotationUtil.getAllAnnotations(sourceParam, false, null, false)) { - final String qualifiedName = annotation.getQualifiedName(); - if (qualifiedName != null && (moduleScope == null || facade.findClass(qualifiedName, moduleScope) != null) && - !AnnotationTargetUtil.isTypeAnnotation(annotation)) { - targetModifierList.add(factory.createAnnotationFromText(annotation.getText(), sourceParam)); - } - } - for (@PsiModifier.ModifierConstant String m : PsiModifier.MODIFIERS) { - targetModifierList.setModifierProperty(m, sourceParam.hasModifierProperty(m)); - } - - filterAnnotations(sourceModifierList.getProject(), targetModifierList, targetModifierList.getResolveScope()); - } - } - - private static void filterAnnotations(Project project, PsiModifierList modifierList, GlobalSearchScope moduleScope) { - Set toRemove = new HashSet<>(); - JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); - for (PsiAnnotation annotation : modifierList.getAnnotations()) { - String qualifiedName = annotation.getQualifiedName(); - if (qualifiedName != null) { - for (OverrideImplementsAnnotationsHandler handler : OverrideImplementsAnnotationsHandler.EP_NAME.getExtensionList()) { - String[] annotations2Remove = handler.annotationsToRemove(project, qualifiedName); - Collections.addAll(toRemove, annotations2Remove); - if (moduleScope != null && psiFacade.findClass(qualifiedName, moduleScope) == null) { - toRemove.add(qualifiedName); - } - } - } - } - for (String fqn : toRemove) { - PsiAnnotation psiAnnotation = modifierList.findAnnotation(fqn); - if (psiAnnotation != null) { - psiAnnotation.delete(); - } - } - } - - //java bean getters/setters - public static PsiMethod generateSimpleGetterPrototype(@Nonnull PsiField field) { - return generatePrototype(field, PropertyUtil.generateGetterPrototype(field)); - } - - public static PsiMethod generateSimpleSetterPrototype(@Nonnull PsiField field) { - return generatePrototype(field, PropertyUtil.generateSetterPrototype(field)); - } - - public static PsiMethod generateSimpleSetterPrototype(PsiField field, PsiClass targetClass) { - return generatePrototype(field, PropertyUtil.generateSetterPrototype(field, targetClass)); - } - - //custom getters/setters - public static String suggestGetterName(PsiField field) { - final PsiMethod prototype = generateGetterPrototype(field); - return prototype != null ? prototype.getName() : PropertyUtil.suggestGetterName(field); - } - - public static String suggestGetterName(String name, PsiType type, Project project) { - return suggestGetterName(JavaPsiFacade.getElementFactory(project) - .createField(name, - type instanceof PsiEllipsisType ? ((PsiEllipsisType)type).toArrayType() : type)); - } - - public static String suggestSetterName(PsiField field) { - final PsiMethod prototype = generateSetterPrototype(field); - return prototype != null ? prototype.getName() : PropertyUtil.suggestSetterName(field); - } - - public static String suggestSetterName(String name, PsiType type, Project project) { - return suggestSetterName(JavaPsiFacade.getElementFactory(project) - .createField(name, - type instanceof PsiEllipsisType ? ((PsiEllipsisType)type).toArrayType() : type)); - } - - public static PsiMethod generateGetterPrototype(@Nonnull PsiField field) { - return generateGetterPrototype(field, true); - } - - public static PsiMethod generateSetterPrototype(@Nonnull PsiField field) { - return generateSetterPrototype(field, true); - } - - public static PsiMethod generateSetterPrototype(@Nonnull PsiField field, PsiClass aClass) { - return generatePrototype(field, aClass, true, SetterTemplatesManager.getInstance()); - } - - static PsiMethod generateGetterPrototype(@Nonnull PsiField field, boolean ignoreInvalidTemplate) { - return generatePrototype(field, field.getContainingClass(), ignoreInvalidTemplate, GetterTemplatesManager.getInstance()); - } - - static PsiMethod generateSetterPrototype(@Nonnull PsiField field, boolean ignoreInvalidTemplate) { - return generatePrototype(field, field.getContainingClass(), ignoreInvalidTemplate, SetterTemplatesManager.getInstance()); - } - - private static PsiMethod generatePrototype(@Nonnull PsiField field, - PsiClass psiClass, - boolean ignoreInvalidTemplate, - TemplatesManager templatesManager) { - Project project = field.getProject(); - PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory(); - String template = templatesManager.getDefaultTemplate().getTemplate(); - String methodText = - GenerationUtil.velocityGenerateCode(psiClass, Collections.singletonList(field), new HashMap<>(), template, 0, false); - - boolean isGetter = templatesManager instanceof GetterTemplatesManager; - PsiMethod result; - try { - result = factory.createMethodFromText(methodText, psiClass); - } - catch (IncorrectOperationException e) { - if (ignoreInvalidTemplate) { - LOG.info(e); - result = isGetter ? PropertyUtil.generateGetterPrototype(field) : PropertyUtil.generateSetterPrototype(field); - assert result != null : field.getText(); - } - else { - throw new GenerateCodeException(e); - } - } - result = (PsiMethod)CodeStyleManager.getInstance(project).reformat(result); - - PsiModifierListOwner annotationTarget; - if (isGetter) { - annotationTarget = result; - } - else { - final PsiParameter[] parameters = result.getParameterList().getParameters(); - annotationTarget = parameters.length == 1 ? parameters[0] : null; - } - if (annotationTarget != null) { - NullableNotNullManager.getInstance(project).copyNullableOrNotNullAnnotation(field, annotationTarget); - } - - return generatePrototype(field, result); - } - - @Nullable - private static PsiMethod generatePrototype(@Nonnull PsiField field, PsiMethod result) { - return setVisibility(field, annotateOnOverrideImplement(field.getContainingClass(), result)); - } - - @Contract("_, null -> null") - public static PsiMethod setVisibility(PsiMember member, PsiMethod prototype) { - if (prototype == null) { - return null; - } - - String visibility = CodeStyleSettingsManager.getSettings(member.getProject()).VISIBILITY; - - @PsiModifier.ModifierConstant String newVisibility; - if (VisibilityUtil.ESCALATE_VISIBILITY.equals(visibility)) { - PsiClass aClass = member instanceof PsiClass ? (PsiClass)member : member.getContainingClass(); - newVisibility = PsiUtil.getMaximumModifierForMember(aClass, false); - } - else { - //noinspection MagicConstant - newVisibility = visibility; - } - VisibilityUtil.setVisibility(prototype.getModifierList(), newVisibility); - - return prototype; - } + } + + + @Nonnull + private static PsiTypeParameter substituteTypeParameter( + final @Nonnull JVMElementFactory factory, + @Nonnull PsiTypeParameter typeParameter, + final @Nonnull PsiSubstitutor substitutor, + @Nonnull final PsiMethod sourceMethod + ) { + if (typeParameter instanceof LightElement) { + List substitutedSupers = + ContainerUtil.map(typeParameter.getSuperTypes(), t -> ObjectUtil.notNull(toClassType(substitutor.substitute(t)), t)); + return factory.createTypeParameter( + Objects.requireNonNull(typeParameter.getName()), + substitutedSupers.toArray(PsiClassType.EMPTY_ARRAY) + ); + } + final PsiElement copy = ObjectUtil.notNull( + typeParameter instanceof PsiCompiledElement + ? ((PsiCompiledElement)typeParameter).getMirror() + : typeParameter, + typeParameter + ).copy(); + LOG.assertTrue(copy != null, typeParameter); + final Map replacementMap = new HashMap<>(); + copy.accept(new JavaRecursiveElementVisitor() { + @Override + public void visitReferenceElement(@Nonnull PsiJavaCodeReferenceElement reference) { + super.visitReferenceElement(reference); + final PsiElement resolve = reference.resolve(); + if (resolve instanceof PsiTypeParameter) { + final PsiType type = factory.createType((PsiTypeParameter)resolve); + replacementMap.put( + reference, + factory.createReferenceElementByType((PsiClassType)substituteType(substitutor, type, sourceMethod, null)) + ); + } + } + }); + return (PsiTypeParameter)RefactoringUtil.replaceElementsWithMap(copy, replacementMap); + } + + private static PsiClassType toClassType(PsiType type) { + if (type instanceof PsiClassType) { + return (PsiClassType)type; + } + if (type instanceof PsiCapturedWildcardType) { + return toClassType(((PsiCapturedWildcardType)type).getUpperBound()); + } + if (type instanceof PsiWildcardType) { + return toClassType(((PsiWildcardType)type).getBound()); + } + return null; + } + + private static void substituteParameters( + @Nonnull JVMElementFactory factory, + @Nonnull JavaCodeStyleManager codeStyleManager, + @Nonnull PsiParameterList sourceParameterList, + @Nonnull PsiParameterList targetParameterList, + @Nonnull PsiSubstitutor substitutor, + PsiElement target + ) { + final PsiParameter[] parameters = sourceParameterList.getParameters(); + final PsiParameter[] newParameters = overriddenParameters(parameters, factory, codeStyleManager, substitutor, target); + for (int i = 0; i < newParameters.length; i++) { + final PsiParameter newParameter = newParameters[i]; + copyOrReplaceModifierList(parameters[i], newParameter); + targetParameterList.add(newParameter); + } + } + + public static PsiParameter[] overriddenParameters( + PsiParameter[] parameters, + @Nonnull JVMElementFactory factory, + @Nonnull JavaCodeStyleManager codeStyleManager, + @Nonnull PsiSubstitutor substitutor, + PsiElement target + ) { + PsiParameter[] result = new PsiParameter[parameters.length]; + UniqueNameGenerator generator = new UniqueNameGenerator(); + + for (int i = 0; i < parameters.length; i++) { + PsiParameter parameter = parameters[i]; + final PsiType parameterType = parameter.getType(); + PsiElement declarationScope = parameter.getDeclarationScope(); + PsiType substituted = declarationScope instanceof PsiTypeParameterListOwner + ? substituteType(substitutor, parameterType, (PsiTypeParameterListOwner)declarationScope, parameter.getModifierList()) + : parameterType; + String paramName = parameter.getName(); + boolean isBaseNameGenerated = true; + final boolean isSubstituted = substituted.equals(parameterType); + if (!isSubstituted && isBaseNameGenerated(codeStyleManager, TypeConversionUtil.erasure(parameterType), paramName)) { + isBaseNameGenerated = false; + } + + if (paramName == null || + isBaseNameGenerated && !isSubstituted && isBaseNameGenerated(codeStyleManager, parameterType, paramName) || + !factory.isValidParameterName(paramName)) { + String[] names = codeStyleManager.suggestVariableName(VariableKind.PARAMETER, null, null, substituted).names; + if (names.length > 0) { + paramName = generator.generateUniqueName(names[0]); + } + else { + paramName = generator.generateUniqueName("p"); + } + } + else if (!generator.test(paramName)) { + paramName = generator.generateUniqueName(paramName); + } + generator.addExistingName(paramName); + result[i] = factory.createParameter(paramName, substituted, target); + } + return result; + } + + private static void substituteThrows( + @Nonnull JVMElementFactory factory, + @Nonnull PsiReferenceList targetThrowsList, + @Nonnull PsiSubstitutor substitutor, + @Nonnull PsiMethod sourceMethod, + List thrownTypes + ) { + for (PsiClassType thrownType : thrownTypes) { + targetThrowsList.add(factory.createReferenceElementByType((PsiClassType)substituteType( + substitutor, + thrownType, + sourceMethod, + null + ))); + } + } + + @RequiredReadAction + private static void copyDocComment(PsiMethod source, PsiMethod target, JVMElementFactory factory) { + final PsiElement navigationElement = source.getNavigationElement(); + if (navigationElement instanceof PsiDocCommentOwner) { + final PsiDocComment docComment = ((PsiDocCommentOwner)navigationElement).getDocComment(); + if (docComment != null) { + target.addAfter(factory.createDocCommentFromText(docComment.getText()), null); + } + } + final PsiParameter[] sourceParameters = source.getParameterList().getParameters(); + final PsiParameterList targetParameterList = target.getParameterList(); + RefactoringUtil.fixJavadocsForParams(target, new HashSet<>(Arrays.asList(targetParameterList.getParameters())), new Condition<>() { + @Override + public boolean value(Pair pair) { + final int parameterIndex = targetParameterList.getParameterIndex(pair.first); + if (parameterIndex >= 0 && parameterIndex < sourceParameters.length) { + return Comparing.strEqual(pair.second, sourceParameters[parameterIndex].getName()); + } + return false; + } + }); + } + + @Nonnull + private static PsiMethod createMethod(@Nonnull JVMElementFactory factory, @Nonnull PsiMethod method, PsiElement target) { + if (method.isConstructor()) { + return factory.createConstructor(method.getName(), target); + } + return factory.createMethod(method.getName(), PsiType.VOID, target); + } + + private static void substituteReturnType( + @Nonnull PsiManager manager, + @Nonnull PsiMethod method, + @Nullable PsiType returnType, + @Nonnull PsiSubstitutor substitutor + ) { + final PsiTypeElement returnTypeElement = method.getReturnTypeElement(); + if (returnTypeElement == null || returnType == null) { + return; + } + final PsiType substitutedReturnType = substituteType(substitutor, returnType, method, method.getModifierList()); + + returnTypeElement.replace(new LightTypeElement( + manager, + substitutedReturnType instanceof PsiWildcardType + ? TypeConversionUtil.erasure(substitutedReturnType) + : substitutedReturnType + )); + } + + @Nonnull + private static JVMElementFactory getFactory(@Nonnull Project p, @Nullable PsiElement target) { + return target == null + ? JavaPsiFacade.getInstance(p).getElementFactory() + : JVMElementFactories.requireFactory(target.getLanguage(), p); + } + + private static boolean isBaseNameGenerated(JavaCodeStyleManager csManager, PsiType parameterType, String paramName) { + if (Arrays.asList(csManager.suggestVariableName(VariableKind.PARAMETER, null, null, parameterType).names).contains(paramName)) { + return true; + } + final String typeName = JavaCodeStyleManagerImpl.getTypeName(parameterType); + return typeName != null + && NameUtil.getSuggestionsByName(typeName, "", "", false, false, parameterType instanceof PsiArrayType).contains(paramName); + } + + private static PsiType substituteType( + final PsiSubstitutor substitutor, + final PsiType type, + @Nonnull PsiTypeParameterListOwner owner, + PsiModifierList modifierList + ) { + PsiType substitutedType = PsiUtil.isRawSubstitutor(owner, substitutor) + ? TypeConversionUtil.erasure(type) + : GenericsUtil.eliminateWildcards(substitutor.substitute(type), false, true); + return substitutedType != null ? AnnotationTargetUtil.keepStrictlyTypeUseAnnotations(modifierList, substitutedType) : null; + } + + public static boolean isChildInRange(PsiElement child, PsiElement first, PsiElement last) { + if (child.equals(first)) { + return true; + } + while (true) { + if (child.equals(first)) { + return false; // before first + } + if (child.equals(last)) { + return true; + } + child = child.getNextSibling(); + if (child == null) { + return false; + } + } + } + + public static void setupGeneratedMethod(PsiMethod method) { + PsiClass containingClass = method.getContainingClass(); + PsiClass base = containingClass == null ? null : containingClass.getSuperClass(); + PsiMethod overridden = base == null ? null : base.findMethodBySignature(method, true); + + boolean emptyTemplate = true; + PsiCodeBlock body = method.getBody(); + if (body != null) { + PsiJavaToken lBrace = body.getLBrace(); + int left = lBrace != null ? lBrace.getStartOffsetInParent() + 1 : 0; + PsiJavaToken rBrace = body.getRBrace(); + int right = rBrace != null ? rBrace.getStartOffsetInParent() : body.getTextLength(); + emptyTemplate = StringUtil.isEmptyOrSpaces(body.getText().substring(left, right)); + } + + if (overridden == null) { + if (emptyTemplate) { + CreateFromUsageUtils.setupMethodBody(method, containingClass); + } + return; + } + + if (emptyTemplate) { + OverrideImplementUtil.setupMethodBody(method, overridden, containingClass); + } + OverrideImplementUtil.annotateOnOverrideImplement(method, base, overridden); + } + + public static void copyOrReplaceModifierList(@Nonnull PsiModifierListOwner sourceParam, @Nonnull PsiModifierListOwner targetParam) { + PsiModifierList sourceModifierList = sourceParam.getModifierList(); + PsiModifierList targetModifierList = targetParam.getModifierList(); + + if (sourceModifierList != null && targetModifierList != null) { + final Module module = ModuleUtilCore.findModuleForPsiElement(targetModifierList); + final GlobalSearchScope moduleScope = module != null ? GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module) : null; + final Project project = targetModifierList.getProject(); + final JavaPsiFacade facade = JavaPsiFacade.getInstance(project); + JVMElementFactory factory = JVMElementFactories.requireFactory(targetParam.getLanguage(), targetParam.getProject()); + for (PsiAnnotation annotation : AnnotationUtil.getAllAnnotations(sourceParam, false, null, false)) { + final String qualifiedName = annotation.getQualifiedName(); + if (qualifiedName != null && (moduleScope == null || facade.findClass(qualifiedName, moduleScope) != null) && + !AnnotationTargetUtil.isTypeAnnotation(annotation)) { + targetModifierList.add(factory.createAnnotationFromText(annotation.getText(), sourceParam)); + } + } + for (@PsiModifier.ModifierConstant String m : PsiModifier.MODIFIERS) { + targetModifierList.setModifierProperty(m, sourceParam.hasModifierProperty(m)); + } + + filterAnnotations(sourceModifierList.getProject(), targetModifierList, targetModifierList.getResolveScope()); + } + } + + private static void filterAnnotations(Project project, PsiModifierList modifierList, GlobalSearchScope moduleScope) { + Set toRemove = new HashSet<>(); + JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project); + for (PsiAnnotation annotation : modifierList.getAnnotations()) { + String qualifiedName = annotation.getQualifiedName(); + if (qualifiedName != null) { + for (OverrideImplementsAnnotationsHandler handler : OverrideImplementsAnnotationsHandler.EP_NAME.getExtensionList()) { + String[] annotations2Remove = handler.annotationsToRemove(project, qualifiedName); + Collections.addAll(toRemove, annotations2Remove); + if (moduleScope != null && psiFacade.findClass(qualifiedName, moduleScope) == null) { + toRemove.add(qualifiedName); + } + } + } + } + for (String fqn : toRemove) { + PsiAnnotation psiAnnotation = modifierList.findAnnotation(fqn); + if (psiAnnotation != null) { + psiAnnotation.delete(); + } + } + } + + //java bean getters/setters + public static PsiMethod generateSimpleGetterPrototype(@Nonnull PsiField field) { + return generatePrototype(field, PropertyUtil.generateGetterPrototype(field)); + } + + public static PsiMethod generateSimpleSetterPrototype(@Nonnull PsiField field) { + return generatePrototype(field, PropertyUtil.generateSetterPrototype(field)); + } + + public static PsiMethod generateSimpleSetterPrototype(PsiField field, PsiClass targetClass) { + return generatePrototype(field, PropertyUtil.generateSetterPrototype(field, targetClass)); + } + + //custom getters/setters + public static String suggestGetterName(PsiField field) { + final PsiMethod prototype = generateGetterPrototype(field); + return prototype != null ? prototype.getName() : PropertyUtil.suggestGetterName(field); + } + + public static String suggestGetterName(String name, PsiType type, Project project) { + return suggestGetterName(JavaPsiFacade.getElementFactory(project).createField( + name, + type instanceof PsiEllipsisType ? ((PsiEllipsisType)type).toArrayType() : type + )); + } + + public static String suggestSetterName(PsiField field) { + final PsiMethod prototype = generateSetterPrototype(field); + return prototype != null ? prototype.getName() : PropertyUtil.suggestSetterName(field); + } + + public static String suggestSetterName(String name, PsiType type, Project project) { + return suggestSetterName(JavaPsiFacade.getElementFactory(project).createField( + name, + type instanceof PsiEllipsisType ? ((PsiEllipsisType)type).toArrayType() : type + )); + } + + public static PsiMethod generateGetterPrototype(@Nonnull PsiField field) { + return generateGetterPrototype(field, true); + } + + public static PsiMethod generateSetterPrototype(@Nonnull PsiField field) { + return generateSetterPrototype(field, true); + } + + public static PsiMethod generateSetterPrototype(@Nonnull PsiField field, PsiClass aClass) { + return generatePrototype(field, aClass, true, SetterTemplatesManager.getInstance()); + } + + static PsiMethod generateGetterPrototype(@Nonnull PsiField field, boolean ignoreInvalidTemplate) { + return generatePrototype(field, field.getContainingClass(), ignoreInvalidTemplate, GetterTemplatesManager.getInstance()); + } + + static PsiMethod generateSetterPrototype(@Nonnull PsiField field, boolean ignoreInvalidTemplate) { + return generatePrototype(field, field.getContainingClass(), ignoreInvalidTemplate, SetterTemplatesManager.getInstance()); + } + + private static PsiMethod generatePrototype( + @Nonnull PsiField field, + PsiClass psiClass, + boolean ignoreInvalidTemplate, + TemplatesManager templatesManager + ) { + Project project = field.getProject(); + PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory(); + String template = templatesManager.getDefaultTemplate().getTemplate(); + String methodText = + GenerationUtil.velocityGenerateCode(psiClass, Collections.singletonList(field), new HashMap<>(), template, 0, false); + + boolean isGetter = templatesManager instanceof GetterTemplatesManager; + PsiMethod result; + try { + result = factory.createMethodFromText(methodText, psiClass); + } + catch (IncorrectOperationException e) { + if (ignoreInvalidTemplate) { + LOG.info(e); + result = isGetter ? PropertyUtil.generateGetterPrototype(field) : PropertyUtil.generateSetterPrototype(field); + assert result != null : field.getText(); + } + else { + throw new GenerateCodeException(e); + } + } + result = (PsiMethod)CodeStyleManager.getInstance(project).reformat(result); - @Nullable - public static PsiMethod annotateOnOverrideImplement(@Nullable PsiClass targetClass, @Nullable PsiMethod generated) { - if (generated == null || targetClass == null) { - return generated; + PsiModifierListOwner annotationTarget; + if (isGetter) { + annotationTarget = result; + } + else { + final PsiParameter[] parameters = result.getParameterList().getParameters(); + annotationTarget = parameters.length == 1 ? parameters[0] : null; + } + if (annotationTarget != null) { + NullableNotNullManager.getInstance(project).copyNullableOrNotNullAnnotation(field, annotationTarget); + } + + return generatePrototype(field, result); } - - if (CodeStyleSettingsManager.getSettings(targetClass.getProject()).INSERT_OVERRIDE_ANNOTATION) { - PsiMethod superMethod = targetClass.findMethodBySignature(generated, true); - if (superMethod != null && superMethod.getContainingClass() != targetClass) { - OverrideImplementUtil.annotateOnOverrideImplement(generated, targetClass, superMethod, true); - } + + @Nullable + private static PsiMethod generatePrototype(@Nonnull PsiField field, PsiMethod result) { + return setVisibility(field, annotateOnOverrideImplement(field.getContainingClass(), result)); + } + + @Contract("_, null -> null") + public static PsiMethod setVisibility(PsiMember member, PsiMethod prototype) { + if (prototype == null) { + return null; + } + + String visibility = CodeStyleSettingsManager.getSettings(member.getProject()).VISIBILITY; + + @PsiModifier.ModifierConstant String newVisibility; + if (VisibilityUtil.ESCALATE_VISIBILITY.equals(visibility)) { + PsiClass aClass = member instanceof PsiClass ? (PsiClass)member : member.getContainingClass(); + newVisibility = PsiUtil.getMaximumModifierForMember(aClass, false); + } + else { + //noinspection MagicConstant + newVisibility = visibility; + } + VisibilityUtil.setVisibility(prototype.getModifierList(), newVisibility); + + return prototype; + } + + @Nullable + public static PsiMethod annotateOnOverrideImplement(@Nullable PsiClass targetClass, @Nullable PsiMethod generated) { + if (generated == null || targetClass == null) { + return generated; + } + + if (CodeStyleSettingsManager.getSettings(targetClass.getProject()).INSERT_OVERRIDE_ANNOTATION) { + PsiMethod superMethod = targetClass.findMethodBySignature(generated, true); + if (superMethod != null && superMethod.getContainingClass() != targetClass) { + OverrideImplementUtil.annotateOnOverrideImplement(generated, targetClass, superMethod, true); + } + } + return generated; } - return generated; - } } diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/GetterSetterPrototypeProvider.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/GetterSetterPrototypeProvider.java index 8fa1a41dc4..a35ca61da9 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/GetterSetterPrototypeProvider.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/GetterSetterPrototypeProvider.java @@ -31,77 +31,77 @@ */ @ExtensionAPI(ComponentScope.APPLICATION) public abstract class GetterSetterPrototypeProvider { - public static final ExtensionPointName EP_NAME = - ExtensionPointName.create(GetterSetterPrototypeProvider.class); + public static final ExtensionPointName EP_NAME = + ExtensionPointName.create(GetterSetterPrototypeProvider.class); - public abstract boolean canGeneratePrototypeFor(PsiField field); + public abstract boolean canGeneratePrototypeFor(PsiField field); - public abstract PsiMethod[] generateGetters(PsiField field); + public abstract PsiMethod[] generateGetters(PsiField field); - public abstract PsiMethod[] generateSetters(PsiField field); + public abstract PsiMethod[] generateSetters(PsiField field); - public PsiMethod[] findGetters(PsiClass psiClass, String propertyName) { - return null; - } - - public String suggestGetterName(String propertyName) { - return null; - } + public PsiMethod[] findGetters(PsiClass psiClass, String propertyName) { + return null; + } - public boolean isSimpleGetter(PsiMethod method, String oldPropertyName) { - return false; - } + public String suggestGetterName(String propertyName) { + return null; + } - public abstract boolean isReadOnly(PsiField field); + public boolean isSimpleGetter(PsiMethod method, String oldPropertyName) { + return false; + } - public static PsiMethod[] generateGetterSetters(PsiField field, boolean generateGetter) { - return generateGetterSetters(field, generateGetter, true); - } + public abstract boolean isReadOnly(PsiField field); - public static PsiMethod[] generateGetterSetters(PsiField field, boolean generateGetter, boolean invalidTemplate) { - for (GetterSetterPrototypeProvider provider : Extensions.getExtensions(EP_NAME)) { - if (provider.canGeneratePrototypeFor(field)) { - return generateGetter ? provider.generateGetters(field) : provider.generateSetters(field); - } + public static PsiMethod[] generateGetterSetters(PsiField field, boolean generateGetter) { + return generateGetterSetters(field, generateGetter, true); } - return new PsiMethod[]{ - generateGetter ? GenerateMembersUtil.generateGetterPrototype(field, invalidTemplate) : GenerateMembersUtil.generateSetterPrototype( - field, - invalidTemplate) - }; - } - public static boolean isReadOnlyProperty(PsiField field) { - for (GetterSetterPrototypeProvider provider : Extensions.getExtensions(EP_NAME)) { - if (provider.canGeneratePrototypeFor(field)) { - return provider.isReadOnly(field); - } + public static PsiMethod[] generateGetterSetters(PsiField field, boolean generateGetter, boolean invalidTemplate) { + for (GetterSetterPrototypeProvider provider : Extensions.getExtensions(EP_NAME)) { + if (provider.canGeneratePrototypeFor(field)) { + return generateGetter ? provider.generateGetters(field) : provider.generateSetters(field); + } + } + return new PsiMethod[]{ + generateGetter + ? GenerateMembersUtil.generateGetterPrototype(field, invalidTemplate) + : GenerateMembersUtil.generateSetterPrototype(field, invalidTemplate) + }; } - return field.hasModifierProperty(PsiModifier.FINAL); - } - public static PsiMethod[] findGetters(PsiClass aClass, String propertyName, boolean isStatic) { - if (!isStatic) { - for (GetterSetterPrototypeProvider provider : Extensions.getExtensions(EP_NAME)) { - final PsiMethod[] getterSetter = provider.findGetters(aClass, propertyName); - if (getterSetter != null) { - return getterSetter; + public static boolean isReadOnlyProperty(PsiField field) { + for (GetterSetterPrototypeProvider provider : Extensions.getExtensions(EP_NAME)) { + if (provider.canGeneratePrototypeFor(field)) { + return provider.isReadOnly(field); + } } - } + return field.hasModifierProperty(PsiModifier.FINAL); } - final PsiMethod propertyGetterSetter = PropertyUtil.findPropertyGetter(aClass, propertyName, isStatic, false); - if (propertyGetterSetter != null) { - return new PsiMethod[]{propertyGetterSetter}; + + public static PsiMethod[] findGetters(PsiClass aClass, String propertyName, boolean isStatic) { + if (!isStatic) { + for (GetterSetterPrototypeProvider provider : Extensions.getExtensions(EP_NAME)) { + final PsiMethod[] getterSetter = provider.findGetters(aClass, propertyName); + if (getterSetter != null) { + return getterSetter; + } + } + } + final PsiMethod propertyGetterSetter = PropertyUtil.findPropertyGetter(aClass, propertyName, isStatic, false); + if (propertyGetterSetter != null) { + return new PsiMethod[]{propertyGetterSetter}; + } + return null; } - return null; - } - public static String suggestNewGetterName(String oldPropertyName, String newPropertyName, PsiMethod method) { - for (GetterSetterPrototypeProvider provider : Extensions.getExtensions(EP_NAME)) { - if (provider.isSimpleGetter(method, oldPropertyName)) { - return provider.suggestGetterName(newPropertyName); - } + public static String suggestNewGetterName(String oldPropertyName, String newPropertyName, PsiMethod method) { + for (GetterSetterPrototypeProvider provider : Extensions.getExtensions(EP_NAME)) { + if (provider.isSimpleGetter(method, oldPropertyName)) { + return provider.suggestGetterName(newPropertyName); + } + } + return null; } - return null; - } } diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/OverrideImplementUtil.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/OverrideImplementUtil.java index 9c909da1cb..be31bf9fcc 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/OverrideImplementUtil.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/OverrideImplementUtil.java @@ -79,610 +79,677 @@ import java.util.function.Consumer; public class OverrideImplementUtil extends OverrideImplementExploreUtil { - private static final Logger LOG = Logger.getInstance(OverrideImplementUtil.class); - - private OverrideImplementUtil() { - } - - protected static List getImplementors() { - return Application.get().getExtensionList(MethodImplementor.class); - } - - /** - * generate methods (with bodies) corresponding to given method declaration - * there are maybe two method implementations for one declaration - * (e.g. EJB' create() -> ejbCreate(), ejbPostCreate() ) - * - * @param aClass context for method implementations - * @param method method to override or implement - * @param toCopyJavaDoc true if copy JavaDoc from method declaration - * @return list of method prototypes - */ - @Nonnull - public static List overrideOrImplementMethod(PsiClass aClass, PsiMethod method, boolean toCopyJavaDoc) throws IncorrectOperationException { - final PsiClass containingClass = method.getContainingClass(); - LOG.assertTrue(containingClass != null); - PsiSubstitutor substitutor = aClass.isInheritor(containingClass, true) - ? TypeConversionUtil.getSuperClassSubstitutor(containingClass, aClass, PsiSubstitutor.EMPTY) : PsiSubstitutor.EMPTY; - return overrideOrImplementMethod( - aClass, - method, - substitutor, - toCopyJavaDoc, - CodeStyleSettingsManager.getSettings(aClass.getProject()).INSERT_OVERRIDE_ANNOTATION - ); - } - - public static boolean isInsertOverride(PsiMethod superMethod, PsiClass targetClass) { - return CodeStyleSettingsManager.getSettings(targetClass.getProject()).INSERT_OVERRIDE_ANNOTATION - && canInsertOverride(superMethod, targetClass); - } - - public static boolean canInsertOverride(PsiMethod superMethod, PsiClass targetClass) { - if (superMethod.isConstructor() || superMethod.hasModifierProperty(PsiModifier.STATIC)) { - return false; - } - if (!PsiUtil.isLanguageLevel5OrHigher(targetClass)) { - return false; - } - if (PsiUtil.isLanguageLevel6OrHigher(targetClass)) { - return true; - } - PsiClass superClass = superMethod.getContainingClass(); - return superClass != null && !superClass.isInterface(); - } - - public static List overrideOrImplementMethod( - PsiClass aClass, - PsiMethod method, - PsiSubstitutor substitutor, - boolean toCopyJavaDoc, - boolean insertOverrideIfPossible - ) throws IncorrectOperationException { - if (!method.isValid() || !substitutor.isValid()) { - return Collections.emptyList(); - } - - List results = new ArrayList<>(); - for (final MethodImplementor implementor : getImplementors()) { - final PsiMethod[] prototypes = implementor.createImplementationPrototypes(aClass, method); - for (PsiMethod prototype : prototypes) { - implementor.createDecorator(aClass, method, toCopyJavaDoc, insertOverrideIfPossible).accept(prototype); - results.add(prototype); - } - } - - if (results.isEmpty()) { - PsiMethod method1 = GenerateMembersUtil.substituteGenericMethod(method, substitutor, aClass); - - PsiElementFactory factory = JavaPsiFacade.getInstance(method.getProject()).getElementFactory(); - PsiMethod result = (PsiMethod) factory.createClass("Dummy").add(method1); - if (PsiUtil.isAnnotationMethod(result)) { - PsiAnnotationMemberValue defaultValue = ((PsiAnnotationMethod) result).getDefaultValue(); - if (defaultValue != null) { - PsiElement defaultKeyword = defaultValue; - while (!(defaultKeyword instanceof PsiKeyword) && defaultKeyword != null) { - defaultKeyword = defaultKeyword.getPrevSibling(); - } - if (defaultKeyword == null) { - defaultKeyword = defaultValue; - } - defaultValue.getParent().deleteChildRange(defaultKeyword, defaultValue); - } - } - Consumer decorator = createDefaultDecorator(aClass, method, toCopyJavaDoc, insertOverrideIfPossible); - decorator.accept(result); - results.add(result); - } - - for (Iterator iterator = results.iterator(); iterator.hasNext(); ) { - if (aClass.findMethodBySignature(iterator.next(), false) != null) { - iterator.remove(); - } - } - - return results; - } - - public static Consumer createDefaultDecorator(final PsiClass aClass, final PsiMethod method, final boolean toCopyJavaDoc, final boolean insertOverrideIfPossible) { - return result -> decorateMethod(aClass, method, toCopyJavaDoc, insertOverrideIfPossible, result); - } - - private static PsiMethod decorateMethod(PsiClass aClass, PsiMethod method, boolean toCopyJavaDoc, boolean insertOverrideIfPossible, PsiMethod result) { - PsiUtil.setModifierProperty(result, PsiModifier.ABSTRACT, aClass.isInterface() && method.hasModifierProperty(PsiModifier.ABSTRACT)); - PsiUtil.setModifierProperty(result, PsiModifier.NATIVE, false); - - if (!toCopyJavaDoc) { - deleteDocComment(result); - } - - //method type params are not allowed when overriding from raw type - final PsiTypeParameterList list = result.getTypeParameterList(); - if (list != null) { - final PsiClass containingClass = method.getContainingClass(); - if (containingClass != null) { - for (PsiClassType classType : aClass.getSuperTypes()) { - if (InheritanceUtil.isInheritorOrSelf(PsiUtil.resolveClassInType(classType), containingClass, true) && classType.isRaw()) { - list.replace(JavaPsiFacade.getElementFactory(aClass.getProject()).createTypeParameterList()); - break; - } - } - } - } - - annotateOnOverrideImplement(result, aClass, method, insertOverrideIfPossible); - - if (CodeStyleSettingsManager.getSettings(aClass.getProject()).REPEAT_SYNCHRONIZED - && method.hasModifierProperty(PsiModifier.SYNCHRONIZED)) { - result.getModifierList().setModifierProperty(PsiModifier.SYNCHRONIZED, true); - } - - final PsiCodeBlock body = JavaPsiFacade.getInstance(method.getProject()).getElementFactory().createCodeBlockFromText("{}", null); - PsiCodeBlock oldBody = result.getBody(); - if (oldBody != null) { - oldBody.replace(body); - } else { - result.add(body); - } - - setupMethodBody(result, method, aClass); - - // probably, it's better to reformat the whole method - it can go from other style sources - final Project project = method.getProject(); - CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project); - CommonCodeStyleSettings javaSettings = CodeStyleSettingsManager.getSettings(project).getCommonSettings(JavaLanguage.INSTANCE); - boolean keepBreaks = javaSettings.KEEP_LINE_BREAKS; - javaSettings.KEEP_LINE_BREAKS = false; - result = (PsiMethod) JavaCodeStyleManager.getInstance(project).shortenClassReferences(result); - result = (PsiMethod) codeStyleManager.reformat(result); - javaSettings.KEEP_LINE_BREAKS = keepBreaks; - return result; - } - - public static void deleteDocComment(PsiMethod result) { - PsiDocComment comment = result.getDocComment(); - if (comment != null) { - comment.delete(); - } - } - - public static void annotateOnOverrideImplement(PsiMethod method, PsiClass targetClass, PsiMethod overridden) { - annotateOnOverrideImplement( - method, - targetClass, - overridden, - CodeStyleSettingsManager.getSettings(method.getProject()).INSERT_OVERRIDE_ANNOTATION - ); - } - - public static void annotateOnOverrideImplement(PsiMethod method, PsiClass targetClass, PsiMethod overridden, boolean insertOverride) { - if (insertOverride && canInsertOverride(overridden, targetClass)) { - final String overrideAnnotationName = Override.class.getName(); - if (!AnnotationUtil.isAnnotated(method, overrideAnnotationName, false, true)) { - AddAnnotationPsiFix.addPhysicalAnnotation(overrideAnnotationName, PsiNameValuePair.EMPTY_ARRAY, method.getModifierList()); - } - } - final Module module = ModuleUtilCore.findModuleForPsiElement(targetClass); - final GlobalSearchScope moduleScope = module != null ? GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module) : null; - final Project project = targetClass.getProject(); - final JavaPsiFacade facade = JavaPsiFacade.getInstance(project); - for (OverrideImplementsAnnotationsHandler each : Extensions.getExtensions(OverrideImplementsAnnotationsHandler.EP_NAME)) { - for (String annotation : each.getAnnotations(project)) { - if (moduleScope != null && facade.findClass(annotation, moduleScope) == null) { - continue; - } - if (AnnotationUtil.isAnnotated(overridden, annotation, false, false) && !AnnotationUtil.isAnnotated(method, annotation, false, false)) { - PsiAnnotation psiAnnotation = AnnotationUtil.findAnnotation(overridden, annotation); - if (psiAnnotation != null && AnnotationUtil.isInferredAnnotation(psiAnnotation)) { - continue; - } - - AddAnnotationPsiFix.removePhysicalAnnotations(method, each.annotationsToRemove(project, annotation)); - AddAnnotationPsiFix.addPhysicalAnnotation(annotation, PsiNameValuePair.EMPTY_ARRAY, method.getModifierList()); - } - } - } - } - - public static void annotate(@Nonnull PsiMethod result, String fqn, String... annosToRemove) throws IncorrectOperationException { - Project project = result.getProject(); - AddAnnotationFix fix = new AddAnnotationFix(fqn, result, annosToRemove); - if (fix.isAvailable(project, null, result.getContainingFile())) { - fix.invoke(project, null, result.getContainingFile()); - } - } - - @Nonnull - public static List> overrideOrImplementMethods( - PsiClass aClass, - Collection candidates, - boolean toCopyJavaDoc, - boolean toInsertAtOverride - ) throws IncorrectOperationException { - List candidateInfos = ContainerUtil.map2List(candidates, s -> new CandidateInfo(s.getElement(), s.getSubstitutor())); - final List methods = overrideOrImplementMethodCandidates(aClass, candidateInfos, toCopyJavaDoc, toInsertAtOverride); - return convert2GenerationInfos(methods); - } - - @Nonnull - public static List overrideOrImplementMethodCandidates( - PsiClass aClass, - Collection candidates, - boolean toCopyJavaDoc, - boolean insertOverrideWherePossible - ) throws IncorrectOperationException { - List result = new ArrayList<>(); - for (CandidateInfo candidateInfo : candidates) { - result.addAll(overrideOrImplementMethod(aClass, (PsiMethod) candidateInfo.getElement(), candidateInfo.getSubstitutor(), toCopyJavaDoc, insertOverrideWherePossible)); - } - return result; - } - - public static List> convert2GenerationInfos(final Collection methods) { - return ContainerUtil.map2List(methods, OverrideImplementUtil::createGenerationInfo); - } - - public static PsiGenerationInfo createGenerationInfo(PsiMethod s) { - return createGenerationInfo(s, true); - } - - public static PsiGenerationInfo createGenerationInfo(PsiMethod s, boolean mergeIfExists) { - for (MethodImplementor implementor : getImplementors()) { - final GenerationInfo info = implementor.createGenerationInfo(s, mergeIfExists); - if (info instanceof PsiGenerationInfo generationInfo) { - //noinspection unchecked - return generationInfo; - } - } - return new PsiGenerationInfo<>(s); - } - - @Nonnull - public static String callSuper(PsiMethod superMethod, PsiMethod overriding) { - @NonNls StringBuilder buffer = new StringBuilder(); - if (!superMethod.isConstructor() && !PsiType.VOID.equals(superMethod.getReturnType())) { - buffer.append("return "); - } - buffer.append("super"); - PsiParameter[] parameters = overriding.getParameterList().getParameters(); - if (!superMethod.isConstructor()) { - buffer.append("."); - buffer.append(superMethod.getName()); - } - buffer.append("("); - for (int i = 0; i < parameters.length; i++) { - String name = parameters[i].getName(); - if (i > 0) { - buffer.append(","); - } - buffer.append(name); - } - buffer.append(")"); - return buffer.toString(); - } - - public static void setupMethodBody(PsiMethod result, PsiMethod originalMethod, PsiClass targetClass) throws IncorrectOperationException { - boolean isAbstract = originalMethod.hasModifierProperty(PsiModifier.ABSTRACT) || originalMethod.hasModifierProperty(PsiModifier.DEFAULT); - String templateName = isAbstract ? JavaTemplateUtil.TEMPLATE_IMPLEMENTED_METHOD_BODY : JavaTemplateUtil.TEMPLATE_OVERRIDDEN_METHOD_BODY; - FileTemplate template = FileTemplateManager.getInstance(result.getProject()).getCodeTemplate(templateName); - setupMethodBody(result, originalMethod, targetClass, template); - } - - public static void setupMethodBody( - final PsiMethod result, - final PsiMethod originalMethod, - final PsiClass targetClass, - final FileTemplate template - ) throws IncorrectOperationException { - if (targetClass.isInterface()) { - if (isImplementInterfaceInJava8Interface(targetClass) || originalMethod.hasModifierProperty(PsiModifier.DEFAULT)) { - PsiUtil.setModifierProperty(result, PsiModifier.DEFAULT, true); - } else { - final PsiCodeBlock body = result.getBody(); - if (body != null) { - body.delete(); - } - } - } - FileType fileType = FileTypeManager.getInstance().getFileTypeByExtension(template.getExtension()); - PsiType returnType = result.getReturnType(); - if (returnType == null) { - returnType = PsiType.VOID; - } - Properties properties = FileTemplateManager.getInstance(result.getProject()).getDefaultProperties(); - properties.setProperty(FileTemplate.ATTRIBUTE_RETURN_TYPE, returnType.getPresentableText()); - properties.setProperty(FileTemplate.ATTRIBUTE_DEFAULT_RETURN_VALUE, PsiTypesUtil.getDefaultValueOfType(returnType)); - properties.setProperty(FileTemplate.ATTRIBUTE_CALL_SUPER, callSuper(originalMethod, result)); - JavaTemplateUtil.setClassAndMethodNameProperties(properties, targetClass, result); - - JVMElementFactory factory = JVMElementFactories.getFactory(targetClass.getLanguage(), originalMethod.getProject()); - if (factory == null) { - factory = JavaPsiFacade.getInstance(originalMethod.getProject()).getElementFactory(); - } - @NonNls String methodText; - - try { - methodText = "void foo () {\n" + template.getText(properties) + "\n}"; - methodText = FileTemplateUtil.indent(methodText, result.getProject(), fileType); - } catch (Exception e) { - throw new IncorrectOperationException("Failed to parse file template", e); - } - if (methodText != null) { - PsiMethod m; - try { - m = factory.createMethodFromText(methodText, originalMethod); - } catch (IncorrectOperationException e) { - Application.get().invokeLater(() -> Messages.showErrorDialog( - CodeInsightLocalize.overrideImplementBrokenFileTemplateMessage().get(), - CodeInsightLocalize.overrideImplementBrokenFileTemplateTitle().get() - )); - return; - } - PsiCodeBlock oldBody = result.getBody(); - if (oldBody != null) { - oldBody.replace(m.getBody()); - } - } - } - - private static boolean isImplementInterfaceInJava8Interface(PsiClass targetClass) { - if (!PsiUtil.isLanguageLevel8OrHigher(targetClass)) { - return false; - } - String commandName = CommandProcessor.getInstance().getCurrentCommandName(); - return commandName != null && StringUtil.containsIgnoreCase(commandName, "implement"); - } - - @RequiredReadAction - public static void chooseAndOverrideMethods(Project project, Editor editor, PsiClass aClass) { - FeatureUsageTracker.getInstance().triggerFeatureUsed(ProductivityFeatureNames.CODEASSISTS_OVERRIDE_IMPLEMENT); - chooseAndOverrideOrImplementMethods(project, editor, aClass, false); - } - - @RequiredReadAction - public static void chooseAndImplementMethods(Project project, Editor editor, PsiClass aClass) { - FeatureUsageTracker.getInstance().triggerFeatureUsed(ProductivityFeatureNames.CODEASSISTS_OVERRIDE_IMPLEMENT); - chooseAndOverrideOrImplementMethods(project, editor, aClass, true); - } - - @RequiredReadAction - public static void chooseAndOverrideOrImplementMethods(final Project project, final Editor editor, final PsiClass aClass, final boolean toImplement) { - LOG.assertTrue(aClass.isValid()); - project.getApplication().assertReadAccessAllowed(); - - Collection candidates = getMethodsToOverrideImplement(aClass, toImplement); - Collection secondary = toImplement || aClass.isInterface() ? new ArrayList<>() - : getMethodsToOverrideImplement(aClass, true); - - final MemberChooser chooser = showOverrideImplementChooser(editor, aClass, toImplement, candidates, secondary); - if (chooser == null) { - return; - } - - final List selectedElements = chooser.getSelectedElements(); - if (selectedElements == null || selectedElements.isEmpty()) { - return; - } - - LOG.assertTrue(aClass.isValid()); - new WriteCommandAction(project, aClass.getContainingFile()) { - @Override - protected void run(@Nonnull final Result result) throws Throwable { - overrideOrImplementMethodsInRightPlace(editor, aClass, selectedElements, chooser.isCopyJavadoc(), chooser.isInsertOverrideAnnotation()); - } - }.execute(); - } - - /** - * @param candidates, secondary should allow modifications - */ - @Nullable - public static MemberChooser showOverrideImplementChooser( - Editor editor, - final PsiElement aClass, - final boolean toImplement, - final Collection candidates, - Collection secondary - ) { - if (toImplement) { - for (Iterator iterator = candidates.iterator(); iterator.hasNext(); ) { - CandidateInfo candidate = iterator.next(); - PsiElement element = candidate.getElement(); - if (element instanceof PsiMethod && ((PsiMethod) element).hasModifierProperty(PsiModifier.DEFAULT)) { - iterator.remove(); - secondary.add(candidate); - } - } - } - - final JavaOverrideImplementMemberChooser chooser = JavaOverrideImplementMemberChooser.create(aClass, toImplement, candidates, secondary); - if (chooser == null) { - return null; - } - Project project = aClass.getProject(); - registerHandlerForComplementaryAction(project, editor, aClass, toImplement, chooser); - - if (project.getApplication().isUnitTestMode()) { - return chooser; - } - chooser.show(); - if (chooser.getExitCode() != DialogWrapper.OK_EXIT_CODE) { - return null; - } - - return chooser; - } - - private static void registerHandlerForComplementaryAction( - final Project project, - final Editor editor, - final PsiElement aClass, - final boolean toImplement, - final MemberChooser chooser - ) { - final JComponent preferredFocusedComponent = chooser.getPreferredFocusedComponent(); - final Keymap keymap = KeymapManager.getInstance().getActiveKeymap(); - - @NonNls final String s = toImplement ? "OverrideMethods" : "ImplementMethods"; - final Shortcut[] shortcuts = keymap.getShortcuts(s); - - if (shortcuts.length > 0 && shortcuts[0] instanceof KeyboardShortcut keyboardShortcut) { - preferredFocusedComponent.getInputMap().put(keyboardShortcut.getFirstKeyStroke(), s); - - preferredFocusedComponent.getActionMap().put(s, new AbstractAction() { - @Override - public void actionPerformed(final ActionEvent e) { - chooser.close(DialogWrapper.CANCEL_EXIT_CODE); - - // invoke later in order to close previous modal dialog - project.getApplication().invokeLater(() -> { - final CodeInsightActionHandler handler = toImplement ? new JavaOverrideMethodsHandler() : new JavaImplementMethodsHandler(); - handler.invoke(project, editor, aClass.getContainingFile()); - }); - } - }); - } - } - - @RequiredReadAction - public static void overrideOrImplementMethodsInRightPlace(Editor editor, PsiClass aClass, Collection candidates, boolean copyJavadoc, boolean insertOverrideWherePossible) { - try { - int offset = editor.getCaretModel().getOffset(); - PsiElement brace = aClass.getLBrace(); - if (brace == null) { - PsiClass psiClass = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory().createClass("X"); - brace = aClass.addRangeAfter(psiClass.getLBrace(), psiClass.getRBrace(), aClass.getLastChild()); - LOG.assertTrue(brace != null, aClass.getLastChild()); - } - - int lbraceOffset = brace.getTextOffset(); - List> resultMembers; - if (offset <= lbraceOffset || aClass.isEnum()) { - resultMembers = new ArrayList<>(); - for (PsiMethodMember candidate : candidates) { - Collection prototypes = - overrideOrImplementMethod(aClass, candidate.getElement(), candidate.getSubstitutor(), copyJavadoc, insertOverrideWherePossible); - List> infos = convert2GenerationInfos(prototypes); - for (PsiGenerationInfo info : infos) { - PsiElement anchor = getDefaultAnchorToOverrideOrImplement(aClass, candidate.getElement(), candidate.getSubstitutor()); - info.insert(aClass, anchor, true); - resultMembers.add(info); - } - } - } else { - List> prototypes = overrideOrImplementMethods(aClass, candidates, copyJavadoc, insertOverrideWherePossible); - resultMembers = GenerateMembersUtil.insertMembersAtOffset(aClass.getContainingFile(), offset, prototypes); - } - - if (!resultMembers.isEmpty()) { - resultMembers.get(0).positionCaret(editor, true); - } - } catch (IncorrectOperationException e) { - LOG.error(e); - } - } - - @Nullable - @RequiredReadAction - public static PsiElement getDefaultAnchorToOverrideOrImplement(PsiClass aClass, PsiMethod baseMethod, PsiSubstitutor substitutor) { - PsiMethod prevBaseMethod = PsiTreeUtil.getPrevSiblingOfType(baseMethod, PsiMethod.class); - while (prevBaseMethod != null) { - String name = prevBaseMethod.isConstructor() ? aClass.getName() : prevBaseMethod.getName(); - //Happens when aClass instanceof PsiAnonymousClass - if (name != null) { - MethodSignature signature = MethodSignatureUtil.createMethodSignature(name, prevBaseMethod.getParameterList(), prevBaseMethod.getTypeParameterList(), substitutor, - prevBaseMethod.isConstructor()); - PsiMethod prevMethod = MethodSignatureUtil.findMethodBySignature(aClass, signature, false); - if (prevMethod != null && prevMethod.isPhysical()) { - return prevMethod.getNextSibling(); - } - } - prevBaseMethod = PsiTreeUtil.getPrevSiblingOfType(prevBaseMethod, PsiMethod.class); - } - - PsiMethod nextBaseMethod = PsiTreeUtil.getNextSiblingOfType(baseMethod, PsiMethod.class); - while (nextBaseMethod != null) { - String name = nextBaseMethod.isConstructor() ? aClass.getName() : nextBaseMethod.getName(); - if (name != null) { - MethodSignature signature = MethodSignatureUtil.createMethodSignature(name, nextBaseMethod.getParameterList(), nextBaseMethod.getTypeParameterList(), substitutor, - nextBaseMethod.isConstructor()); - PsiMethod nextMethod = MethodSignatureUtil.findMethodBySignature(aClass, signature, false); - if (nextMethod != null && nextMethod.isPhysical()) { - return nextMethod; - } - } - nextBaseMethod = PsiTreeUtil.getNextSiblingOfType(nextBaseMethod, PsiMethod.class); - } - - return null; - } - - @RequiredReadAction - public static List> overrideOrImplement(PsiClass psiClass, @Nonnull PsiMethod baseMethod) throws IncorrectOperationException { - FileEditorManager fileEditorManager = FileEditorManager.getInstance(baseMethod.getProject()); - List> results = new ArrayList<>(); - try { - - List> prototypes = convert2GenerationInfos(overrideOrImplementMethod(psiClass, baseMethod, false)); - if (prototypes.isEmpty()) { + private static final Logger LOG = Logger.getInstance(OverrideImplementUtil.class); + + private OverrideImplementUtil() { + } + + protected static List getImplementors() { + return Application.get().getExtensionList(MethodImplementor.class); + } + + /** + * generate methods (with bodies) corresponding to given method declaration + * there are maybe two method implementations for one declaration + * (e.g. EJB' create() -> ejbCreate(), ejbPostCreate() ) + * + * @param aClass context for method implementations + * @param method method to override or implement + * @param toCopyJavaDoc true if copy JavaDoc from method declaration + * @return list of method prototypes + */ + @Nonnull + public static List overrideOrImplementMethod(PsiClass aClass, PsiMethod method, boolean toCopyJavaDoc) + throws IncorrectOperationException { + final PsiClass containingClass = method.getContainingClass(); + LOG.assertTrue(containingClass != null); + PsiSubstitutor substitutor = aClass.isInheritor(containingClass, true) + ? TypeConversionUtil.getSuperClassSubstitutor(containingClass, aClass, PsiSubstitutor.EMPTY) : PsiSubstitutor.EMPTY; + return overrideOrImplementMethod( + aClass, + method, + substitutor, + toCopyJavaDoc, + CodeStyleSettingsManager.getSettings(aClass.getProject()).INSERT_OVERRIDE_ANNOTATION + ); + } + + public static boolean isInsertOverride(PsiMethod superMethod, PsiClass targetClass) { + return CodeStyleSettingsManager.getSettings(targetClass.getProject()).INSERT_OVERRIDE_ANNOTATION + && canInsertOverride(superMethod, targetClass); + } + + public static boolean canInsertOverride(PsiMethod superMethod, PsiClass targetClass) { + if (superMethod.isConstructor() || superMethod.hasModifierProperty(PsiModifier.STATIC)) { + return false; + } + if (!PsiUtil.isLanguageLevel5OrHigher(targetClass)) { + return false; + } + if (PsiUtil.isLanguageLevel6OrHigher(targetClass)) { + return true; + } + PsiClass superClass = superMethod.getContainingClass(); + return superClass != null && !superClass.isInterface(); + } + + public static List overrideOrImplementMethod( + PsiClass aClass, + PsiMethod method, + PsiSubstitutor substitutor, + boolean toCopyJavaDoc, + boolean insertOverrideIfPossible + ) throws IncorrectOperationException { + if (!method.isValid() || !substitutor.isValid()) { + return Collections.emptyList(); + } + + List results = new ArrayList<>(); + for (final MethodImplementor implementor : getImplementors()) { + final PsiMethod[] prototypes = implementor.createImplementationPrototypes(aClass, method); + for (PsiMethod prototype : prototypes) { + implementor.createDecorator(aClass, method, toCopyJavaDoc, insertOverrideIfPossible).accept(prototype); + results.add(prototype); + } + } + + if (results.isEmpty()) { + PsiMethod method1 = GenerateMembersUtil.substituteGenericMethod(method, substitutor, aClass); + + PsiElementFactory factory = JavaPsiFacade.getInstance(method.getProject()).getElementFactory(); + PsiMethod result = (PsiMethod)factory.createClass("Dummy").add(method1); + if (PsiUtil.isAnnotationMethod(result)) { + PsiAnnotationMemberValue defaultValue = ((PsiAnnotationMethod)result).getDefaultValue(); + if (defaultValue != null) { + PsiElement defaultKeyword = defaultValue; + while (!(defaultKeyword instanceof PsiKeyword) && defaultKeyword != null) { + defaultKeyword = defaultKeyword.getPrevSibling(); + } + if (defaultKeyword == null) { + defaultKeyword = defaultValue; + } + defaultValue.getParent().deleteChildRange(defaultKeyword, defaultValue); + } + } + Consumer decorator = createDefaultDecorator(aClass, method, toCopyJavaDoc, insertOverrideIfPossible); + decorator.accept(result); + results.add(result); + } + + for (Iterator iterator = results.iterator(); iterator.hasNext(); ) { + if (aClass.findMethodBySignature(iterator.next(), false) != null) { + iterator.remove(); + } + } + + return results; + } + + public static Consumer createDefaultDecorator( + final PsiClass aClass, + final PsiMethod method, + final boolean toCopyJavaDoc, + final boolean insertOverrideIfPossible + ) { + return result -> decorateMethod(aClass, method, toCopyJavaDoc, insertOverrideIfPossible, result); + } + + private static PsiMethod decorateMethod( + PsiClass aClass, + PsiMethod method, + boolean toCopyJavaDoc, + boolean insertOverrideIfPossible, + PsiMethod result + ) { + PsiUtil.setModifierProperty(result, PsiModifier.ABSTRACT, aClass.isInterface() && method.hasModifierProperty(PsiModifier.ABSTRACT)); + PsiUtil.setModifierProperty(result, PsiModifier.NATIVE, false); + + if (!toCopyJavaDoc) { + deleteDocComment(result); + } + + //method type params are not allowed when overriding from raw type + final PsiTypeParameterList list = result.getTypeParameterList(); + if (list != null) { + final PsiClass containingClass = method.getContainingClass(); + if (containingClass != null) { + for (PsiClassType classType : aClass.getSuperTypes()) { + if (InheritanceUtil.isInheritorOrSelf(PsiUtil.resolveClassInType(classType), containingClass, true) + && classType.isRaw()) { + list.replace(JavaPsiFacade.getElementFactory(aClass.getProject()).createTypeParameterList()); + break; + } + } + } + } + + annotateOnOverrideImplement(result, aClass, method, insertOverrideIfPossible); + + if (CodeStyleSettingsManager.getSettings(aClass.getProject()).REPEAT_SYNCHRONIZED + && method.hasModifierProperty(PsiModifier.SYNCHRONIZED)) { + result.getModifierList().setModifierProperty(PsiModifier.SYNCHRONIZED, true); + } + + final PsiCodeBlock body = JavaPsiFacade.getInstance(method.getProject()).getElementFactory().createCodeBlockFromText("{}", null); + PsiCodeBlock oldBody = result.getBody(); + if (oldBody != null) { + oldBody.replace(body); + } + else { + result.add(body); + } + + setupMethodBody(result, method, aClass); + + // probably, it's better to reformat the whole method - it can go from other style sources + final Project project = method.getProject(); + CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project); + CommonCodeStyleSettings javaSettings = CodeStyleSettingsManager.getSettings(project).getCommonSettings(JavaLanguage.INSTANCE); + boolean keepBreaks = javaSettings.KEEP_LINE_BREAKS; + javaSettings.KEEP_LINE_BREAKS = false; + result = (PsiMethod)JavaCodeStyleManager.getInstance(project).shortenClassReferences(result); + result = (PsiMethod)codeStyleManager.reformat(result); + javaSettings.KEEP_LINE_BREAKS = keepBreaks; + return result; + } + + public static void deleteDocComment(PsiMethod result) { + PsiDocComment comment = result.getDocComment(); + if (comment != null) { + comment.delete(); + } + } + + public static void annotateOnOverrideImplement(PsiMethod method, PsiClass targetClass, PsiMethod overridden) { + annotateOnOverrideImplement( + method, + targetClass, + overridden, + CodeStyleSettingsManager.getSettings(method.getProject()).INSERT_OVERRIDE_ANNOTATION + ); + } + + public static void annotateOnOverrideImplement(PsiMethod method, PsiClass targetClass, PsiMethod overridden, boolean insertOverride) { + if (insertOverride && canInsertOverride(overridden, targetClass)) { + final String overrideAnnotationName = Override.class.getName(); + if (!AnnotationUtil.isAnnotated(method, overrideAnnotationName, false, true)) { + AddAnnotationPsiFix.addPhysicalAnnotation(overrideAnnotationName, PsiNameValuePair.EMPTY_ARRAY, method.getModifierList()); + } + } + final Module module = ModuleUtilCore.findModuleForPsiElement(targetClass); + final GlobalSearchScope moduleScope = module != null ? GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module) : null; + final Project project = targetClass.getProject(); + final JavaPsiFacade facade = JavaPsiFacade.getInstance(project); + for (OverrideImplementsAnnotationsHandler each : Extensions.getExtensions(OverrideImplementsAnnotationsHandler.EP_NAME)) { + for (String annotation : each.getAnnotations(project)) { + if (moduleScope != null && facade.findClass(annotation, moduleScope) == null) { + continue; + } + if (AnnotationUtil.isAnnotated(overridden, annotation, false, false) + && !AnnotationUtil.isAnnotated(method, annotation, false, false)) { + PsiAnnotation psiAnnotation = AnnotationUtil.findAnnotation(overridden, annotation); + if (psiAnnotation != null && AnnotationUtil.isInferredAnnotation(psiAnnotation)) { + continue; + } + + AddAnnotationPsiFix.removePhysicalAnnotations(method, each.annotationsToRemove(project, annotation)); + AddAnnotationPsiFix.addPhysicalAnnotation(annotation, PsiNameValuePair.EMPTY_ARRAY, method.getModifierList()); + } + } + } + } + + public static void annotate(@Nonnull PsiMethod result, String fqn, String... annosToRemove) throws IncorrectOperationException { + Project project = result.getProject(); + AddAnnotationFix fix = new AddAnnotationFix(fqn, result, annosToRemove); + if (fix.isAvailable(project, null, result.getContainingFile())) { + fix.invoke(project, null, result.getContainingFile()); + } + } + + @Nonnull + public static List> overrideOrImplementMethods( + PsiClass aClass, + Collection candidates, + boolean toCopyJavaDoc, + boolean toInsertAtOverride + ) throws IncorrectOperationException { + List candidateInfos = ContainerUtil.map2List(candidates, s -> new CandidateInfo(s.getElement(), s.getSubstitutor())); + final List methods = overrideOrImplementMethodCandidates(aClass, candidateInfos, toCopyJavaDoc, toInsertAtOverride); + return convert2GenerationInfos(methods); + } + + @Nonnull + public static List overrideOrImplementMethodCandidates( + PsiClass aClass, + Collection candidates, + boolean toCopyJavaDoc, + boolean insertOverrideWherePossible + ) throws IncorrectOperationException { + List result = new ArrayList<>(); + for (CandidateInfo candidateInfo : candidates) { + result.addAll(overrideOrImplementMethod( + aClass, + (PsiMethod)candidateInfo.getElement(), + candidateInfo.getSubstitutor(), + toCopyJavaDoc, + insertOverrideWherePossible + )); + } + return result; + } + + public static List> convert2GenerationInfos(final Collection methods) { + return ContainerUtil.map2List(methods, OverrideImplementUtil::createGenerationInfo); + } + + public static PsiGenerationInfo createGenerationInfo(PsiMethod s) { + return createGenerationInfo(s, true); + } + + public static PsiGenerationInfo createGenerationInfo(PsiMethod s, boolean mergeIfExists) { + for (MethodImplementor implementor : getImplementors()) { + final GenerationInfo info = implementor.createGenerationInfo(s, mergeIfExists); + if (info instanceof PsiGenerationInfo generationInfo) { + //noinspection unchecked + return generationInfo; + } + } + return new PsiGenerationInfo<>(s); + } + + @Nonnull + public static String callSuper(PsiMethod superMethod, PsiMethod overriding) { + @NonNls StringBuilder buffer = new StringBuilder(); + if (!superMethod.isConstructor() && !PsiType.VOID.equals(superMethod.getReturnType())) { + buffer.append("return "); + } + buffer.append("super"); + PsiParameter[] parameters = overriding.getParameterList().getParameters(); + if (!superMethod.isConstructor()) { + buffer.append("."); + buffer.append(superMethod.getName()); + } + buffer.append("("); + for (int i = 0; i < parameters.length; i++) { + String name = parameters[i].getName(); + if (i > 0) { + buffer.append(","); + } + buffer.append(name); + } + buffer.append(")"); + return buffer.toString(); + } + + public static void setupMethodBody(PsiMethod result, PsiMethod originalMethod, PsiClass targetClass) + throws IncorrectOperationException { + boolean isAbstract = + originalMethod.hasModifierProperty(PsiModifier.ABSTRACT) || originalMethod.hasModifierProperty(PsiModifier.DEFAULT); + String templateName = + isAbstract ? JavaTemplateUtil.TEMPLATE_IMPLEMENTED_METHOD_BODY : JavaTemplateUtil.TEMPLATE_OVERRIDDEN_METHOD_BODY; + FileTemplate template = FileTemplateManager.getInstance(result.getProject()).getCodeTemplate(templateName); + setupMethodBody(result, originalMethod, targetClass, template); + } + + public static void setupMethodBody( + final PsiMethod result, + final PsiMethod originalMethod, + final PsiClass targetClass, + final FileTemplate template + ) throws IncorrectOperationException { + if (targetClass.isInterface()) { + if (isImplementInterfaceInJava8Interface(targetClass) || originalMethod.hasModifierProperty(PsiModifier.DEFAULT)) { + PsiUtil.setModifierProperty(result, PsiModifier.DEFAULT, true); + } + else { + final PsiCodeBlock body = result.getBody(); + if (body != null) { + body.delete(); + } + } + } + FileType fileType = FileTypeManager.getInstance().getFileTypeByExtension(template.getExtension()); + PsiType returnType = result.getReturnType(); + if (returnType == null) { + returnType = PsiType.VOID; + } + Properties properties = FileTemplateManager.getInstance(result.getProject()).getDefaultProperties(); + properties.setProperty(FileTemplate.ATTRIBUTE_RETURN_TYPE, returnType.getPresentableText()); + properties.setProperty(FileTemplate.ATTRIBUTE_DEFAULT_RETURN_VALUE, PsiTypesUtil.getDefaultValueOfType(returnType)); + properties.setProperty(FileTemplate.ATTRIBUTE_CALL_SUPER, callSuper(originalMethod, result)); + JavaTemplateUtil.setClassAndMethodNameProperties(properties, targetClass, result); + + JVMElementFactory factory = JVMElementFactories.getFactory(targetClass.getLanguage(), originalMethod.getProject()); + if (factory == null) { + factory = JavaPsiFacade.getInstance(originalMethod.getProject()).getElementFactory(); + } + @NonNls String methodText; + + try { + methodText = "void foo () {\n" + template.getText(properties) + "\n}"; + methodText = FileTemplateUtil.indent(methodText, result.getProject(), fileType); + } + catch (Exception e) { + throw new IncorrectOperationException("Failed to parse file template", e); + } + if (methodText != null) { + PsiMethod m; + try { + m = factory.createMethodFromText(methodText, originalMethod); + } + catch (IncorrectOperationException e) { + Application.get().invokeLater(() -> Messages.showErrorDialog( + CodeInsightLocalize.overrideImplementBrokenFileTemplateMessage().get(), + CodeInsightLocalize.overrideImplementBrokenFileTemplateTitle().get() + )); + return; + } + PsiCodeBlock oldBody = result.getBody(); + if (oldBody != null) { + oldBody.replace(m.getBody()); + } + } + } + + private static boolean isImplementInterfaceInJava8Interface(PsiClass targetClass) { + if (!PsiUtil.isLanguageLevel8OrHigher(targetClass)) { + return false; + } + String commandName = CommandProcessor.getInstance().getCurrentCommandName(); + return commandName != null && StringUtil.containsIgnoreCase(commandName, "implement"); + } + + @RequiredReadAction + public static void chooseAndOverrideMethods(Project project, Editor editor, PsiClass aClass) { + FeatureUsageTracker.getInstance().triggerFeatureUsed(ProductivityFeatureNames.CODEASSISTS_OVERRIDE_IMPLEMENT); + chooseAndOverrideOrImplementMethods(project, editor, aClass, false); + } + + @RequiredReadAction + public static void chooseAndImplementMethods(Project project, Editor editor, PsiClass aClass) { + FeatureUsageTracker.getInstance().triggerFeatureUsed(ProductivityFeatureNames.CODEASSISTS_OVERRIDE_IMPLEMENT); + chooseAndOverrideOrImplementMethods(project, editor, aClass, true); + } + + @RequiredReadAction + public static void chooseAndOverrideOrImplementMethods( + final Project project, + final Editor editor, + final PsiClass aClass, + final boolean toImplement + ) { + LOG.assertTrue(aClass.isValid()); + project.getApplication().assertReadAccessAllowed(); + + Collection candidates = getMethodsToOverrideImplement(aClass, toImplement); + Collection secondary = toImplement || aClass.isInterface() ? new ArrayList<>() + : getMethodsToOverrideImplement(aClass, true); + + final MemberChooser chooser = showOverrideImplementChooser(editor, aClass, toImplement, candidates, secondary); + if (chooser == null) { + return; + } + + final List selectedElements = chooser.getSelectedElements(); + if (selectedElements == null || selectedElements.isEmpty()) { + return; + } + + LOG.assertTrue(aClass.isValid()); + new WriteCommandAction(project, aClass.getContainingFile()) { + @Override + protected void run(@Nonnull final Result result) throws Throwable { + overrideOrImplementMethodsInRightPlace( + editor, + aClass, + selectedElements, + chooser.isCopyJavadoc(), + chooser.isInsertOverrideAnnotation() + ); + } + }.execute(); + } + + /** + * @param candidates, secondary should allow modifications + */ + @Nullable + public static MemberChooser showOverrideImplementChooser( + Editor editor, + final PsiElement aClass, + final boolean toImplement, + final Collection candidates, + Collection secondary + ) { + if (toImplement) { + for (Iterator iterator = candidates.iterator(); iterator.hasNext(); ) { + CandidateInfo candidate = iterator.next(); + PsiElement element = candidate.getElement(); + if (element instanceof PsiMethod && ((PsiMethod)element).hasModifierProperty(PsiModifier.DEFAULT)) { + iterator.remove(); + secondary.add(candidate); + } + } + } + + final JavaOverrideImplementMemberChooser chooser = + JavaOverrideImplementMemberChooser.create(aClass, toImplement, candidates, secondary); + if (chooser == null) { + return null; + } + Project project = aClass.getProject(); + registerHandlerForComplementaryAction(project, editor, aClass, toImplement, chooser); + + if (project.getApplication().isUnitTestMode()) { + return chooser; + } + chooser.show(); + if (chooser.getExitCode() != DialogWrapper.OK_EXIT_CODE) { + return null; + } + + return chooser; + } + + private static void registerHandlerForComplementaryAction( + final Project project, + final Editor editor, + final PsiElement aClass, + final boolean toImplement, + final MemberChooser chooser + ) { + final JComponent preferredFocusedComponent = chooser.getPreferredFocusedComponent(); + final Keymap keymap = KeymapManager.getInstance().getActiveKeymap(); + + @NonNls final String s = toImplement ? "OverrideMethods" : "ImplementMethods"; + final Shortcut[] shortcuts = keymap.getShortcuts(s); + + if (shortcuts.length > 0 && shortcuts[0] instanceof KeyboardShortcut keyboardShortcut) { + preferredFocusedComponent.getInputMap().put(keyboardShortcut.getFirstKeyStroke(), s); + + preferredFocusedComponent.getActionMap().put(s, new AbstractAction() { + @Override + public void actionPerformed(final ActionEvent e) { + chooser.close(DialogWrapper.CANCEL_EXIT_CODE); + + // invoke later in order to close previous modal dialog + project.getApplication().invokeLater(() -> { + final CodeInsightActionHandler handler = + toImplement ? new JavaOverrideMethodsHandler() : new JavaImplementMethodsHandler(); + handler.invoke(project, editor, aClass.getContainingFile()); + }); + } + }); + } + } + + @RequiredReadAction + public static void overrideOrImplementMethodsInRightPlace( + Editor editor, + PsiClass aClass, + Collection candidates, + boolean copyJavadoc, + boolean insertOverrideWherePossible + ) { + try { + int offset = editor.getCaretModel().getOffset(); + PsiElement brace = aClass.getLBrace(); + if (brace == null) { + PsiClass psiClass = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory().createClass("X"); + brace = aClass.addRangeAfter(psiClass.getLBrace(), psiClass.getRBrace(), aClass.getLastChild()); + LOG.assertTrue(brace != null, aClass.getLastChild()); + } + + int lbraceOffset = brace.getTextOffset(); + List> resultMembers; + if (offset <= lbraceOffset || aClass.isEnum()) { + resultMembers = new ArrayList<>(); + for (PsiMethodMember candidate : candidates) { + Collection prototypes = overrideOrImplementMethod( + aClass, + candidate.getElement(), + candidate.getSubstitutor(), + copyJavadoc, + insertOverrideWherePossible + ); + List> infos = convert2GenerationInfos(prototypes); + for (PsiGenerationInfo info : infos) { + PsiElement anchor = + getDefaultAnchorToOverrideOrImplement(aClass, candidate.getElement(), candidate.getSubstitutor()); + info.insert(aClass, anchor, true); + resultMembers.add(info); + } + } + } + else { + List> prototypes = + overrideOrImplementMethods(aClass, candidates, copyJavadoc, insertOverrideWherePossible); + resultMembers = GenerateMembersUtil.insertMembersAtOffset(aClass.getContainingFile(), offset, prototypes); + } + + if (!resultMembers.isEmpty()) { + resultMembers.get(0).positionCaret(editor, true); + } + } + catch (IncorrectOperationException e) { + LOG.error(e); + } + } + + @Nullable + @RequiredReadAction + public static PsiElement getDefaultAnchorToOverrideOrImplement(PsiClass aClass, PsiMethod baseMethod, PsiSubstitutor substitutor) { + PsiMethod prevBaseMethod = PsiTreeUtil.getPrevSiblingOfType(baseMethod, PsiMethod.class); + while (prevBaseMethod != null) { + String name = prevBaseMethod.isConstructor() ? aClass.getName() : prevBaseMethod.getName(); + //Happens when aClass instanceof PsiAnonymousClass + if (name != null) { + MethodSignature signature = MethodSignatureUtil.createMethodSignature( + name, + prevBaseMethod.getParameterList(), + prevBaseMethod.getTypeParameterList(), + substitutor, + prevBaseMethod.isConstructor() + ); + PsiMethod prevMethod = MethodSignatureUtil.findMethodBySignature(aClass, signature, false); + if (prevMethod != null && prevMethod.isPhysical()) { + return prevMethod.getNextSibling(); + } + } + prevBaseMethod = PsiTreeUtil.getPrevSiblingOfType(prevBaseMethod, PsiMethod.class); + } + + PsiMethod nextBaseMethod = PsiTreeUtil.getNextSiblingOfType(baseMethod, PsiMethod.class); + while (nextBaseMethod != null) { + String name = nextBaseMethod.isConstructor() ? aClass.getName() : nextBaseMethod.getName(); + if (name != null) { + MethodSignature signature = MethodSignatureUtil.createMethodSignature( + name, + nextBaseMethod.getParameterList(), + nextBaseMethod.getTypeParameterList(), + substitutor, + nextBaseMethod.isConstructor() + ); + PsiMethod nextMethod = MethodSignatureUtil.findMethodBySignature(aClass, signature, false); + if (nextMethod != null && nextMethod.isPhysical()) { + return nextMethod; + } + } + nextBaseMethod = PsiTreeUtil.getNextSiblingOfType(nextBaseMethod, PsiMethod.class); + } + return null; - } - - PsiSubstitutor substitutor = - TypeConversionUtil.getSuperClassSubstitutor(baseMethod.getContainingClass(), psiClass, PsiSubstitutor.EMPTY); - PsiElement anchor = getDefaultAnchorToOverrideOrImplement(psiClass, baseMethod, substitutor); - results = GenerateMembersUtil.insertMembersBeforeAnchor(psiClass, anchor, prototypes); - - return results; - } finally { - PsiFile psiFile = psiClass.getContainingFile(); - Editor editor = fileEditorManager.openTextEditor( - OpenFileDescriptorFactory.getInstance(psiFile.getProject()).builder(psiFile.getVirtualFile()).build(), - true - ); - if (editor != null && !results.isEmpty()) { - results.get(0).positionCaret(editor, true); - editor.getScrollingModel().scrollToCaret(ScrollType.CENTER); - } - } - } - - @Nullable - @RequiredReadAction - public static PsiClass getContextClass(Project project, Editor editor, PsiFile file, boolean allowInterface) { - PsiDocumentManager.getInstance(project).commitAllDocuments(); - - int offset = editor.getCaretModel().getOffset(); - PsiElement element = file.findElementAt(offset); - do { - element = PsiTreeUtil.getParentOfType(element, PsiClass.class); - } - while (element instanceof PsiTypeParameter); - - final PsiClass aClass = (PsiClass) element; - if (aClass instanceof PsiSyntheticClass) { - return null; - } - return aClass == null || !allowInterface && aClass.isInterface() ? null : aClass; - } - - @RequiredReadAction - public static void overrideOrImplementMethodsInRightPlace( - Editor editor1, - PsiClass aClass, - Collection members, - boolean copyJavadoc - ) { - boolean insert = CodeStyleSettingsManager.getSettings(aClass.getProject()).INSERT_OVERRIDE_ANNOTATION; - overrideOrImplementMethodsInRightPlace(editor1, aClass, members, copyJavadoc, insert); - } - - public static List overrideOrImplementMethodCandidates( - PsiClass aClass, - Collection candidatesToImplement, - boolean copyJavadoc - ) throws IncorrectOperationException { - boolean insert = CodeStyleSettingsManager.getSettings(aClass.getProject()).INSERT_OVERRIDE_ANNOTATION; - return overrideOrImplementMethodCandidates(aClass, candidatesToImplement, copyJavadoc, insert); - } + } + + @RequiredReadAction + public static List> overrideOrImplement(PsiClass psiClass, @Nonnull PsiMethod baseMethod) + throws IncorrectOperationException { + FileEditorManager fileEditorManager = FileEditorManager.getInstance(baseMethod.getProject()); + List> results = new ArrayList<>(); + try { + + List> prototypes = convert2GenerationInfos(overrideOrImplementMethod(psiClass, baseMethod, false)); + if (prototypes.isEmpty()) { + return null; + } + + PsiSubstitutor substitutor = + TypeConversionUtil.getSuperClassSubstitutor(baseMethod.getContainingClass(), psiClass, PsiSubstitutor.EMPTY); + PsiElement anchor = getDefaultAnchorToOverrideOrImplement(psiClass, baseMethod, substitutor); + results = GenerateMembersUtil.insertMembersBeforeAnchor(psiClass, anchor, prototypes); + + return results; + } + finally { + PsiFile psiFile = psiClass.getContainingFile(); + Editor editor = fileEditorManager.openTextEditor( + OpenFileDescriptorFactory.getInstance(psiFile.getProject()).builder(psiFile.getVirtualFile()).build(), + true + ); + if (editor != null && !results.isEmpty()) { + results.get(0).positionCaret(editor, true); + editor.getScrollingModel().scrollToCaret(ScrollType.CENTER); + } + } + } + + @Nullable + @RequiredReadAction + public static PsiClass getContextClass(Project project, Editor editor, PsiFile file, boolean allowInterface) { + PsiDocumentManager.getInstance(project).commitAllDocuments(); + + int offset = editor.getCaretModel().getOffset(); + PsiElement element = file.findElementAt(offset); + do { + element = PsiTreeUtil.getParentOfType(element, PsiClass.class); + } + while (element instanceof PsiTypeParameter); + + final PsiClass aClass = (PsiClass)element; + if (aClass instanceof PsiSyntheticClass) { + return null; + } + return aClass == null || !allowInterface && aClass.isInterface() ? null : aClass; + } + + @RequiredReadAction + public static void overrideOrImplementMethodsInRightPlace( + Editor editor1, + PsiClass aClass, + Collection members, + boolean copyJavadoc + ) { + boolean insert = CodeStyleSettingsManager.getSettings(aClass.getProject()).INSERT_OVERRIDE_ANNOTATION; + overrideOrImplementMethodsInRightPlace(editor1, aClass, members, copyJavadoc, insert); + } + + public static List overrideOrImplementMethodCandidates( + PsiClass aClass, + Collection candidatesToImplement, + boolean copyJavadoc + ) throws IncorrectOperationException { + boolean insert = CodeStyleSettingsManager.getSettings(aClass.getProject()).INSERT_OVERRIDE_ANNOTATION; + return overrideOrImplementMethodCandidates(aClass, candidatesToImplement, copyJavadoc, insert); + } } \ No newline at end of file diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/OverrideImplementsAnnotationsHandler.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/OverrideImplementsAnnotationsHandler.java index 1c78a5f344..f5368b315e 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/OverrideImplementsAnnotationsHandler.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/OverrideImplementsAnnotationsHandler.java @@ -29,10 +29,11 @@ @ExtensionAPI(ComponentScope.APPLICATION) public interface OverrideImplementsAnnotationsHandler { - ExtensionPointName EP_NAME = ExtensionPointName.create(OverrideImplementsAnnotationsHandler.class); + ExtensionPointName EP_NAME = + ExtensionPointName.create(OverrideImplementsAnnotationsHandler.class); - String[] getAnnotations(Project project); + String[] getAnnotations(Project project); - @Nonnull - String[] annotationsToRemove(Project project, @Nonnull String fqName); + @Nonnull + String[] annotationsToRemove(Project project, @Nonnull String fqName); } \ No newline at end of file diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/surroundWith/JavaExpressionSurroundDescriptor.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/surroundWith/JavaExpressionSurroundDescriptor.java index e998ff1b92..ba7f2b7ef7 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/surroundWith/JavaExpressionSurroundDescriptor.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/surroundWith/JavaExpressionSurroundDescriptor.java @@ -28,6 +28,7 @@ import consulo.language.psi.PsiFile; import jakarta.annotation.Nonnull; + import java.util.ArrayList; import java.util.Collections; @@ -36,52 +37,52 @@ */ @ExtensionImpl public class JavaExpressionSurroundDescriptor implements SurroundDescriptor { - private Surrounder[] mySurrounders = null; + private Surrounder[] mySurrounders = null; - private static final Surrounder[] SURROUNDERS = { - new JavaWithParenthesesSurrounder(), - new JavaWithCastSurrounder(), - new JavaWithNotSurrounder(), - new JavaWithNotInstanceofSurrounder(), - new JavaWithIfExpressionSurrounder(), - new JavaWithIfElseExpressionSurrounder(), - new JavaWithNullCheckSurrounder() - }; + private static final Surrounder[] SURROUNDERS = { + new JavaWithParenthesesSurrounder(), + new JavaWithCastSurrounder(), + new JavaWithNotSurrounder(), + new JavaWithNotInstanceofSurrounder(), + new JavaWithIfExpressionSurrounder(), + new JavaWithIfElseExpressionSurrounder(), + new JavaWithNullCheckSurrounder() + }; - @Override - @Nonnull - public PsiElement[] getElementsToSurround(PsiFile file, int startOffset, int endOffset) { - PsiExpression expr = CodeInsightUtil.findExpressionInRange(file, startOffset, endOffset); - if (expr == null) { - expr = IntroduceVariableBase.getSelectedExpression(file.getProject(), file, startOffset, endOffset); - if (expr == null) { - return PsiElement.EMPTY_ARRAY; - } + @Override + @Nonnull + public PsiElement[] getElementsToSurround(PsiFile file, int startOffset, int endOffset) { + PsiExpression expr = CodeInsightUtil.findExpressionInRange(file, startOffset, endOffset); + if (expr == null) { + expr = IntroduceVariableBase.getSelectedExpression(file.getProject(), file, startOffset, endOffset); + if (expr == null) { + return PsiElement.EMPTY_ARRAY; + } + } + FeatureUsageTracker.getInstance().triggerFeatureUsed("codeassists.surroundwith.expression"); + return new PsiElement[]{expr}; } - FeatureUsageTracker.getInstance().triggerFeatureUsed("codeassists.surroundwith.expression"); - return new PsiElement[] {expr}; - } - @Override - @Nonnull - public Surrounder[] getSurrounders() { - if (mySurrounders == null) { - final ArrayList list = new ArrayList(); - Collections.addAll(list, SURROUNDERS); - Collections.addAll(list, JavaExpressionSurrounder.EP_NAME.getExtensions()); - mySurrounders = list.toArray(new Surrounder[list.size()]); + @Override + @Nonnull + public Surrounder[] getSurrounders() { + if (mySurrounders == null) { + final ArrayList list = new ArrayList(); + Collections.addAll(list, SURROUNDERS); + Collections.addAll(list, JavaExpressionSurrounder.EP_NAME.getExtensions()); + mySurrounders = list.toArray(new Surrounder[list.size()]); + } + return mySurrounders; } - return mySurrounders; - } - @Override - public boolean isExclusive() { - return false; - } + @Override + public boolean isExclusive() { + return false; + } - @Nonnull - @Override - public Language getLanguage() { - return JavaLanguage.INSTANCE; - } + @Nonnull + @Override + public Language getLanguage() { + return JavaLanguage.INSTANCE; + } } diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/surroundWith/JavaExpressionSurrounder.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/surroundWith/JavaExpressionSurrounder.java index 61e69c90c9..ed793449e8 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/surroundWith/JavaExpressionSurrounder.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/surroundWith/JavaExpressionSurrounder.java @@ -33,24 +33,23 @@ @ExtensionAPI(ComponentScope.APPLICATION) public abstract class JavaExpressionSurrounder implements Surrounder { - public static ExtensionPointName EP_NAME = ExtensionPointName.create(JavaExpressionSurrounder.class); - - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.generation.surroundWith.SurroundExpressionHandler"); - - @Override - public boolean isApplicable(@Nonnull PsiElement[] elements) { - LOG.assertTrue(elements.length == 1 && elements[0] instanceof PsiExpression); - return isApplicable((PsiExpression)elements[0]); - } - - public abstract boolean isApplicable(PsiExpression expr); - - @Override - public TextRange surroundElements(@Nonnull Project project, - @Nonnull Editor editor, - @Nonnull PsiElement[] elements) throws IncorrectOperationException { - return surroundExpression(project, editor, (PsiExpression)elements[0]); - } - - public abstract TextRange surroundExpression(Project project, Editor editor, PsiExpression expr) throws IncorrectOperationException; + public static ExtensionPointName EP_NAME = ExtensionPointName.create(JavaExpressionSurrounder.class); + + private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.generation.surroundWith.SurroundExpressionHandler"); + + @Override + public boolean isApplicable(@Nonnull PsiElement[] elements) { + LOG.assertTrue(elements.length == 1 && elements[0] instanceof PsiExpression); + return isApplicable((PsiExpression)elements[0]); + } + + public abstract boolean isApplicable(PsiExpression expr); + + @Override + public TextRange surroundElements(@Nonnull Project project, @Nonnull Editor editor, @Nonnull PsiElement[] elements) + throws IncorrectOperationException { + return surroundExpression(project, editor, (PsiExpression)elements[0]); + } + + public abstract TextRange surroundExpression(Project project, Editor editor, PsiExpression expr) throws IncorrectOperationException; } \ No newline at end of file diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/javadoc/DocumentationDelegateProvider.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/javadoc/DocumentationDelegateProvider.java index 0fd0a4f2cf..2fc9ccea71 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/javadoc/DocumentationDelegateProvider.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/javadoc/DocumentationDelegateProvider.java @@ -26,47 +26,46 @@ @ExtensionAPI(ComponentScope.APPLICATION) public abstract class DocumentationDelegateProvider { + public static final ExtensionPointName EP_NAME = + ExtensionPointName.create(DocumentationDelegateProvider.class); - public static final ExtensionPointName EP_NAME = - ExtensionPointName.create(DocumentationDelegateProvider.class); + /** + *

+ * Computes PsiDocCommentOwner to get documentation from. + *

+ *

+ * Suppose there is a {@code Foo#bar()} with doc and {@code Baz#bar()} without doc: + *

+     * class Foo {
+     *   /**
+     *   * Some javadoc
+     *   */
+     *   void bar() {}
+     * }
+     * class Baz {
+     *   void bar() {}
+     * }
+     * 
+ * If it is needed to display doc from {@code Foo#bar()} when doc for {@code Baz#bar()} is queried + * then this method should return PsiMethod corresponding to {@code Foo#bar()} for PsiMethod corresponding to {@code Baz#bar()}. + *
+ * The copied documentation will include Description copied from link. + *

+ * + * @param member method to search delegate for. + * @return delegate PsiDocCommentOwner instance. + */ + @Nullable + public abstract PsiDocCommentOwner computeDocumentationDelegate(@Nonnull PsiMember member); - /** - *

- * Computes PsiDocCommentOwner to get documentation from. - *

- *

- * Suppose there is a {@code Foo#bar()} with doc and {@code Baz#bar()} without doc: - *

-   * class Foo {
-   *   /**
-   *   * Some javadoc
-   *   */
-   *   void bar() {}
-   * }
-   * class Baz {
-   *   void bar() {}
-   * }
-   * 
- * If it is needed to display doc from {@code Foo#bar()} when doc for {@code Baz#bar()} is queried - * then this method should return PsiMethod corresponding to {@code Foo#bar()} for PsiMethod corresponding to {@code Baz#bar()}. - *
- * The copied documentation will include Description copied from link. - *

- * - * @param member method to search delegate for. - * @return delegate PsiDocCommentOwner instance. - */ - @Nullable - public abstract PsiDocCommentOwner computeDocumentationDelegate(@Nonnull PsiMember member); - - @Nullable - public static PsiDocCommentOwner findDocumentationDelegate(@Nonnull PsiMember method) { - for (DocumentationDelegateProvider delegator : EP_NAME.getExtensionList()) { - PsiDocCommentOwner type = delegator.computeDocumentationDelegate(method); - if (type != null) { - return type; - } + @Nullable + public static PsiDocCommentOwner findDocumentationDelegate(@Nonnull PsiMember method) { + for (DocumentationDelegateProvider delegator : EP_NAME.getExtensionList()) { + PsiDocCommentOwner type = delegator.computeDocumentationDelegate(method); + if (type != null) { + return type; + } + } + return null; } - return null; - } } diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/template/macro/VariableTypeCalculator.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/template/macro/VariableTypeCalculator.java index 46231253a2..ade58207fa 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/template/macro/VariableTypeCalculator.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/template/macro/VariableTypeCalculator.java @@ -29,22 +29,24 @@ */ @ExtensionAPI(ComponentScope.APPLICATION) public abstract class VariableTypeCalculator { - public static final ExtensionPointName EP_NAME = - ExtensionPointName.create(VariableTypeCalculator.class); + public static final ExtensionPointName EP_NAME = + ExtensionPointName.create(VariableTypeCalculator.class); - @Nullable - public abstract PsiType inferVarTypeAt(@Nonnull PsiVariable var, @Nonnull PsiElement place); + @Nullable + public abstract PsiType inferVarTypeAt(@Nonnull PsiVariable var, @Nonnull PsiElement place); - /** - * @return inferred type of variable in the context of place - */ - @Nonnull - public static PsiType getVarTypeAt(@Nonnull PsiVariable var, @Nonnull PsiElement place) { - for (VariableTypeCalculator calculator : EP_NAME.getExtensionList()) { - final PsiType type = calculator.inferVarTypeAt(var, place); - if (type != null) return type; - } + /** + * @return inferred type of variable in the context of place + */ + @Nonnull + public static PsiType getVarTypeAt(@Nonnull PsiVariable var, @Nonnull PsiElement place) { + for (VariableTypeCalculator calculator : EP_NAME.getExtensionList()) { + final PsiType type = calculator.inferVarTypeAt(var, place); + if (type != null) { + return type; + } + } - return var.getType(); - } + return var.getType(); + } } diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInspection/uncheckedWarnings/UncheckedWarningLocalInspectionBase.java b/plugin/src/main/java/com/intellij/java/impl/codeInspection/uncheckedWarnings/UncheckedWarningLocalInspectionBase.java index 7e2d63a28b..ac9dc8a847 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInspection/uncheckedWarnings/UncheckedWarningLocalInspectionBase.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInspection/uncheckedWarnings/UncheckedWarningLocalInspectionBase.java @@ -51,522 +51,607 @@ import java.util.function.Consumer; public abstract class UncheckedWarningLocalInspectionBase extends BaseJavaBatchLocalInspectionTool { - @NonNls - public static final String SHORT_NAME = "UNCHECKED_WARNING"; - @NonNls - private static final String ID = "unchecked"; - private static final Logger LOG = Logger.getInstance(UncheckedWarningLocalInspectionBase.class); - public boolean IGNORE_UNCHECKED_ASSIGNMENT; - public boolean IGNORE_UNCHECKED_GENERICS_ARRAY_CREATION; - public boolean IGNORE_UNCHECKED_CALL; - public boolean IGNORE_UNCHECKED_CAST; - public boolean IGNORE_UNCHECKED_OVERRIDING; - - @Nonnull - static JCheckBox createSetting(@Nonnull String cbText, final boolean option, @Nonnull Consumer pass) { - final JCheckBox uncheckedCb = new JCheckBox(cbText, option); - uncheckedCb.addActionListener(e -> pass.accept(uncheckedCb)); - return uncheckedCb; - } - - @Nonnull - private static LocalQuickFix[] getChangeVariableTypeFixes(@Nonnull PsiVariable parameter, @Nullable PsiType itemType, LocalQuickFix[] generifyFixes) { - if (itemType instanceof PsiMethodReferenceType) { - return generifyFixes; - } - LOG.assertTrue(parameter.isValid()); - final List result = new ArrayList<>(); - if (itemType != null) { - for (ChangeVariableTypeQuickFixProvider fixProvider : ChangeVariableTypeQuickFixProvider.EP_NAME.getExtensionList()) { - for (IntentionAction action : fixProvider.getFixes(parameter, itemType)) { - if (action instanceof LocalQuickFix) { - result.add((LocalQuickFix) action); - } - } - } - } + @NonNls + public static final String SHORT_NAME = "UNCHECKED_WARNING"; + @NonNls + private static final String ID = "unchecked"; + private static final Logger LOG = Logger.getInstance(UncheckedWarningLocalInspectionBase.class); + public boolean IGNORE_UNCHECKED_ASSIGNMENT; + public boolean IGNORE_UNCHECKED_GENERICS_ARRAY_CREATION; + public boolean IGNORE_UNCHECKED_CALL; + public boolean IGNORE_UNCHECKED_CAST; + public boolean IGNORE_UNCHECKED_OVERRIDING; - if (generifyFixes.length > 0) { - Collections.addAll(result, generifyFixes); - } - return result.toArray(new LocalQuickFix[result.size()]); - } - - @Override - @Nonnull - public String getGroupDisplayName() { - return ""; - } - - @Override - @Nonnull - public String getDisplayName() { - return InspectionLocalize.uncheckedWarning().get(); - } - - @Override - @Nonnull - @NonNls - public String getShortName() { - return SHORT_NAME; - } - - @Override - @Pattern(VALID_ID_PATTERN) - @Nonnull - @NonNls - public String getID() { - return ID; - } - - @Override - public boolean isEnabledByDefault() { - return true; - } - - @Override - public void writeSettings(@Nonnull Element node) throws WriteExternalException { - if (IGNORE_UNCHECKED_ASSIGNMENT || IGNORE_UNCHECKED_CALL || IGNORE_UNCHECKED_CAST || IGNORE_UNCHECKED_OVERRIDING || IGNORE_UNCHECKED_GENERICS_ARRAY_CREATION) { - super.writeSettings(node); - } - } - - @Nonnull - @Override - public PsiElementVisitor buildVisitorImpl(@Nonnull final ProblemsHolder holder, boolean isOnTheFly, @Nonnull LocalInspectionToolSession session, Object state) { - LanguageLevel languageLevel = PsiUtil.getLanguageLevel(session.getFile()); - if (!languageLevel.isAtLeast(LanguageLevel.JDK_1_5)) { - return super.buildVisitorImpl(holder, isOnTheFly, session, state); + @Nonnull + static JCheckBox createSetting(@Nonnull String cbText, final boolean option, @Nonnull Consumer pass) { + final JCheckBox uncheckedCb = new JCheckBox(cbText, option); + uncheckedCb.addActionListener(e -> pass.accept(uncheckedCb)); + return uncheckedCb; } - return new UncheckedWarningsVisitor(isOnTheFly, languageLevel) { - @Override - protected void registerProblem(@Nonnull String message, @Nullable PsiElement callExpression, @Nonnull PsiElement psiElement, @Nonnull LocalQuickFix[] quickFixes) { - final String rawExpression = isMethodCalledOnRawType(callExpression); - if (rawExpression != null) { - final String referenceName = ((PsiMethodCallExpression) callExpression).getMethodExpression().getReferenceName(); - message += ". Reason: '" + rawExpression + "' has raw type, so result of " + referenceName + " is erased"; + @Nonnull + private static LocalQuickFix[] getChangeVariableTypeFixes( + @Nonnull PsiVariable parameter, + @Nullable PsiType itemType, + LocalQuickFix[] generifyFixes + ) { + if (itemType instanceof PsiMethodReferenceType) { + return generifyFixes; } - holder.registerProblem(psiElement, message, quickFixes); - } - }; - } - - @Nonnull - protected LocalQuickFix[] createFixes() { - return LocalQuickFix.EMPTY_ARRAY; - } - - private static String isMethodCalledOnRawType(PsiElement expression) { - if (expression instanceof PsiMethodCallExpression) { - final PsiExpression qualifierExpression = ((PsiMethodCallExpression) expression).getMethodExpression().getQualifierExpression(); - if (qualifierExpression != null) { - final PsiClass qualifierClass = PsiUtil.resolveClassInClassTypeOnly(qualifierExpression.getType()); - if (qualifierClass != null) { - if (PsiUtil.isRawSubstitutor(qualifierClass, ((PsiMethodCallExpression) expression).resolveMethodGenerics().getSubstitutor())) { - return qualifierExpression.getText(); - } + LOG.assertTrue(parameter.isValid()); + final List result = new ArrayList<>(); + if (itemType != null) { + for (ChangeVariableTypeQuickFixProvider fixProvider : ChangeVariableTypeQuickFixProvider.EP_NAME.getExtensionList()) { + for (IntentionAction action : fixProvider.getFixes(parameter, itemType)) { + if (action instanceof LocalQuickFix) { + result.add((LocalQuickFix)action); + } + } + } + } + + if (generifyFixes.length > 0) { + Collections.addAll(result, generifyFixes); } - } + return result.toArray(new LocalQuickFix[result.size()]); } - return null; - } - private abstract class UncheckedWarningsVisitor extends JavaElementVisitor { - private final boolean myOnTheFly; + @Override @Nonnull - private final LanguageLevel myLanguageLevel; - private final LocalQuickFix[] myGenerifyFixes; - - UncheckedWarningsVisitor(boolean onTheFly, @Nonnull LanguageLevel level) { - myOnTheFly = onTheFly; - myLanguageLevel = level; - myGenerifyFixes = onTheFly ? createFixes() : LocalQuickFix.EMPTY_ARRAY; + public String getGroupDisplayName() { + return ""; } - protected abstract void registerProblem(@Nonnull String message, PsiElement callExpression, @Nonnull PsiElement psiElement, @Nonnull LocalQuickFix[] quickFixes); - - @Override - public void visitReferenceExpression(PsiReferenceExpression expression) { - if (IGNORE_UNCHECKED_GENERICS_ARRAY_CREATION) { - return; - } - final JavaResolveResult result = expression.advancedResolve(false); - if (JavaGenericsUtil.isUncheckedWarning(expression, result, myLanguageLevel)) { - registerProblem("Unchecked generics array creation for varargs parameter", null, expression, LocalQuickFix.EMPTY_ARRAY); - } + @Nonnull + public String getDisplayName() { + return InspectionLocalize.uncheckedWarning().get(); } @Override - public void visitNewExpression(PsiNewExpression expression) { - super.visitNewExpression(expression); - if (IGNORE_UNCHECKED_GENERICS_ARRAY_CREATION) { - return; - } - final PsiJavaCodeReferenceElement classReference = expression.getClassOrAnonymousClassReference(); - if (classReference != null && JavaGenericsUtil.isUncheckedWarning(classReference, expression.resolveMethodGenerics(), myLanguageLevel)) { - registerProblem("Unchecked generics array creation for varargs parameter", expression, classReference, LocalQuickFix.EMPTY_ARRAY); - } + @Nonnull + @NonNls + public String getShortName() { + return SHORT_NAME; } @Override - public void visitTypeCastExpression(PsiTypeCastExpression expression) { - super.visitTypeCastExpression(expression); - if (IGNORE_UNCHECKED_CAST) { - return; - } - final PsiTypeElement typeElement = expression.getCastType(); - if (typeElement == null) { - return; - } - final PsiType castType = typeElement.getType(); - final PsiExpression operand = expression.getOperand(); - if (operand == null) { - return; - } - final PsiType exprType = operand.getType(); - if (exprType == null) { - return; - } - if (!TypeConversionUtil.areTypesConvertible(exprType, castType)) { - return; - } - if (JavaGenericsUtil.isUncheckedCast(castType, exprType)) { - final String description = JavaErrorBundle.message("generics.unchecked.cast", JavaHighlightUtil.formatType(exprType), JavaHighlightUtil.formatType(castType)); - registerProblem(description, operand, expression, myGenerifyFixes); - } + @Pattern(VALID_ID_PATTERN) + @Nonnull + @NonNls + public String getID() { + return ID; } @Override - public void visitMethodReferenceExpression(PsiMethodReferenceExpression expression) { - super.visitMethodReferenceExpression(expression); - if (IGNORE_UNCHECKED_CALL) { - return; - } - final JavaResolveResult result = expression.advancedResolve(false); - final String description = getUncheckedCallDescription(expression, result); - if (description != null) { - final PsiElement referenceNameElement = expression.getReferenceNameElement(); - registerProblem(description, expression, referenceNameElement != null ? referenceNameElement : expression, myGenerifyFixes); - } + public boolean isEnabledByDefault() { + return true; } @Override - public void visitCallExpression(PsiCallExpression callExpression) { - super.visitCallExpression(callExpression); - final JavaResolveResult result = callExpression.resolveMethodGenerics(); - final String description = getUncheckedCallDescription(callExpression, result); - if (description != null) { - if (IGNORE_UNCHECKED_CALL) { - return; + public void writeSettings(@Nonnull Element node) throws WriteExternalException { + if (IGNORE_UNCHECKED_ASSIGNMENT || IGNORE_UNCHECKED_CALL || IGNORE_UNCHECKED_CAST || IGNORE_UNCHECKED_OVERRIDING || IGNORE_UNCHECKED_GENERICS_ARRAY_CREATION) { + super.writeSettings(node); } - final PsiExpression element = callExpression instanceof PsiMethodCallExpression ? ((PsiMethodCallExpression) callExpression).getMethodExpression() : callExpression; - registerProblem(description, null, element, myGenerifyFixes); - } else { - if (IGNORE_UNCHECKED_ASSIGNMENT) { - return; + } + + @Nonnull + @Override + public PsiElementVisitor buildVisitorImpl( + @Nonnull final ProblemsHolder holder, + boolean isOnTheFly, + @Nonnull LocalInspectionToolSession session, + Object state + ) { + LanguageLevel languageLevel = PsiUtil.getLanguageLevel(session.getFile()); + if (!languageLevel.isAtLeast(LanguageLevel.JDK_1_5)) { + return super.buildVisitorImpl(holder, isOnTheFly, session, state); } - final PsiSubstitutor substitutor = result.getSubstitutor(); - final PsiExpressionList argumentList = callExpression.getArgumentList(); - if (argumentList != null) { - final PsiMethod method = (PsiMethod) result.getElement(); - if (method != null) { - final PsiExpression[] expressions = argumentList.getExpressions(); - final PsiParameter[] parameters = method.getParameterList().getParameters(); - if (parameters.length != 0) { - for (int i = 0; i < expressions.length; i++) { - PsiParameter parameter = parameters[Math.min(i, parameters.length - 1)]; - final PsiExpression expression = expressions[i]; - final PsiType parameterType = substitutor.substitute(parameter.getType()); - final PsiType expressionType = expression.getType(); - if (expressionType != null) { - checkRawToGenericsAssignment(expression, expression, parameterType, expressionType, true, myGenerifyFixes); + + return new UncheckedWarningsVisitor(isOnTheFly, languageLevel) { + @Override + protected void registerProblem( + @Nonnull String message, + @Nullable PsiElement callExpression, + @Nonnull PsiElement psiElement, + @Nonnull LocalQuickFix[] quickFixes + ) { + final String rawExpression = isMethodCalledOnRawType(callExpression); + if (rawExpression != null) { + final String referenceName = ((PsiMethodCallExpression)callExpression).getMethodExpression().getReferenceName(); + message += ". Reason: '" + rawExpression + "' has raw type, so result of " + referenceName + " is erased"; } - } + holder.registerProblem(psiElement, message, quickFixes); } - } - } - } + }; } - @Override - public void visitVariable(PsiVariable variable) { - super.visitVariable(variable); - if (IGNORE_UNCHECKED_ASSIGNMENT) { - return; - } - PsiExpression initializer = variable.getInitializer(); - if (initializer == null || initializer instanceof PsiArrayInitializerExpression) { - return; - } - final PsiType initializerType = initializer.getType(); - checkRawToGenericsAssignment(initializer, initializer, variable.getType(), initializerType, true, myOnTheFly ? getChangeVariableTypeFixes(variable, initializerType, myGenerifyFixes) : - LocalQuickFix.EMPTY_ARRAY); + @Nonnull + protected LocalQuickFix[] createFixes() { + return LocalQuickFix.EMPTY_ARRAY; } - @Override - public void visitForeachStatement(PsiForeachStatement statement) { - super.visitForeachStatement(statement); - if (IGNORE_UNCHECKED_ASSIGNMENT) { - return; - } - final PsiParameter parameter = statement.getIterationParameter(); - final PsiType parameterType = parameter.getType(); - final PsiExpression iteratedValue = statement.getIteratedValue(); - if (iteratedValue == null) { - return; - } - final PsiType itemType = JavaGenericsUtil.getCollectionItemType(iteratedValue); - LocalQuickFix[] fixes = myOnTheFly ? getChangeVariableTypeFixes(parameter, itemType, myGenerifyFixes) : LocalQuickFix.EMPTY_ARRAY; - checkRawToGenericsAssignment(parameter, iteratedValue, parameterType, itemType, true, fixes); + private static String isMethodCalledOnRawType(PsiElement expression) { + if (expression instanceof PsiMethodCallExpression) { + final PsiExpression qualifierExpression = ((PsiMethodCallExpression)expression).getMethodExpression().getQualifierExpression(); + if (qualifierExpression != null) { + final PsiClass qualifierClass = PsiUtil.resolveClassInClassTypeOnly(qualifierExpression.getType()); + if (qualifierClass != null) { + if (PsiUtil.isRawSubstitutor( + qualifierClass, + ((PsiMethodCallExpression)expression).resolveMethodGenerics().getSubstitutor() + )) { + return qualifierExpression.getText(); + } + } + } + } + return null; } - @Override - public void visitAssignmentExpression(PsiAssignmentExpression expression) { - super.visitAssignmentExpression(expression); - if (IGNORE_UNCHECKED_ASSIGNMENT) { - return; - } - if (!"=".equals(expression.getOperationSign().getText())) { - return; - } - PsiExpression lExpr = expression.getLExpression(); - PsiExpression rExpr = expression.getRExpression(); - if (rExpr == null) { - return; - } - PsiType lType = lExpr.getType(); - PsiType rType = rExpr.getType(); - if (rType == null) { - return; - } - PsiVariable leftVar = null; - if (lExpr instanceof PsiReferenceExpression) { - PsiElement element = ((PsiReferenceExpression) lExpr).resolve(); - if (element instanceof PsiVariable) { - leftVar = (PsiVariable) element; + private abstract class UncheckedWarningsVisitor extends JavaElementVisitor { + private final boolean myOnTheFly; + @Nonnull + private final LanguageLevel myLanguageLevel; + private final LocalQuickFix[] myGenerifyFixes; + + UncheckedWarningsVisitor(boolean onTheFly, @Nonnull LanguageLevel level) { + myOnTheFly = onTheFly; + myLanguageLevel = level; + myGenerifyFixes = onTheFly ? createFixes() : LocalQuickFix.EMPTY_ARRAY; } - } - checkRawToGenericsAssignment(rExpr, rExpr, lType, rType, true, myOnTheFly && leftVar != null ? getChangeVariableTypeFixes(leftVar, rType, myGenerifyFixes) : LocalQuickFix.EMPTY_ARRAY); - } - @Override - public void visitArrayInitializerExpression(PsiArrayInitializerExpression arrayInitializer) { - super.visitArrayInitializerExpression(arrayInitializer); - if (IGNORE_UNCHECKED_ASSIGNMENT) { - return; - } - final PsiType type = arrayInitializer.getType(); - if (!(type instanceof PsiArrayType)) { - return; - } - final PsiType componentType = ((PsiArrayType) type).getComponentType(); - - - boolean arrayTypeFixChecked = false; - VariableArrayTypeFix fix = null; - - final PsiExpression[] initializers = arrayInitializer.getInitializers(); - for (PsiExpression expression : initializers) { - final PsiType itemType = expression.getType(); - - if (itemType == null) { - continue; + protected abstract void registerProblem( + @Nonnull String message, + PsiElement callExpression, + @Nonnull PsiElement psiElement, + @Nonnull LocalQuickFix[] quickFixes + ); + + + @Override + public void visitReferenceExpression(PsiReferenceExpression expression) { + if (IGNORE_UNCHECKED_GENERICS_ARRAY_CREATION) { + return; + } + final JavaResolveResult result = expression.advancedResolve(false); + if (JavaGenericsUtil.isUncheckedWarning(expression, result, myLanguageLevel)) { + registerProblem("Unchecked generics array creation for varargs parameter", null, expression, LocalQuickFix.EMPTY_ARRAY); + } } - if (!TypeConversionUtil.isAssignable(componentType, itemType)) { - continue; + + @Override + public void visitNewExpression(PsiNewExpression expression) { + super.visitNewExpression(expression); + if (IGNORE_UNCHECKED_GENERICS_ARRAY_CREATION) { + return; + } + final PsiJavaCodeReferenceElement classReference = expression.getClassOrAnonymousClassReference(); + if (classReference != null && JavaGenericsUtil.isUncheckedWarning( + classReference, + expression.resolveMethodGenerics(), + myLanguageLevel + )) { + registerProblem( + "Unchecked generics array creation for varargs parameter", + expression, + classReference, + LocalQuickFix.EMPTY_ARRAY + ); + } } - if (JavaGenericsUtil.isRawToGeneric(componentType, itemType)) { - String description = JavaErrorBundle.message("generics.unchecked.assignment", JavaHighlightUtil.formatType(itemType), JavaHighlightUtil.formatType(componentType)); - if (!arrayTypeFixChecked) { - final PsiType checkResult = JavaHighlightUtil.sameType(initializers); - fix = checkResult != null ? VariableArrayTypeFix.createFix(arrayInitializer, checkResult) : null; - arrayTypeFixChecked = true; - } - - if (fix != null) { - registerProblem(description, null, expression, new LocalQuickFix[]{fix}); - } + + @Override + public void visitTypeCastExpression(PsiTypeCastExpression expression) { + super.visitTypeCastExpression(expression); + if (IGNORE_UNCHECKED_CAST) { + return; + } + final PsiTypeElement typeElement = expression.getCastType(); + if (typeElement == null) { + return; + } + final PsiType castType = typeElement.getType(); + final PsiExpression operand = expression.getOperand(); + if (operand == null) { + return; + } + final PsiType exprType = operand.getType(); + if (exprType == null) { + return; + } + if (!TypeConversionUtil.areTypesConvertible(exprType, castType)) { + return; + } + if (JavaGenericsUtil.isUncheckedCast(castType, exprType)) { + final String description = JavaErrorBundle.message( + "generics.unchecked.cast", + JavaHighlightUtil.formatType(exprType), + JavaHighlightUtil.formatType(castType) + ); + registerProblem(description, operand, expression, myGenerifyFixes); + } } - } - } - private void checkRawToGenericsAssignment(@Nonnull PsiElement parameter, - PsiExpression expression, - PsiType parameterType, - PsiType itemType, - boolean checkAssignability, - @Nonnull LocalQuickFix[] quickFixes) { - if (parameterType == null || itemType == null) { - return; - } - if (checkAssignability && !TypeConversionUtil.isAssignable(parameterType, itemType)) { - return; - } - if (JavaGenericsUtil.isRawToGeneric(parameterType, itemType)) { - String description = JavaErrorBundle.message("generics.unchecked.assignment", JavaHighlightUtil.formatType(itemType), JavaHighlightUtil.formatType(parameterType)); - registerProblem(description, expression, parameter, quickFixes); - } - } + @Override + public void visitMethodReferenceExpression(PsiMethodReferenceExpression expression) { + super.visitMethodReferenceExpression(expression); + if (IGNORE_UNCHECKED_CALL) { + return; + } + final JavaResolveResult result = expression.advancedResolve(false); + final String description = getUncheckedCallDescription(expression, result); + if (description != null) { + final PsiElement referenceNameElement = expression.getReferenceNameElement(); + registerProblem(description, expression, referenceNameElement != null ? referenceNameElement : expression, myGenerifyFixes); + } + } - @Override - public void visitMethod(PsiMethod method) { - super.visitMethod(method); - if (IGNORE_UNCHECKED_OVERRIDING) { - return; - } - if (!method.isConstructor()) { - List superMethodSignatures = method.getHierarchicalMethodSignature().getSuperSignatures(); - if (!superMethodSignatures.isEmpty() && !method.hasModifierProperty(PsiModifier.STATIC)) { - final MethodSignature signature = method.getSignature(PsiSubstitutor.EMPTY); - for (MethodSignatureBackedByPsiMethod superSignature : superMethodSignatures) { - PsiMethod baseMethod = superSignature.getMethod(); - PsiSubstitutor substitutor = MethodSignatureUtil.getSuperMethodSignatureSubstitutor(signature, superSignature); - if (substitutor == null) { - substitutor = superSignature.getSubstitutor(); - } - if (PsiUtil.isRawSubstitutor(baseMethod, superSignature.getSubstitutor())) { - continue; - } - final PsiType baseReturnType = substitutor.substitute(baseMethod.getReturnType()); - final PsiType overriderReturnType = method.getReturnType(); - if (baseReturnType == null || overriderReturnType == null) { - return; - } - if (JavaGenericsUtil.isRawToGeneric(baseReturnType, overriderReturnType)) { - final String message = JavaErrorBundle.message("unchecked.overriding.incompatible.return.type", JavaHighlightUtil.formatType(overriderReturnType), JavaHighlightUtil - .formatType(baseReturnType)); - - final PsiTypeElement returnTypeElement = method.getReturnTypeElement(); - LOG.assertTrue(returnTypeElement != null); - registerProblem(message, null, returnTypeElement, LocalQuickFix.EMPTY_ARRAY); - } - } + @Override + public void visitCallExpression(PsiCallExpression callExpression) { + super.visitCallExpression(callExpression); + final JavaResolveResult result = callExpression.resolveMethodGenerics(); + final String description = getUncheckedCallDescription(callExpression, result); + if (description != null) { + if (IGNORE_UNCHECKED_CALL) { + return; + } + final PsiExpression element = + callExpression instanceof PsiMethodCallExpression ? ((PsiMethodCallExpression)callExpression).getMethodExpression() : callExpression; + registerProblem(description, null, element, myGenerifyFixes); + } + else { + if (IGNORE_UNCHECKED_ASSIGNMENT) { + return; + } + final PsiSubstitutor substitutor = result.getSubstitutor(); + final PsiExpressionList argumentList = callExpression.getArgumentList(); + if (argumentList != null) { + final PsiMethod method = (PsiMethod)result.getElement(); + if (method != null) { + final PsiExpression[] expressions = argumentList.getExpressions(); + final PsiParameter[] parameters = method.getParameterList().getParameters(); + if (parameters.length != 0) { + for (int i = 0; i < expressions.length; i++) { + PsiParameter parameter = parameters[Math.min(i, parameters.length - 1)]; + final PsiExpression expression = expressions[i]; + final PsiType parameterType = substitutor.substitute(parameter.getType()); + final PsiType expressionType = expression.getType(); + if (expressionType != null) { + checkRawToGenericsAssignment( + expression, + expression, + parameterType, + expressionType, + true, + myGenerifyFixes + ); + } + } + } + } + } + } } - } - } - @Override - public void visitReturnStatement(PsiReturnStatement statement) { - super.visitReturnStatement(statement); - if (IGNORE_UNCHECKED_ASSIGNMENT) { - return; - } - final PsiType returnType = PsiTypesUtil.getMethodReturnType(statement); - if (returnType != null && !PsiType.VOID.equals(returnType)) { - final PsiExpression returnValue = statement.getReturnValue(); - if (returnValue != null) { - final PsiType valueType = returnValue.getType(); - if (valueType != null) { - final PsiElement psiElement = PsiTreeUtil.getParentOfType(statement, PsiMethod.class, PsiLambdaExpression.class); - LocalQuickFix[] fixes = psiElement instanceof PsiMethod ? new LocalQuickFix[]{QuickFixFactory.getInstance().createMethodReturnFix((PsiMethod) psiElement, valueType, true)} : - LocalQuickFix.EMPTY_ARRAY; - checkRawToGenericsAssignment(returnValue, returnValue, returnType, valueType, false, fixes); - } + @Override + public void visitVariable(PsiVariable variable) { + super.visitVariable(variable); + if (IGNORE_UNCHECKED_ASSIGNMENT) { + return; + } + PsiExpression initializer = variable.getInitializer(); + if (initializer == null || initializer instanceof PsiArrayInitializerExpression) { + return; + } + final PsiType initializerType = initializer.getType(); + checkRawToGenericsAssignment( + initializer, + initializer, + variable.getType(), + initializerType, + true, + myOnTheFly + ? getChangeVariableTypeFixes(variable, initializerType, myGenerifyFixes) + : LocalQuickFix.EMPTY_ARRAY + ); } - } - } - @Override - public void visitLambdaExpression(PsiLambdaExpression expression) { - super.visitLambdaExpression(expression); - - if (IGNORE_UNCHECKED_ASSIGNMENT) { - return; - } - PsiElement body = expression.getBody(); - if (body instanceof PsiExpression) { - PsiType interfaceReturnType = LambdaUtil.getFunctionalInterfaceReturnType(expression); - if (interfaceReturnType != null && !PsiType.VOID.equals(interfaceReturnType)) { - PsiType type = ((PsiExpression) body).getType(); - if (type != null) { - checkRawToGenericsAssignment(body, (PsiExpression) body, interfaceReturnType, type, false, LocalQuickFix.EMPTY_ARRAY); - } + @Override + public void visitForeachStatement(PsiForeachStatement statement) { + super.visitForeachStatement(statement); + if (IGNORE_UNCHECKED_ASSIGNMENT) { + return; + } + final PsiParameter parameter = statement.getIterationParameter(); + final PsiType parameterType = parameter.getType(); + final PsiExpression iteratedValue = statement.getIteratedValue(); + if (iteratedValue == null) { + return; + } + final PsiType itemType = JavaGenericsUtil.getCollectionItemType(iteratedValue); + LocalQuickFix[] fixes = + myOnTheFly ? getChangeVariableTypeFixes(parameter, itemType, myGenerifyFixes) : LocalQuickFix.EMPTY_ARRAY; + checkRawToGenericsAssignment(parameter, iteratedValue, parameterType, itemType, true, fixes); } - } - } - @Nullable - private String getUncheckedCallDescription(PsiElement place, JavaResolveResult resolveResult) { - final PsiElement element = resolveResult.getElement(); - if (!(element instanceof PsiMethod)) { - return null; - } - final PsiMethod method = (PsiMethod) element; - final PsiSubstitutor substitutor = resolveResult.getSubstitutor(); - if (!PsiUtil.isRawSubstitutor(method, substitutor)) { - if (JavaVersionService.getInstance().isAtLeast(place, JavaSdkVersion.JDK_1_8)) { - for (PsiTypeParameter parameter : PsiUtil.typeParametersIterable(method)) { - final PsiClassType[] extendsListTypes = parameter.getExtendsListTypes(); - if (extendsListTypes.length > 0) { - final PsiType subst = substitutor.substitute(parameter); - for (PsiClassType classType : extendsListTypes) { - if (JavaGenericsUtil.isRawToGeneric(substitutor.substitute(classType), subst)) { - return JavaErrorBundle.message("generics.unchecked.call", JavaHighlightUtil.formatMethod(method)); + @Override + public void visitAssignmentExpression(PsiAssignmentExpression expression) { + super.visitAssignmentExpression(expression); + if (IGNORE_UNCHECKED_ASSIGNMENT) { + return; + } + if (!"=".equals(expression.getOperationSign().getText())) { + return; + } + PsiExpression lExpr = expression.getLExpression(); + PsiExpression rExpr = expression.getRExpression(); + if (rExpr == null) { + return; + } + PsiType lType = lExpr.getType(); + PsiType rType = rExpr.getType(); + if (rType == null) { + return; + } + PsiVariable leftVar = null; + if (lExpr instanceof PsiReferenceExpression) { + PsiElement element = ((PsiReferenceExpression)lExpr).resolve(); + if (element instanceof PsiVariable) { + leftVar = (PsiVariable)element; } - } } - } + checkRawToGenericsAssignment( + rExpr, + rExpr, + lType, + rType, + true, + myOnTheFly && leftVar != null ? getChangeVariableTypeFixes(leftVar, rType, myGenerifyFixes) : LocalQuickFix.EMPTY_ARRAY + ); } - return null; - } - final PsiParameter[] parameters = method.getParameterList().getParameters(); - for (final PsiParameter parameter : parameters) { - final PsiType parameterType = parameter.getType(); - if (parameterType.accept(new PsiTypeVisitor() { - @Override - public Boolean visitPrimitiveType(PsiPrimitiveType primitiveType) { - return Boolean.FALSE; - } - - @Override - public Boolean visitArrayType(PsiArrayType arrayType) { - return arrayType.getComponentType().accept(this); - } - - @Override - public Boolean visitClassType(PsiClassType classType) { - PsiClassType.ClassResolveResult result = classType.resolveGenerics(); - PsiClass psiClass = result.getElement(); - if (psiClass instanceof PsiTypeParameter) { - if (((PsiTypeParameter) psiClass).getOwner() == method) { - return Boolean.FALSE; - } - return substitutor.substitute((PsiTypeParameter) psiClass) == null ? Boolean.TRUE : Boolean.FALSE; - } - if (psiClass != null) { - PsiSubstitutor typeSubstitutor = result.getSubstitutor(); - for (PsiTypeParameter parameter : PsiUtil.typeParametersIterable(psiClass)) { - PsiType psiType = typeSubstitutor.substitute(parameter); - if (psiType != null && psiType.accept(this).booleanValue()) { - return Boolean.TRUE; + + @Override + public void visitArrayInitializerExpression(PsiArrayInitializerExpression arrayInitializer) { + super.visitArrayInitializerExpression(arrayInitializer); + if (IGNORE_UNCHECKED_ASSIGNMENT) { + return; + } + final PsiType type = arrayInitializer.getType(); + if (!(type instanceof PsiArrayType)) { + return; + } + final PsiType componentType = ((PsiArrayType)type).getComponentType(); + + + boolean arrayTypeFixChecked = false; + VariableArrayTypeFix fix = null; + + final PsiExpression[] initializers = arrayInitializer.getInitializers(); + for (PsiExpression expression : initializers) { + final PsiType itemType = expression.getType(); + + if (itemType == null) { + continue; + } + if (!TypeConversionUtil.isAssignable(componentType, itemType)) { + continue; } - } - } - return Boolean.FALSE; - } - - @Override - public Boolean visitWildcardType(PsiWildcardType wildcardType) { - PsiType bound = wildcardType.getBound(); - if (bound != null) { - return bound.accept(this); - } - return Boolean.TRUE; - } - - @Override - public Boolean visitEllipsisType(PsiEllipsisType ellipsisType) { - return ellipsisType.getComponentType().accept(this); - } - }).booleanValue()) { - final PsiElementFactory elementFactory = JavaPsiFacade.getInstance(method.getProject()).getElementFactory(); - PsiType type = elementFactory.createType(method.getContainingClass(), substitutor); - return JavaErrorBundle.message("generics.unchecked.call.to.member.of.raw.type", JavaHighlightUtil.formatMethod(method), JavaHighlightUtil.formatType(type)); + if (JavaGenericsUtil.isRawToGeneric(componentType, itemType)) { + String description = JavaErrorBundle.message( + "generics.unchecked.assignment", + JavaHighlightUtil.formatType(itemType), + JavaHighlightUtil.formatType(componentType) + ); + if (!arrayTypeFixChecked) { + final PsiType checkResult = JavaHighlightUtil.sameType(initializers); + fix = checkResult != null ? VariableArrayTypeFix.createFix(arrayInitializer, checkResult) : null; + arrayTypeFixChecked = true; + } + + if (fix != null) { + registerProblem(description, null, expression, new LocalQuickFix[]{fix}); + } + } + } + } + + private void checkRawToGenericsAssignment( + @Nonnull PsiElement parameter, + PsiExpression expression, + PsiType parameterType, + PsiType itemType, + boolean checkAssignability, + @Nonnull LocalQuickFix[] quickFixes + ) { + if (parameterType == null || itemType == null) { + return; + } + if (checkAssignability && !TypeConversionUtil.isAssignable(parameterType, itemType)) { + return; + } + if (JavaGenericsUtil.isRawToGeneric(parameterType, itemType)) { + String description = JavaErrorBundle.message( + "generics.unchecked.assignment", + JavaHighlightUtil.formatType(itemType), + JavaHighlightUtil.formatType(parameterType) + ); + registerProblem(description, expression, parameter, quickFixes); + } + } + + @Override + public void visitMethod(PsiMethod method) { + super.visitMethod(method); + if (IGNORE_UNCHECKED_OVERRIDING) { + return; + } + if (!method.isConstructor()) { + List superMethodSignatures = method.getHierarchicalMethodSignature().getSuperSignatures(); + if (!superMethodSignatures.isEmpty() && !method.hasModifierProperty(PsiModifier.STATIC)) { + final MethodSignature signature = method.getSignature(PsiSubstitutor.EMPTY); + for (MethodSignatureBackedByPsiMethod superSignature : superMethodSignatures) { + PsiMethod baseMethod = superSignature.getMethod(); + PsiSubstitutor substitutor = MethodSignatureUtil.getSuperMethodSignatureSubstitutor(signature, superSignature); + if (substitutor == null) { + substitutor = superSignature.getSubstitutor(); + } + if (PsiUtil.isRawSubstitutor(baseMethod, superSignature.getSubstitutor())) { + continue; + } + final PsiType baseReturnType = substitutor.substitute(baseMethod.getReturnType()); + final PsiType overriderReturnType = method.getReturnType(); + if (baseReturnType == null || overriderReturnType == null) { + return; + } + if (JavaGenericsUtil.isRawToGeneric(baseReturnType, overriderReturnType)) { + final String message = JavaErrorBundle.message( + "unchecked.overriding.incompatible.return.type", + JavaHighlightUtil.formatType(overriderReturnType), + JavaHighlightUtil.formatType(baseReturnType) + ); + + final PsiTypeElement returnTypeElement = method.getReturnTypeElement(); + LOG.assertTrue(returnTypeElement != null); + registerProblem(message, null, returnTypeElement, LocalQuickFix.EMPTY_ARRAY); + } + } + } + } + } + + @Override + public void visitReturnStatement(PsiReturnStatement statement) { + super.visitReturnStatement(statement); + if (IGNORE_UNCHECKED_ASSIGNMENT) { + return; + } + final PsiType returnType = PsiTypesUtil.getMethodReturnType(statement); + if (returnType != null && !PsiType.VOID.equals(returnType)) { + final PsiExpression returnValue = statement.getReturnValue(); + if (returnValue != null) { + final PsiType valueType = returnValue.getType(); + if (valueType != null) { + final PsiElement psiElement = PsiTreeUtil.getParentOfType(statement, PsiMethod.class, PsiLambdaExpression.class); + LocalQuickFix[] fixes = psiElement instanceof PsiMethod + ? new LocalQuickFix[]{QuickFixFactory.getInstance().createMethodReturnFix((PsiMethod)psiElement, valueType, true)} : + LocalQuickFix.EMPTY_ARRAY; + checkRawToGenericsAssignment(returnValue, returnValue, returnType, valueType, false, fixes); + } + } + } + } + + @Override + public void visitLambdaExpression(PsiLambdaExpression expression) { + super.visitLambdaExpression(expression); + + if (IGNORE_UNCHECKED_ASSIGNMENT) { + return; + } + PsiElement body = expression.getBody(); + if (body instanceof PsiExpression) { + PsiType interfaceReturnType = LambdaUtil.getFunctionalInterfaceReturnType(expression); + if (interfaceReturnType != null && !PsiType.VOID.equals(interfaceReturnType)) { + PsiType type = ((PsiExpression)body).getType(); + if (type != null) { + checkRawToGenericsAssignment( + body, + (PsiExpression)body, + interfaceReturnType, + type, + false, + LocalQuickFix.EMPTY_ARRAY + ); + } + } + } + } + + @Nullable + private String getUncheckedCallDescription(PsiElement place, JavaResolveResult resolveResult) { + final PsiElement element = resolveResult.getElement(); + if (!(element instanceof PsiMethod)) { + return null; + } + final PsiMethod method = (PsiMethod)element; + final PsiSubstitutor substitutor = resolveResult.getSubstitutor(); + if (!PsiUtil.isRawSubstitutor(method, substitutor)) { + if (JavaVersionService.getInstance().isAtLeast(place, JavaSdkVersion.JDK_1_8)) { + for (PsiTypeParameter parameter : PsiUtil.typeParametersIterable(method)) { + final PsiClassType[] extendsListTypes = parameter.getExtendsListTypes(); + if (extendsListTypes.length > 0) { + final PsiType subst = substitutor.substitute(parameter); + for (PsiClassType classType : extendsListTypes) { + if (JavaGenericsUtil.isRawToGeneric(substitutor.substitute(classType), subst)) { + return JavaErrorBundle.message("generics.unchecked.call", JavaHighlightUtil.formatMethod(method)); + } + } + } + } + } + return null; + } + final PsiParameter[] parameters = method.getParameterList().getParameters(); + for (final PsiParameter parameter : parameters) { + final PsiType parameterType = parameter.getType(); + if (parameterType.accept(new PsiTypeVisitor() { + @Override + public Boolean visitPrimitiveType(PsiPrimitiveType primitiveType) { + return Boolean.FALSE; + } + + @Override + public Boolean visitArrayType(PsiArrayType arrayType) { + return arrayType.getComponentType().accept(this); + } + + @Override + public Boolean visitClassType(PsiClassType classType) { + PsiClassType.ClassResolveResult result = classType.resolveGenerics(); + PsiClass psiClass = result.getElement(); + if (psiClass instanceof PsiTypeParameter) { + if (((PsiTypeParameter)psiClass).getOwner() == method) { + return Boolean.FALSE; + } + return substitutor.substitute((PsiTypeParameter)psiClass) == null ? Boolean.TRUE : Boolean.FALSE; + } + if (psiClass != null) { + PsiSubstitutor typeSubstitutor = result.getSubstitutor(); + for (PsiTypeParameter parameter : PsiUtil.typeParametersIterable(psiClass)) { + PsiType psiType = typeSubstitutor.substitute(parameter); + if (psiType != null && psiType.accept(this).booleanValue()) { + return Boolean.TRUE; + } + } + } + return Boolean.FALSE; + } + + @Override + public Boolean visitWildcardType(PsiWildcardType wildcardType) { + PsiType bound = wildcardType.getBound(); + if (bound != null) { + return bound.accept(this); + } + return Boolean.TRUE; + } + + @Override + public Boolean visitEllipsisType(PsiEllipsisType ellipsisType) { + return ellipsisType.getComponentType().accept(this); + } + }).booleanValue()) { + final PsiElementFactory elementFactory = JavaPsiFacade.getInstance(method.getProject()).getElementFactory(); + PsiType type = elementFactory.createType(method.getContainingClass(), substitutor); + return JavaErrorBundle.message( + "generics.unchecked.call.to.member.of.raw.type", + JavaHighlightUtil.formatMethod(method), + JavaHighlightUtil.formatType(type) + ); + } + } + return null; } - } - return null; } - } } diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInspection/visibility/VisibilityExtension.java b/plugin/src/main/java/com/intellij/java/impl/codeInspection/visibility/VisibilityExtension.java index e8213907df..3c480b2523 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInspection/visibility/VisibilityExtension.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInspection/visibility/VisibilityExtension.java @@ -23,7 +23,7 @@ @ExtensionAPI(ComponentScope.APPLICATION) public interface VisibilityExtension { - ExtensionPointName EP_NAME = ExtensionPointName.create(VisibilityExtension.class); + ExtensionPointName EP_NAME = ExtensionPointName.create(VisibilityExtension.class); - void fillIgnoreList(RefManager refManager, ProblemDescriptionsProcessor processor); + void fillIgnoreList(RefManager refManager, ProblemDescriptionsProcessor processor); } \ No newline at end of file diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInspection/visibility/VisibilityInspection.java b/plugin/src/main/java/com/intellij/java/impl/codeInspection/visibility/VisibilityInspection.java index 56a096254f..7c06f07e84 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInspection/visibility/VisibilityInspection.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInspection/visibility/VisibilityInspection.java @@ -61,699 +61,582 @@ import java.util.List; @ExtensionImpl -public class VisibilityInspection extends GlobalJavaInspectionTool implements OldStyleInspection -{ - private static final Logger LOG = Logger.getInstance(VisibilityInspection.class); - public boolean SUGGEST_PACKAGE_LOCAL_FOR_MEMBERS = true; - public boolean SUGGEST_PACKAGE_LOCAL_FOR_TOP_CLASSES = true; - public boolean SUGGEST_PRIVATE_FOR_INNERS = false; - @NonNls - private static final String SHORT_NAME = "WeakerAccess"; - - private class OptionsPanel extends JPanel - { - private final JCheckBox myPackageLocalForMembersCheckbox; - private final JCheckBox myPrivateForInnersCheckbox; - private final JCheckBox myPackageLocalForTopClassesCheckbox; - - private OptionsPanel() - { - super(new GridBagLayout()); - - GridBagConstraints gc = new GridBagConstraints(); - gc.fill = GridBagConstraints.HORIZONTAL; - gc.weightx = 1; - gc.weighty = 0; - gc.anchor = GridBagConstraints.NORTHWEST; - - myPackageLocalForMembersCheckbox = new JCheckBox(InspectionLocalize.inspectionVisibilityOption().get()); - myPackageLocalForMembersCheckbox.setSelected(SUGGEST_PACKAGE_LOCAL_FOR_MEMBERS); - myPackageLocalForMembersCheckbox.getModel() - .addChangeListener(e -> SUGGEST_PACKAGE_LOCAL_FOR_MEMBERS = myPackageLocalForMembersCheckbox.isSelected()); - - gc.gridy = 0; - add(myPackageLocalForMembersCheckbox, gc); - - myPackageLocalForTopClassesCheckbox = new JCheckBox(InspectionLocalize.inspectionVisibilityOption1().get()); - myPackageLocalForTopClassesCheckbox.setSelected(SUGGEST_PACKAGE_LOCAL_FOR_TOP_CLASSES); - myPackageLocalForTopClassesCheckbox.getModel() - .addChangeListener(e -> SUGGEST_PACKAGE_LOCAL_FOR_TOP_CLASSES = myPackageLocalForTopClassesCheckbox.isSelected()); - - gc.gridy = 1; - add(myPackageLocalForTopClassesCheckbox, gc); - - - myPrivateForInnersCheckbox = new JCheckBox(InspectionLocalize.inspectionVisibilityOption2().get()); - myPrivateForInnersCheckbox.setSelected(SUGGEST_PRIVATE_FOR_INNERS); - myPrivateForInnersCheckbox.getModel().addChangeListener(e -> SUGGEST_PRIVATE_FOR_INNERS = myPrivateForInnersCheckbox.isSelected()); - - gc.gridy = 2; - gc.weighty = 1; - add(myPrivateForInnersCheckbox, gc); - } - } - - @Override - public JComponent createOptionsPanel() - { - return new OptionsPanel(); - } - - @Override - @Nonnull - public String getDisplayName() - { - return InspectionLocalize.inspectionVisibilityDisplayName().get(); - } - - @Override - @Nonnull - public String getGroupDisplayName() - { - return InspectionLocalize.groupNamesDeclarationRedundancy().get(); - } - - @Override - @Nonnull - public String getShortName() - { - return SHORT_NAME; - } - - @Override - @Nullable - @RequiredReadAction - public CommonProblemDescriptor[] checkElement( - final RefEntity refEntity, - final AnalysisScope scope, - final InspectionManager manager, - final GlobalInspectionContext globalContext, - final ProblemDescriptionsProcessor processor, - Object state - ) - { - if (refEntity instanceof RefJavaElement refElement) - { - if (refElement instanceof RefParameter || refElement.isSyntheticJSP()) { - return null; - } - - //ignore entry points. - if (refElement.isEntry()) - { - return null; - } - - //ignore implicit constructors. User should not be able to see them. - if (refElement instanceof RefImplicitConstructor) - { - return null; - } - - if (refElement instanceof RefField refField && refField.getElement() instanceof PsiEnumConstant) - { - return null; - } - - //ignore library override methods. - if (refElement instanceof RefMethod refMethod && (refMethod.isExternalOverride() || refMethod.isEntry())) { - return null; - } - - //ignore anonymous classes. They do not have access modifiers. - if (refElement instanceof RefClass refClass) - { - if (refClass.isAnonymous() || refClass.isEntry() || refClass.isTestCase() - || refClass.isServlet() || refClass.isApplet() || refClass.isLocalClass() - || isTopLevelClass(refClass) && !SUGGEST_PACKAGE_LOCAL_FOR_TOP_CLASSES) { - return null; - } - } - - //ignore unreferenced code. They could be a potential entry points. - if (refElement.getInReferences().isEmpty()) - { - return null; - } - - //ignore interface members. They always have public access modifier. - if (refElement.getOwner() instanceof RefClass refClass && refClass.isInterface()) { - return null; - } - - String access = getPossibleAccess(refElement); - if (access != refElement.getAccessModifier() && access != null) - { - final PsiElement element = refElement.getElement(); - final PsiElement nameIdentifier = element != null ? IdentifierUtil.getNameIdentifier(element) : null; - if (nameIdentifier != null) - { - return new ProblemDescriptor[]{ - manager.createProblemDescriptor( - nameIdentifier, - access.equals(PsiModifier.PRIVATE) - ? InspectionLocalize.inspectionVisibilityComposeSuggestion("private").get() - : access.equals(PsiModifier.PACKAGE_LOCAL) - ? InspectionLocalize.inspectionVisibilityComposeSuggestion("package local").get() - : InspectionLocalize.inspectionVisibilityComposeSuggestion("protected").get(), - new AcceptSuggestedAccess(globalContext.getRefManager(), access), - ProblemHighlightType.GENERIC_ERROR_OR_WARNING, false - ) - }; - } - } - } - return null; - } - - @Nullable - @PsiModifier.ModifierConstant - public String getPossibleAccess(@Nullable RefJavaElement refElement) - { - if (refElement == null) - { - return null; - } - String curAccess = refElement.getAccessModifier(); - String weakestAccess = PsiModifier.PRIVATE; - - if (isTopLevelClass(refElement) || isCalledOnSubClasses(refElement)) - { - weakestAccess = PsiModifier.PACKAGE_LOCAL; - } - - if (isAbstractMethod(refElement)) - { - weakestAccess = PsiModifier.PROTECTED; - } - - if (curAccess == weakestAccess) - { - return curAccess; - } - - while (true) - { - String weakerAccess = getWeakerAccess(curAccess, refElement); - if (weakerAccess == null || RefJavaUtil.getInstance().compareAccess(weakerAccess, weakestAccess) < 0) - { - break; - } - if (isAccessible(refElement, weakerAccess)) - { - curAccess = weakerAccess; - } - else - { - break; - } - } - - return curAccess; - } - - private static boolean isCalledOnSubClasses(RefElement refElement) - { - return refElement instanceof RefMethod refMethod && refMethod.isCalledOnSubClass(); - } - - private static boolean isAbstractMethod(RefElement refElement) - { - return refElement instanceof RefMethod refMethod && refMethod.isAbstract(); - } - - private static boolean isTopLevelClass(RefElement refElement) - { - return refElement instanceof RefClass && RefJavaUtil.getInstance().getTopLevelClass(refElement) == refElement; - } - - @Nullable - @PsiModifier.ModifierConstant - private String getWeakerAccess(@PsiModifier.ModifierConstant String curAccess, RefElement refElement) - { - if (curAccess == PsiModifier.PUBLIC) - { - return isTopLevelClass(refElement) ? PsiModifier.PACKAGE_LOCAL : PsiModifier.PROTECTED; - } - if (curAccess == PsiModifier.PROTECTED) - { - return SUGGEST_PACKAGE_LOCAL_FOR_MEMBERS ? PsiModifier.PACKAGE_LOCAL : PsiModifier.PRIVATE; - } - if (curAccess == PsiModifier.PACKAGE_LOCAL) - { - return PsiModifier.PRIVATE; - } - - return null; - } - - private boolean isAccessible(RefJavaElement to, @PsiModifier.ModifierConstant String accessModifier) - { - for (RefElement refElement : to.getInReferences()) - { - if (!isAccessibleFrom(refElement, to, accessModifier)) - { - return false; - } - } - - if (to instanceof RefMethod refMethod) - { - if (refMethod.isAbstract() && (refMethod.getDerivedMethods().isEmpty() || refMethod.getAccessModifier() == PsiModifier.PRIVATE)) - { - return false; - } - - for (RefMethod refOverride : refMethod.getDerivedMethods()) - { - if (!isAccessibleFrom(refOverride, to, accessModifier)) - { - return false; - } - } - - for (RefMethod refSuper : refMethod.getSuperMethods()) - { - if (RefJavaUtil.getInstance().compareAccess(refSuper.getAccessModifier(), accessModifier) > 0) - { - return false; - } - } - } - - if (to instanceof RefClass refClass) - { - for (RefClass subClass : refClass.getSubClasses()) - { - if (!isAccessibleFrom(subClass, to, accessModifier)) - { - return false; - } - } - - List children = refClass.getChildren(); - if (children != null) - { - for (Object refElement : children) - { - if (!isAccessible((RefJavaElement) refElement, accessModifier)) - { - return false; - } - } - } - - for (final RefElement refElement : refClass.getInTypeReferences()) - { - if (!isAccessibleFrom(refElement, refClass, accessModifier)) - { - return false; - } - } - - List classExporters = ((RefClassImpl) refClass).getClassExporters(); - if (classExporters != null) - { - for (RefJavaElement refExporter : classExporters) - { - if (getAccessLevel(accessModifier) < getAccessLevel(refExporter.getAccessModifier())) - { - return false; - } - } - } - } - - return true; - } - - private static int getAccessLevel(@PsiModifier.ModifierConstant String access) - { - if (access == PsiModifier.PRIVATE) - { - return 1; - } - if (access == PsiModifier.PACKAGE_LOCAL) - { - return 2; - } - if (access == PsiModifier.PROTECTED) - { - return 3; - } - return 4; - } - - private boolean isAccessibleFrom(RefElement from, RefJavaElement to, String accessModifier) - { - if (accessModifier == PsiModifier.PUBLIC) - { - return true; - } - - final RefJavaUtil refUtil = RefJavaUtil.getInstance(); - if (accessModifier == PsiModifier.PACKAGE_LOCAL) - { - return RefJavaUtil.getPackage(from) == RefJavaUtil.getPackage(to); - } - - RefClass fromTopLevel = refUtil.getTopLevelClass(from); - RefClass toTopLevel = refUtil.getTopLevelClass(to); - RefClass fromOwner = refUtil.getOwnerClass(from); - RefClass toOwner = refUtil.getOwnerClass(to); - - if (accessModifier == PsiModifier.PROTECTED) - { - if (SUGGEST_PRIVATE_FOR_INNERS) - { - return refUtil.isInheritor(fromTopLevel, toOwner) - || fromOwner != null && refUtil.isInheritor(fromOwner, toTopLevel) - || toOwner != null && refUtil.getOwnerClass(toOwner) == from; - } - - return refUtil.isInheritor(fromTopLevel, toOwner); - } - - if (accessModifier == PsiModifier.PRIVATE) - { - if (SUGGEST_PRIVATE_FOR_INNERS) - { - if (isInExtendsList(to, fromTopLevel.getElement().getExtendsList()) - || isInExtendsList(to, fromTopLevel.getElement().getImplementsList()) - || isInAnnotations(to, fromTopLevel)) - { - return false; - } - return fromTopLevel == toOwner || fromOwner == toTopLevel || toOwner != null && refUtil.getOwnerClass(toOwner) == from; - } - - if (fromOwner != null && fromOwner.isStatic() && !to.isStatic() && refUtil.isInheritor(fromOwner, toOwner)) - { - return false; - } - - if (fromTopLevel == toOwner) - { - if (from instanceof RefClass fromRefClass && to instanceof RefClass) - { - final PsiClass fromClass = fromRefClass.getElement(); - LOG.assertTrue(fromClass != null); - if (isInExtendsList(to, fromClass.getExtendsList()) - || isInExtendsList(to, fromClass.getImplementsList())) - { - return false; - } - } - - return true; - } - } - - return false; - } - - private static boolean isInAnnotations(final RefJavaElement to, final RefClass fromTopLevel) - { - final PsiModifierList modifierList = fromTopLevel.getElement().getModifierList(); - if (modifierList == null) - { - return false; - } - final PsiElement toElement = to.getElement(); - - final boolean[] resolved = new boolean[]{false}; - modifierList.accept(new JavaRecursiveElementWalkingVisitor() - { - @RequiredReadAction - @Override - public void visitReferenceExpression(PsiReferenceExpression expression) - { - if (resolved[0]) - { - return; - } - super.visitReferenceExpression(expression); - if (expression.resolve() == toElement) - { - resolved[0] = true; - } - } - }); - return resolved[0]; - } - - private static boolean isInExtendsList(final RefJavaElement to, final PsiReferenceList extendsList) - { - if (extendsList != null) - { - final PsiJavaCodeReferenceElement[] referenceElements = extendsList.getReferenceElements(); - for (PsiJavaCodeReferenceElement referenceElement : referenceElements) - { - final PsiReferenceParameterList parameterList = referenceElement.getParameterList(); - if (parameterList != null) - { - for (PsiType type : parameterList.getTypeArguments()) - { - if (extendsList.getManager().areElementsEquivalent(PsiUtil.resolveClassInType(type), to.getElement())) - { - return true; - } - } - } - } - } - return false; - } - - - @Override - protected boolean queryExternalUsagesRequests( - final RefManager manager, - final GlobalJavaInspectionContext globalContext, - final ProblemDescriptionsProcessor processor, - Object state - ) - { - final EntryPointsManager entryPointsManager = globalContext.getEntryPointsManager(manager); - for (RefElement entryPoint : entryPointsManager.getEntryPoints()) - { - ignoreElement(processor, entryPoint); - } - - for (VisibilityExtension addin : VisibilityExtension.EP_NAME.getExtensions()) - { - addin.fillIgnoreList(manager, processor); - } - manager.iterate(new RefJavaVisitor() - { - @Override - public void visitElement(@Nonnull final RefEntity refEntity) - { - if (!(refEntity instanceof RefElement)) - { - return; - } - if (processor.getDescriptions(refEntity) == null) - { - return; - } - refEntity.accept(new RefJavaVisitor() - { - @Override - public void visitField(@Nonnull final RefField refField) - { - if (refField.getAccessModifier() != PsiModifier.PRIVATE) - { - globalContext.enqueueFieldUsagesProcessor(refField, psiReference -> { - ignoreElement(processor, refField); - return false; - }); - } - } - - @Override - public void visitMethod(@Nonnull final RefMethod refMethod) - { - if (!refMethod.isExternalOverride() && refMethod.getAccessModifier() != PsiModifier.PRIVATE - && !(refMethod instanceof RefImplicitConstructor)) - { - globalContext.enqueueDerivedMethodsProcessor(refMethod, derivedMethod -> { - ignoreElement(processor, refMethod); - return false; - }); - - globalContext.enqueueMethodUsagesProcessor(refMethod, psiReference -> { - ignoreElement(processor, refMethod); - return false; - }); - - if (entryPointsManager.isAddNonJavaEntries()) - { - final RefClass ownerClass = refMethod.getOwnerClass(); - if (refMethod.isConstructor() && ownerClass.getDefaultConstructor() != null) - { - String qualifiedName = ownerClass.getElement().getQualifiedName(); - if (qualifiedName != null) - { - final Project project = manager.getProject(); - PsiSearchHelper.SERVICE.getInstance(project).processUsagesInNonJavaFiles( - qualifiedName, - (file, startOffset, endOffset) -> { - entryPointsManager.addEntryPoint(refMethod, false); - ignoreElement(processor, refMethod); - return false; - }, - GlobalSearchScope.projectScope(project) - ); - } - } - } - } - } - - @Override - public void visitClass(@Nonnull final RefClass refClass) - { - if (!refClass.isAnonymous()) - { - globalContext.enqueueDerivedClassesProcessor(refClass, inheritor -> { - ignoreElement(processor, refClass); - return false; - }); - - globalContext.enqueueClassUsagesProcessor(refClass, psiReference -> { - ignoreElement(processor, refClass); - return false; - }); - } - } - }); - - } - }); - return false; - } - - private static void ignoreElement(@Nonnull ProblemDescriptionsProcessor processor, @Nonnull RefEntity refElement) - { - processor.ignoreElement(refElement); - - if (refElement instanceof RefClass refClass) - { - RefMethod defaultConstructor = refClass.getDefaultConstructor(); - if (defaultConstructor != null) - { - processor.ignoreElement(defaultConstructor); - return; - } - } - - RefEntity owner = refElement.getOwner(); - if (owner instanceof RefElement) - { - processor.ignoreElement(owner); - } - } - - @Override - public void compose(@Nonnull final StringBuffer buf, @Nonnull final RefEntity refEntity, final HTMLComposer composer) - { - composer.appendElementInReferences(buf, (RefElement) refEntity); - } - - @Override - @Nullable - public QuickFix getQuickFix(final String hint) - { - return new AcceptSuggestedAccess(null, hint); - } - - @Override - @Nullable - public String getHint(@Nonnull final QuickFix fix) - { - return ((AcceptSuggestedAccess) fix).getHint(); - } - - private static class AcceptSuggestedAccess implements LocalQuickFix - { - private final RefManager myManager; - @PsiModifier.ModifierConstant - private final String myHint; - - private AcceptSuggestedAccess(final RefManager manager, @PsiModifier.ModifierConstant String hint) - { - myManager = manager; - myHint = hint; - } - - @Override - @Nonnull - public String getName() - { - return InspectionLocalize.inspectionVisibilityAcceptQuickfix().get(); - } - - @Override - @Nonnull - public String getFamilyName() - { - return getName(); - } - - @Override - @RequiredWriteAction - public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) - { - if (!FileModificationService.getInstance().preparePsiElementForWrite(descriptor.getPsiElement())) - { - return; - } - final PsiModifierListOwner element = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), PsiModifierListOwner.class); - if (element != null) - { - RefElement refElement = null; - if (myManager != null) - { - refElement = myManager.getReference(element); - } - try - { - if (element instanceof PsiVariable variable) - { - variable.normalizeDeclaration(); - } - - PsiModifierList list = element.getModifierList(); - - LOG.assertTrue(list != null); - - if (element instanceof PsiMethod psiMethod) - { - PsiClass containingClass = psiMethod.getContainingClass(); - if (containingClass != null && containingClass.getParent() instanceof PsiFile - && myHint == PsiModifier.PRIVATE && list.hasModifierProperty(PsiModifier.FINAL)) - { - list.setModifierProperty(PsiModifier.FINAL, false); - } - } - - list.setModifierProperty(myHint, true); - if (refElement instanceof RefJavaElement refJavaElement) - { - RefJavaUtil.getInstance().setAccessModifier(refJavaElement, myHint); - } - } - catch (IncorrectOperationException e) - { - LOG.error(e); - } - } - } - - public String getHint() - { - return myHint; - } - } +public class VisibilityInspection extends GlobalJavaInspectionTool implements OldStyleInspection { + private static final Logger LOG = Logger.getInstance(VisibilityInspection.class); + public boolean SUGGEST_PACKAGE_LOCAL_FOR_MEMBERS = true; + public boolean SUGGEST_PACKAGE_LOCAL_FOR_TOP_CLASSES = true; + public boolean SUGGEST_PRIVATE_FOR_INNERS = false; + @NonNls + private static final String SHORT_NAME = "WeakerAccess"; + + private class OptionsPanel extends JPanel { + private final JCheckBox myPackageLocalForMembersCheckbox; + private final JCheckBox myPrivateForInnersCheckbox; + private final JCheckBox myPackageLocalForTopClassesCheckbox; + + private OptionsPanel() { + super(new GridBagLayout()); + + GridBagConstraints gc = new GridBagConstraints(); + gc.fill = GridBagConstraints.HORIZONTAL; + gc.weightx = 1; + gc.weighty = 0; + gc.anchor = GridBagConstraints.NORTHWEST; + + myPackageLocalForMembersCheckbox = new JCheckBox(InspectionLocalize.inspectionVisibilityOption().get()); + myPackageLocalForMembersCheckbox.setSelected(SUGGEST_PACKAGE_LOCAL_FOR_MEMBERS); + myPackageLocalForMembersCheckbox.getModel() + .addChangeListener(e -> SUGGEST_PACKAGE_LOCAL_FOR_MEMBERS = myPackageLocalForMembersCheckbox.isSelected()); + + gc.gridy = 0; + add(myPackageLocalForMembersCheckbox, gc); + + myPackageLocalForTopClassesCheckbox = new JCheckBox(InspectionLocalize.inspectionVisibilityOption1().get()); + myPackageLocalForTopClassesCheckbox.setSelected(SUGGEST_PACKAGE_LOCAL_FOR_TOP_CLASSES); + myPackageLocalForTopClassesCheckbox.getModel() + .addChangeListener(e -> SUGGEST_PACKAGE_LOCAL_FOR_TOP_CLASSES = myPackageLocalForTopClassesCheckbox.isSelected()); + + gc.gridy = 1; + add(myPackageLocalForTopClassesCheckbox, gc); + + + myPrivateForInnersCheckbox = new JCheckBox(InspectionLocalize.inspectionVisibilityOption2().get()); + myPrivateForInnersCheckbox.setSelected(SUGGEST_PRIVATE_FOR_INNERS); + myPrivateForInnersCheckbox.getModel() + .addChangeListener(e -> SUGGEST_PRIVATE_FOR_INNERS = myPrivateForInnersCheckbox.isSelected()); + + gc.gridy = 2; + gc.weighty = 1; + add(myPrivateForInnersCheckbox, gc); + } + } + + @Override + public JComponent createOptionsPanel() { + return new OptionsPanel(); + } + + @Override + @Nonnull + public String getDisplayName() { + return InspectionLocalize.inspectionVisibilityDisplayName().get(); + } + + @Override + @Nonnull + public String getGroupDisplayName() { + return InspectionLocalize.groupNamesDeclarationRedundancy().get(); + } + + @Override + @Nonnull + public String getShortName() { + return SHORT_NAME; + } + + @Override + @Nullable + @RequiredReadAction + public CommonProblemDescriptor[] checkElement( + final RefEntity refEntity, + final AnalysisScope scope, + final InspectionManager manager, + final GlobalInspectionContext globalContext, + final ProblemDescriptionsProcessor processor, + Object state + ) { + if (refEntity instanceof RefJavaElement refElement) { + if (refElement instanceof RefParameter || refElement.isSyntheticJSP()) { + return null; + } + + //ignore entry points. + if (refElement.isEntry()) { + return null; + } + + //ignore implicit constructors. User should not be able to see them. + if (refElement instanceof RefImplicitConstructor) { + return null; + } + + if (refElement instanceof RefField refField && refField.getElement() instanceof PsiEnumConstant) { + return null; + } + + //ignore library override methods. + if (refElement instanceof RefMethod refMethod && (refMethod.isExternalOverride() || refMethod.isEntry())) { + return null; + } + + //ignore anonymous classes. They do not have access modifiers. + if (refElement instanceof RefClass refClass) { + if (refClass.isAnonymous() || refClass.isEntry() || refClass.isTestCase() + || refClass.isServlet() || refClass.isApplet() || refClass.isLocalClass() + || isTopLevelClass(refClass) && !SUGGEST_PACKAGE_LOCAL_FOR_TOP_CLASSES) { + return null; + } + } + + //ignore unreferenced code. They could be a potential entry points. + if (refElement.getInReferences().isEmpty()) { + return null; + } + + //ignore interface members. They always have public access modifier. + if (refElement.getOwner() instanceof RefClass refClass && refClass.isInterface()) { + return null; + } + + String access = getPossibleAccess(refElement); + if (access != refElement.getAccessModifier() && access != null) { + final PsiElement element = refElement.getElement(); + final PsiElement nameIdentifier = element != null ? IdentifierUtil.getNameIdentifier(element) : null; + if (nameIdentifier != null) { + return new ProblemDescriptor[]{ + manager.createProblemDescriptor( + nameIdentifier, + access.equals(PsiModifier.PRIVATE) + ? InspectionLocalize.inspectionVisibilityComposeSuggestion("private").get() + : access.equals(PsiModifier.PACKAGE_LOCAL) + ? InspectionLocalize.inspectionVisibilityComposeSuggestion("package local").get() + : InspectionLocalize.inspectionVisibilityComposeSuggestion("protected").get(), + new AcceptSuggestedAccess(globalContext.getRefManager(), access), + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, false + ) + }; + } + } + } + return null; + } + + @Nullable + @PsiModifier.ModifierConstant + public String getPossibleAccess(@Nullable RefJavaElement refElement) { + if (refElement == null) { + return null; + } + String curAccess = refElement.getAccessModifier(); + String weakestAccess = PsiModifier.PRIVATE; + + if (isTopLevelClass(refElement) || isCalledOnSubClasses(refElement)) { + weakestAccess = PsiModifier.PACKAGE_LOCAL; + } + + if (isAbstractMethod(refElement)) { + weakestAccess = PsiModifier.PROTECTED; + } + + if (curAccess == weakestAccess) { + return curAccess; + } + + while (true) { + String weakerAccess = getWeakerAccess(curAccess, refElement); + if (weakerAccess == null || RefJavaUtil.getInstance().compareAccess(weakerAccess, weakestAccess) < 0) { + break; + } + if (isAccessible(refElement, weakerAccess)) { + curAccess = weakerAccess; + } + else { + break; + } + } + + return curAccess; + } + + private static boolean isCalledOnSubClasses(RefElement refElement) { + return refElement instanceof RefMethod refMethod && refMethod.isCalledOnSubClass(); + } + + private static boolean isAbstractMethod(RefElement refElement) { + return refElement instanceof RefMethod refMethod && refMethod.isAbstract(); + } + + private static boolean isTopLevelClass(RefElement refElement) { + return refElement instanceof RefClass && RefJavaUtil.getInstance().getTopLevelClass(refElement) == refElement; + } + + @Nullable + @PsiModifier.ModifierConstant + private String getWeakerAccess(@PsiModifier.ModifierConstant String curAccess, RefElement refElement) { + if (curAccess == PsiModifier.PUBLIC) { + return isTopLevelClass(refElement) ? PsiModifier.PACKAGE_LOCAL : PsiModifier.PROTECTED; + } + if (curAccess == PsiModifier.PROTECTED) { + return SUGGEST_PACKAGE_LOCAL_FOR_MEMBERS ? PsiModifier.PACKAGE_LOCAL : PsiModifier.PRIVATE; + } + if (curAccess == PsiModifier.PACKAGE_LOCAL) { + return PsiModifier.PRIVATE; + } + + return null; + } + + private boolean isAccessible(RefJavaElement to, @PsiModifier.ModifierConstant String accessModifier) { + for (RefElement refElement : to.getInReferences()) { + if (!isAccessibleFrom(refElement, to, accessModifier)) { + return false; + } + } + + if (to instanceof RefMethod refMethod) { + if (refMethod.isAbstract() && (refMethod.getDerivedMethods() + .isEmpty() || refMethod.getAccessModifier() == PsiModifier.PRIVATE)) { + return false; + } + + for (RefMethod refOverride : refMethod.getDerivedMethods()) { + if (!isAccessibleFrom(refOverride, to, accessModifier)) { + return false; + } + } + + for (RefMethod refSuper : refMethod.getSuperMethods()) { + if (RefJavaUtil.getInstance().compareAccess(refSuper.getAccessModifier(), accessModifier) > 0) { + return false; + } + } + } + + if (to instanceof RefClass refClass) { + for (RefClass subClass : refClass.getSubClasses()) { + if (!isAccessibleFrom(subClass, to, accessModifier)) { + return false; + } + } + + List children = refClass.getChildren(); + if (children != null) { + for (Object refElement : children) { + if (!isAccessible((RefJavaElement)refElement, accessModifier)) { + return false; + } + } + } + + for (final RefElement refElement : refClass.getInTypeReferences()) { + if (!isAccessibleFrom(refElement, refClass, accessModifier)) { + return false; + } + } + + List classExporters = ((RefClassImpl)refClass).getClassExporters(); + if (classExporters != null) { + for (RefJavaElement refExporter : classExporters) { + if (getAccessLevel(accessModifier) < getAccessLevel(refExporter.getAccessModifier())) { + return false; + } + } + } + } + + return true; + } + + private static int getAccessLevel(@PsiModifier.ModifierConstant String access) { + if (access == PsiModifier.PRIVATE) { + return 1; + } + if (access == PsiModifier.PACKAGE_LOCAL) { + return 2; + } + if (access == PsiModifier.PROTECTED) { + return 3; + } + return 4; + } + + private boolean isAccessibleFrom(RefElement from, RefJavaElement to, String accessModifier) { + if (accessModifier == PsiModifier.PUBLIC) { + return true; + } + + final RefJavaUtil refUtil = RefJavaUtil.getInstance(); + if (accessModifier == PsiModifier.PACKAGE_LOCAL) { + return RefJavaUtil.getPackage(from) == RefJavaUtil.getPackage(to); + } + + RefClass fromTopLevel = refUtil.getTopLevelClass(from); + RefClass toTopLevel = refUtil.getTopLevelClass(to); + RefClass fromOwner = refUtil.getOwnerClass(from); + RefClass toOwner = refUtil.getOwnerClass(to); + + if (accessModifier == PsiModifier.PROTECTED) { + if (SUGGEST_PRIVATE_FOR_INNERS) { + return refUtil.isInheritor(fromTopLevel, toOwner) + || fromOwner != null && refUtil.isInheritor(fromOwner, toTopLevel) + || toOwner != null && refUtil.getOwnerClass(toOwner) == from; + } + + return refUtil.isInheritor(fromTopLevel, toOwner); + } + + if (accessModifier == PsiModifier.PRIVATE) { + if (SUGGEST_PRIVATE_FOR_INNERS) { + if (isInExtendsList(to, fromTopLevel.getElement().getExtendsList()) + || isInExtendsList(to, fromTopLevel.getElement().getImplementsList()) + || isInAnnotations(to, fromTopLevel)) { + return false; + } + return fromTopLevel == toOwner || fromOwner == toTopLevel || toOwner != null && refUtil.getOwnerClass(toOwner) == from; + } + + if (fromOwner != null && fromOwner.isStatic() && !to.isStatic() && refUtil.isInheritor(fromOwner, toOwner)) { + return false; + } + + if (fromTopLevel == toOwner) { + if (from instanceof RefClass fromRefClass && to instanceof RefClass) { + final PsiClass fromClass = fromRefClass.getElement(); + LOG.assertTrue(fromClass != null); + if (isInExtendsList(to, fromClass.getExtendsList()) + || isInExtendsList(to, fromClass.getImplementsList())) { + return false; + } + } + + return true; + } + } + + return false; + } + + private static boolean isInAnnotations(final RefJavaElement to, final RefClass fromTopLevel) { + final PsiModifierList modifierList = fromTopLevel.getElement().getModifierList(); + if (modifierList == null) { + return false; + } + final PsiElement toElement = to.getElement(); + + final boolean[] resolved = new boolean[]{false}; + modifierList.accept(new JavaRecursiveElementWalkingVisitor() { + @RequiredReadAction + @Override + public void visitReferenceExpression(PsiReferenceExpression expression) { + if (resolved[0]) { + return; + } + super.visitReferenceExpression(expression); + if (expression.resolve() == toElement) { + resolved[0] = true; + } + } + }); + return resolved[0]; + } + + private static boolean isInExtendsList(final RefJavaElement to, final PsiReferenceList extendsList) { + if (extendsList != null) { + final PsiJavaCodeReferenceElement[] referenceElements = extendsList.getReferenceElements(); + for (PsiJavaCodeReferenceElement referenceElement : referenceElements) { + final PsiReferenceParameterList parameterList = referenceElement.getParameterList(); + if (parameterList != null) { + for (PsiType type : parameterList.getTypeArguments()) { + if (extendsList.getManager().areElementsEquivalent(PsiUtil.resolveClassInType(type), to.getElement())) { + return true; + } + } + } + } + } + return false; + } + + + @Override + protected boolean queryExternalUsagesRequests( + final RefManager manager, + final GlobalJavaInspectionContext globalContext, + final ProblemDescriptionsProcessor processor, + Object state + ) { + final EntryPointsManager entryPointsManager = globalContext.getEntryPointsManager(manager); + for (RefElement entryPoint : entryPointsManager.getEntryPoints()) { + ignoreElement(processor, entryPoint); + } + + for (VisibilityExtension addin : VisibilityExtension.EP_NAME.getExtensions()) { + addin.fillIgnoreList(manager, processor); + } + manager.iterate(new RefJavaVisitor() { + @Override + public void visitElement(@Nonnull final RefEntity refEntity) { + if (!(refEntity instanceof RefElement)) { + return; + } + if (processor.getDescriptions(refEntity) == null) { + return; + } + refEntity.accept(new RefJavaVisitor() { + @Override + public void visitField(@Nonnull final RefField refField) { + if (refField.getAccessModifier() != PsiModifier.PRIVATE) { + globalContext.enqueueFieldUsagesProcessor(refField, psiReference -> { + ignoreElement(processor, refField); + return false; + }); + } + } + + @Override + public void visitMethod(@Nonnull final RefMethod refMethod) { + if (!refMethod.isExternalOverride() && refMethod.getAccessModifier() != PsiModifier.PRIVATE + && !(refMethod instanceof RefImplicitConstructor)) { + globalContext.enqueueDerivedMethodsProcessor(refMethod, derivedMethod -> { + ignoreElement(processor, refMethod); + return false; + }); + + globalContext.enqueueMethodUsagesProcessor(refMethod, psiReference -> { + ignoreElement(processor, refMethod); + return false; + }); + + if (entryPointsManager.isAddNonJavaEntries()) { + final RefClass ownerClass = refMethod.getOwnerClass(); + if (refMethod.isConstructor() && ownerClass.getDefaultConstructor() != null) { + String qualifiedName = ownerClass.getElement().getQualifiedName(); + if (qualifiedName != null) { + final Project project = manager.getProject(); + PsiSearchHelper.SERVICE.getInstance(project).processUsagesInNonJavaFiles( + qualifiedName, + (file, startOffset, endOffset) -> { + entryPointsManager.addEntryPoint(refMethod, false); + ignoreElement(processor, refMethod); + return false; + }, + GlobalSearchScope.projectScope(project) + ); + } + } + } + } + } + + @Override + public void visitClass(@Nonnull final RefClass refClass) { + if (!refClass.isAnonymous()) { + globalContext.enqueueDerivedClassesProcessor(refClass, inheritor -> { + ignoreElement(processor, refClass); + return false; + }); + + globalContext.enqueueClassUsagesProcessor(refClass, psiReference -> { + ignoreElement(processor, refClass); + return false; + }); + } + } + }); + + } + }); + return false; + } + + private static void ignoreElement(@Nonnull ProblemDescriptionsProcessor processor, @Nonnull RefEntity refElement) { + processor.ignoreElement(refElement); + + if (refElement instanceof RefClass refClass) { + RefMethod defaultConstructor = refClass.getDefaultConstructor(); + if (defaultConstructor != null) { + processor.ignoreElement(defaultConstructor); + return; + } + } + + RefEntity owner = refElement.getOwner(); + if (owner instanceof RefElement) { + processor.ignoreElement(owner); + } + } + + @Override + public void compose(@Nonnull final StringBuffer buf, @Nonnull final RefEntity refEntity, final HTMLComposer composer) { + composer.appendElementInReferences(buf, (RefElement)refEntity); + } + + @Override + @Nullable + public QuickFix getQuickFix(final String hint) { + return new AcceptSuggestedAccess(null, hint); + } + + @Override + @Nullable + public String getHint(@Nonnull final QuickFix fix) { + return ((AcceptSuggestedAccess)fix).getHint(); + } + + private static class AcceptSuggestedAccess implements LocalQuickFix { + private final RefManager myManager; + @PsiModifier.ModifierConstant + private final String myHint; + + private AcceptSuggestedAccess(final RefManager manager, @PsiModifier.ModifierConstant String hint) { + myManager = manager; + myHint = hint; + } + + @Override + @Nonnull + public String getName() { + return InspectionLocalize.inspectionVisibilityAcceptQuickfix().get(); + } + + @Override + @Nonnull + public String getFamilyName() { + return getName(); + } + + @Override + @RequiredWriteAction + public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { + if (!FileModificationService.getInstance().preparePsiElementForWrite(descriptor.getPsiElement())) { + return; + } + final PsiModifierListOwner element = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), PsiModifierListOwner.class); + if (element != null) { + RefElement refElement = null; + if (myManager != null) { + refElement = myManager.getReference(element); + } + try { + if (element instanceof PsiVariable variable) { + variable.normalizeDeclaration(); + } + + PsiModifierList list = element.getModifierList(); + + LOG.assertTrue(list != null); + + if (element instanceof PsiMethod psiMethod) { + PsiClass containingClass = psiMethod.getContainingClass(); + if (containingClass != null && containingClass.getParent() instanceof PsiFile + && myHint == PsiModifier.PRIVATE && list.hasModifierProperty(PsiModifier.FINAL)) { + list.setModifierProperty(PsiModifier.FINAL, false); + } + } + + list.setModifierProperty(myHint, true); + if (refElement instanceof RefJavaElement refJavaElement) { + RefJavaUtil.getInstance().setAccessModifier(refJavaElement, myHint); + } + } + catch (IncorrectOperationException e) { + LOG.error(e); + } + } + } + + public String getHint() { + return myHint; + } + } } diff --git a/plugin/src/main/java/com/intellij/java/impl/generate/GenerateToStringActionHandlerImpl.java b/plugin/src/main/java/com/intellij/java/impl/generate/GenerateToStringActionHandlerImpl.java index b4c6fe0a62..7acce98049 100644 --- a/plugin/src/main/java/com/intellij/java/impl/generate/GenerateToStringActionHandlerImpl.java +++ b/plugin/src/main/java/com/intellij/java/impl/generate/GenerateToStringActionHandlerImpl.java @@ -53,6 +53,7 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; @@ -68,226 +69,243 @@ @Singleton @ServiceImpl public class GenerateToStringActionHandlerImpl implements GenerateToStringActionHandler, CodeInsightActionHandler { - private static final Logger logger = Logger.getInstance("#GenerateToStringActionHandlerImpl"); - - @Override - public boolean startInWriteAction() { - return true; - } - - @Override - public void invoke(@Nonnull Project project, @Nonnull Editor editor, @Nonnull PsiFile file) { - PsiClass clazz = getSubjectClass(editor, file); - assert clazz != null; - - doExecuteAction(project, clazz, editor); - } - + private static final Logger logger = Logger.getInstance("#GenerateToStringActionHandlerImpl"); - @Override - public void executeActionQuickFix(final Project project, final PsiClass clazz) { - doExecuteAction(project, clazz, null); - } + @Override + public boolean startInWriteAction() { + return true; + } - private static void doExecuteAction(@Nonnull final Project project, @Nonnull final PsiClass clazz, final Editor editor) { - logger.debug("+++ doExecuteAction - START +++"); + @Override + public void invoke(@Nonnull Project project, @Nonnull Editor editor, @Nonnull PsiFile file) { + PsiClass clazz = getSubjectClass(editor, file); + assert clazz != null; - if (logger.isDebugEnabled()) { - logger.debug("Current project " + project.getName()); + doExecuteAction(project, clazz, editor); } - final PsiElementClassMember[] dialogMembers = buildMembersToShow(clazz); - final MemberChooserHeaderPanel header = new MemberChooserHeaderPanel(clazz); - logger.debug("Displaying member chooser dialog"); - ApplicationManager.getApplication().invokeLater(new Runnable() { - @Override - public void run() { - if (project.isDisposed()) { - return; - } - final MemberChooser chooser = new MemberChooser(dialogMembers, true, true, project, PsiUtil.isLanguageLevel5OrHigher(clazz), header) { - @Nullable - @Override - protected String getHelpId() { - return "editing.altInsert.tostring"; - } - }; - chooser.setTitle("Generate toString()"); - - chooser.setCopyJavadocVisible(false); - chooser.selectElements(dialogMembers); - header.setChooser(chooser); - chooser.show(); - - if (DialogWrapper.OK_EXIT_CODE == chooser.getExitCode()) { - Collection selectedMembers = GenerationUtil.convertClassMembersToPsiMembers(chooser.getSelectedElements()); - - final TemplateResource template = header.getSelectedTemplate(); - ToStringTemplatesManager.getInstance().setDefaultTemplate(template); - - if (template.isValidTemplate()) { - GenerateToStringWorker.executeGenerateActionLater(clazz, editor, selectedMembers, template, chooser.isInsertOverrideAnnotation()); - } else { - HintManager.getInstance().showErrorHint(editor, "toString() template '" + template.getFileName() + "' is invalid"); - } - } - } - }); - - logger.debug("+++ doExecuteAction - END +++"); - } - - public static void updateDialog(PsiClass clazz, MemberChooser dialog) { - final PsiElementClassMember[] members = buildMembersToShow(clazz); - dialog.resetElements(members); - dialog.selectElements(members); - } - - private static PsiElementClassMember[] buildMembersToShow(PsiClass clazz) { - Config config = GenerateToStringContext.getConfig(); - PsiField[] filteredFields = GenerateToStringUtils.filterAvailableFields(clazz, config.getFilterPattern()); - if (logger.isDebugEnabled()) { - logger.debug("Number of fields after filtering: " + filteredFields.length); - } - PsiMethod[] filteredMethods; - if (config.enableMethods) { - // filter methods as it is enabled from config - filteredMethods = GenerateToStringUtils.filterAvailableMethods(clazz, config.getFilterPattern()); - if (logger.isDebugEnabled()) { - logger.debug("Number of methods after filtering: " + filteredMethods.length); - } - } else { - filteredMethods = PsiMethod.EMPTY_ARRAY; + @Override + public void executeActionQuickFix(final Project project, final PsiClass clazz) { + doExecuteAction(project, clazz, null); } - return GenerationUtil.combineToClassMemberList(filteredFields, filteredMethods); - } + private static void doExecuteAction(@Nonnull final Project project, @Nonnull final PsiClass clazz, final Editor editor) { + logger.debug("+++ doExecuteAction - START +++"); - @Nullable - private static PsiClass getSubjectClass(Editor editor, final PsiFile file) { - if (file == null) { - return null; - } + if (logger.isDebugEnabled()) { + logger.debug("Current project " + project.getName()); + } - int offset = editor.getCaretModel().getOffset(); - PsiElement context = file.findElementAt(offset); + final PsiElementClassMember[] dialogMembers = buildMembersToShow(clazz); - if (context == null) { - return null; - } + final MemberChooserHeaderPanel header = new MemberChooserHeaderPanel(clazz); + logger.debug("Displaying member chooser dialog"); + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + if (project.isDisposed()) { + return; + } + final MemberChooser chooser = new MemberChooser( + dialogMembers, + true, + true, + project, + PsiUtil.isLanguageLevel5OrHigher(clazz), + header + ) { + @Nullable + @Override + protected String getHelpId() { + return "editing.altInsert.tostring"; + } + }; + chooser.setTitle("Generate toString()"); + + chooser.setCopyJavadocVisible(false); + chooser.selectElements(dialogMembers); + header.setChooser(chooser); + chooser.show(); + + if (DialogWrapper.OK_EXIT_CODE == chooser.getExitCode()) { + Collection selectedMembers = GenerationUtil.convertClassMembersToPsiMembers(chooser.getSelectedElements()); + + final TemplateResource template = header.getSelectedTemplate(); + ToStringTemplatesManager.getInstance().setDefaultTemplate(template); + + if (template.isValidTemplate()) { + GenerateToStringWorker.executeGenerateActionLater( + clazz, + editor, + selectedMembers, + template, + chooser.isInsertOverrideAnnotation() + ); + } + else { + HintManager.getInstance().showErrorHint(editor, "toString() template '" + template.getFileName() + "' is invalid"); + } + } + } + }); - PsiClass clazz = PsiTreeUtil.getParentOfType(context, PsiClass.class, false); - if (clazz == null) { - return null; + logger.debug("+++ doExecuteAction - END +++"); } - //exclude interfaces, non-java classes etc - for (GenerateToStringClassFilter filter : GenerateToStringClassFilter.EP_NAME.getExtensionList()) { - if (!filter.canGenerateToString(clazz)) { - return null; - } + public static void updateDialog(PsiClass clazz, MemberChooser dialog) { + final PsiElementClassMember[] members = buildMembersToShow(clazz); + dialog.resetElements(members); + dialog.selectElements(members); } - return clazz; - } - public static class MemberChooserHeaderPanel extends JPanel { - private MemberChooser chooser; - private final ComboBox comboBox; + private static PsiElementClassMember[] buildMembersToShow(PsiClass clazz) { + Config config = GenerateToStringContext.getConfig(); + PsiField[] filteredFields = GenerateToStringUtils.filterAvailableFields(clazz, config.getFilterPattern()); + if (logger.isDebugEnabled()) { + logger.debug("Number of fields after filtering: " + filteredFields.length); + } + PsiMethod[] filteredMethods; + if (config.enableMethods) { + // filter methods as it is enabled from config + filteredMethods = GenerateToStringUtils.filterAvailableMethods(clazz, config.getFilterPattern()); + if (logger.isDebugEnabled()) { + logger.debug("Number of methods after filtering: " + filteredMethods.length); + } + } + else { + filteredMethods = PsiMethod.EMPTY_ARRAY; + } - public void setChooser(MemberChooser chooser) { - this.chooser = chooser; + return GenerationUtil.combineToClassMemberList(filteredFields, filteredMethods); } - public MemberChooserHeaderPanel(final PsiClass clazz) { - super(new GridBagLayout()); - - final Collection templates = ToStringTemplatesManager.getInstance().getAllTemplates(); - final TemplateResource[] all = templates.toArray(new TemplateResource[templates.size()]); + @Nullable + private static PsiClass getSubjectClass(Editor editor, final PsiFile file) { + if (file == null) { + return null; + } - final JButton settingsButton = new JButton("Settings"); - settingsButton.setMnemonic(KeyEvent.VK_S); + int offset = editor.getCaretModel().getOffset(); + PsiElement context = file.findElementAt(offset); - comboBox = new ComboBox<>(all); - comboBox.setRenderer(new ColoredListCellRenderer() { + if (context == null) { + return null; + } - @Override - protected void customizeCellRenderer(@Nonnull JList jList, - TemplateResource templateResource, - int i, - boolean b, - boolean b1) { - append(templateResource.getName()); + PsiClass clazz = PsiTreeUtil.getParentOfType(context, PsiClass.class, false); + if (clazz == null) { + return null; } - }); - settingsButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - final TemplatesPanel ui = new TemplatesPanel(clazz.getProject()); - Disposable disposable = Disposable.newDisposable(); - Configurable composite = new TabbedConfigurable(disposable) { - @Override - protected List createConfigurables() { - List res = new ArrayList(); - res.add(new GenerateToStringConfigurable(clazz.getProject())); - res.add(ui); - return res; - } - @Override - public String getDisplayName() { - return "toString() Generation Settings"; + //exclude interfaces, non-java classes etc + for (GenerateToStringClassFilter filter : GenerateToStringClassFilter.EP_NAME.getExtensionList()) { + if (!filter.canGenerateToString(clazz)) { + return null; } + } + return clazz; + } - @Override - public String getHelpTopic() { - return "editing.altInsert.tostring.settings"; - } + public static class MemberChooserHeaderPanel extends JPanel { + private MemberChooser chooser; + private final ComboBox comboBox; - @Override - public void apply() throws ConfigurationException { - super.apply(); - updateDialog(clazz, chooser); - - comboBox.removeAllItems(); - for (TemplateResource resource : ToStringTemplatesManager.getInstance().getAllTemplates()) { - comboBox.addItem(resource); - } - comboBox.setSelectedItem(ToStringTemplatesManager.getInstance().getDefaultTemplate()); - } - }; + public void setChooser(MemberChooser chooser) { + this.chooser = chooser; + } - ShowSettingsUtil.getInstance().editConfigurable(MemberChooserHeaderPanel.this, composite, new Runnable() { - @Override - public void run() { - ui.selectItem(ToStringTemplatesManager.getInstance().getDefaultTemplate()); - } - }); - Disposer.dispose(disposable); + public MemberChooserHeaderPanel(final PsiClass clazz) { + super(new GridBagLayout()); + + final Collection templates = ToStringTemplatesManager.getInstance().getAllTemplates(); + final TemplateResource[] all = templates.toArray(new TemplateResource[templates.size()]); + + final JButton settingsButton = new JButton("Settings"); + settingsButton.setMnemonic(KeyEvent.VK_S); + + comboBox = new ComboBox<>(all); + comboBox.setRenderer(new ColoredListCellRenderer() { + + @Override + protected void customizeCellRenderer( + @Nonnull JList jList, + TemplateResource templateResource, + int i, + boolean b, + boolean b1 + ) { + append(templateResource.getName()); + } + }); + settingsButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + final TemplatesPanel ui = new TemplatesPanel(clazz.getProject()); + Disposable disposable = Disposable.newDisposable(); + Configurable composite = new TabbedConfigurable(disposable) { + @Override + protected List createConfigurables() { + List res = new ArrayList(); + res.add(new GenerateToStringConfigurable(clazz.getProject())); + res.add(ui); + return res; + } + + @Override + public String getDisplayName() { + return "toString() Generation Settings"; + } + + @Override + public String getHelpTopic() { + return "editing.altInsert.tostring.settings"; + } + + @Override + public void apply() throws ConfigurationException { + super.apply(); + updateDialog(clazz, chooser); + + comboBox.removeAllItems(); + for (TemplateResource resource : ToStringTemplatesManager.getInstance().getAllTemplates()) { + comboBox.addItem(resource); + } + comboBox.setSelectedItem(ToStringTemplatesManager.getInstance().getDefaultTemplate()); + } + }; + + ShowSettingsUtil.getInstance().editConfigurable(MemberChooserHeaderPanel.this, composite, new Runnable() { + @Override + public void run() { + ui.selectItem(ToStringTemplatesManager.getInstance().getDefaultTemplate()); + } + }); + Disposer.dispose(disposable); + } + }); + + comboBox.setSelectedItem(ToStringTemplatesManager.getInstance().getDefaultTemplate()); + + final JLabel templatesLabel = new JLabel("Template: "); + templatesLabel.setDisplayedMnemonic('T'); + templatesLabel.setLabelFor(comboBox); + + final GridBagConstraints constraints = new GridBagConstraints(); + constraints.anchor = GridBagConstraints.BASELINE; + constraints.gridx = 0; + add(templatesLabel, constraints); + constraints.gridx = 1; + constraints.weightx = 1.0; + constraints.fill = GridBagConstraints.HORIZONTAL; + add(comboBox, constraints); + constraints.gridx = 2; + constraints.weightx = 0.0; + add(settingsButton, constraints); } - }); - - comboBox.setSelectedItem(ToStringTemplatesManager.getInstance().getDefaultTemplate()); - - final JLabel templatesLabel = new JLabel("Template: "); - templatesLabel.setDisplayedMnemonic('T'); - templatesLabel.setLabelFor(comboBox); - - final GridBagConstraints constraints = new GridBagConstraints(); - constraints.anchor = GridBagConstraints.BASELINE; - constraints.gridx = 0; - add(templatesLabel, constraints); - constraints.gridx = 1; - constraints.weightx = 1.0; - constraints.fill = GridBagConstraints.HORIZONTAL; - add(comboBox, constraints); - constraints.gridx = 2; - constraints.weightx = 0.0; - add(settingsButton, constraints); - } - public TemplateResource getSelectedTemplate() { - return (TemplateResource) comboBox.getSelectedItem(); + public TemplateResource getSelectedTemplate() { + return (TemplateResource)comboBox.getSelectedItem(); + } } - } } diff --git a/plugin/src/main/java/com/intellij/java/impl/ide/favoritesTreeView/smartPointerPsiNodes/BaseSmartPointerPsiNode.java b/plugin/src/main/java/com/intellij/java/impl/ide/favoritesTreeView/smartPointerPsiNodes/BaseSmartPointerPsiNode.java index a827546993..449e17e0ce 100644 --- a/plugin/src/main/java/com/intellij/java/impl/ide/favoritesTreeView/smartPointerPsiNodes/BaseSmartPointerPsiNode.java +++ b/plugin/src/main/java/com/intellij/java/impl/ide/favoritesTreeView/smartPointerPsiNodes/BaseSmartPointerPsiNode.java @@ -29,114 +29,126 @@ import consulo.virtualFileSystem.VirtualFile; import jakarta.annotation.Nonnull; + import java.util.ArrayList; import java.util.Collection; public abstract class BaseSmartPointerPsiNode extends ProjectViewNode implements PsiElementNavigationItem { - private static final Logger LOG = Logger.getInstance(BaseSmartPointerPsiNode.class); - - protected BaseSmartPointerPsiNode(Project project, Type value, ViewSettings viewSettings) { - super(project, value, viewSettings); - } - - @Override - @Nonnull - public final Collection getChildren() { - PsiElement value = getPsiElement(); - if (value == null) return new ArrayList<>(); - LOG.assertTrue(value.isValid()); - return getChildrenImpl(); - } - - @Nonnull - protected abstract Collection getChildrenImpl(); - - protected boolean isMarkReadOnly() { - final Object parentValue = getParentValue(); - return parentValue instanceof PsiDirectory || parentValue instanceof PackageElement; - } - - @Override - public PsiElement getTargetElement() { - VirtualFile file = getVirtualFileForValue(); - if (file == null) { - return null; - } else { - return file.isDirectory() ? PsiManager.getInstance(getProject()).findDirectory(file) : PsiManager.getInstance(getProject()).findFile(file); + private static final Logger LOG = Logger.getInstance(BaseSmartPointerPsiNode.class); + + protected BaseSmartPointerPsiNode(Project project, Type value, ViewSettings viewSettings) { + super(project, value, viewSettings); + } + + @Override + @Nonnull + public final Collection getChildren() { + PsiElement value = getPsiElement(); + if (value == null) { + return new ArrayList<>(); + } + LOG.assertTrue(value.isValid()); + return getChildrenImpl(); } - } - private VirtualFile getVirtualFileForValue() { - PsiElement value = getPsiElement(); - if (value == null) return null; - return PsiUtilCore.getVirtualFile(value); - } - // Should be called in atomic action + @Nonnull + protected abstract Collection getChildrenImpl(); + + protected boolean isMarkReadOnly() { + final Object parentValue = getParentValue(); + return parentValue instanceof PsiDirectory || parentValue instanceof PackageElement; + } - protected abstract void updateImpl(PresentationData data); + @Override + public PsiElement getTargetElement() { + VirtualFile file = getVirtualFileForValue(); + if (file == null) { + return null; + } + else { + return file.isDirectory() + ? PsiManager.getInstance(getProject()).findDirectory(file) + : PsiManager.getInstance(getProject()).findFile(file); + } + } + private VirtualFile getVirtualFileForValue() { + PsiElement value = getPsiElement(); + if (value == null) { + return null; + } + return PsiUtilCore.getVirtualFile(value); + } + // Should be called in atomic action + + protected abstract void updateImpl(PresentationData data); + + + @Override + public void update(PresentationData data) { + final PsiElement value = getPsiElement(); + if (value == null || !value.isValid()) { + setValue(null); + } + if (getPsiElement() == null) { + return; + } + + int flags = Iconable.ICON_FLAG_VISIBILITY; + if (isMarkReadOnly()) { + flags |= Iconable.ICON_FLAG_READ_STATUS; + } + + LOG.assertTrue(value.isValid()); + + Image icon = IconDescriptorUpdaters.getIcon(value, flags); + data.setIcon(icon); + data.setPresentableText(myName); + if (isDeprecated()) { + data.setAttributesKey(CodeInsightColors.DEPRECATED_ATTRIBUTES); + } + updateImpl(data); + for (ProjectViewNodeDecorator decorator : ProjectViewNodeDecorator.EP_NAME.getExtensionList(myProject)) { + decorator.decorate(this, data); + } + } - @Override - public void update(PresentationData data) { - final PsiElement value = getPsiElement(); - if (value == null || !value.isValid()) { - setValue(null); + private boolean isDeprecated() { + final PsiElement element = getPsiElement(); + return element instanceof PsiDocCommentOwner + && element.isValid() + && ((PsiDocCommentOwner)element).isDeprecated(); } - if (getPsiElement() == null) return; - int flags = Iconable.ICON_FLAG_VISIBILITY; - if (isMarkReadOnly()) { - flags |= Iconable.ICON_FLAG_READ_STATUS; + @Override + public boolean contains(@Nonnull VirtualFile file) { + if (getPsiElement() == null) { + return false; + } + PsiFile containingFile = getPsiElement().getContainingFile(); + return file.equals(containingFile.getVirtualFile()); } - LOG.assertTrue(value.isValid()); + @Override + public void navigate(boolean requestFocus) { + if (canNavigate()) { + ((NavigationItem)getPsiElement()).navigate(requestFocus); + } + } - Image icon = IconDescriptorUpdaters.getIcon(value, flags); - data.setIcon(icon); - data.setPresentableText(myName); - if (isDeprecated()) { - data.setAttributesKey(CodeInsightColors.DEPRECATED_ATTRIBUTES); + @Override + public boolean canNavigate() { + return getPsiElement() instanceof NavigationItem && ((NavigationItem)getPsiElement()).canNavigate(); } - updateImpl(data); - for (ProjectViewNodeDecorator decorator : ProjectViewNodeDecorator.EP_NAME.getExtensionList(myProject)) { - decorator.decorate(this, data); + + @Override + public boolean canNavigateToSource() { + return getPsiElement() instanceof NavigationItem && ((NavigationItem)getPsiElement()).canNavigateToSource(); } - } - - private boolean isDeprecated() { - final PsiElement element = getPsiElement(); - return element instanceof PsiDocCommentOwner - && element.isValid() - && ((PsiDocCommentOwner) element).isDeprecated(); - } - - @Override - public boolean contains(@Nonnull VirtualFile file) { - if (getPsiElement() == null) return false; - PsiFile containingFile = getPsiElement().getContainingFile(); - return file.equals(containingFile.getVirtualFile()); - } - - @Override - public void navigate(boolean requestFocus) { - if (canNavigate()) { - ((NavigationItem) getPsiElement()).navigate(requestFocus); + + protected PsiElement getPsiElement() { + //noinspection CastToIncompatibleInterface + return (PsiElement)getValue(); // automatically de-anchorized in AbstractTreeNode.getValue } - } - - @Override - public boolean canNavigate() { - return getPsiElement() instanceof NavigationItem && ((NavigationItem) getPsiElement()).canNavigate(); - } - - @Override - public boolean canNavigateToSource() { - return getPsiElement() instanceof NavigationItem && ((NavigationItem) getPsiElement()).canNavigateToSource(); - } - - protected PsiElement getPsiElement() { - //noinspection CastToIncompatibleInterface - return (PsiElement) getValue(); // automatically de-anchorized in AbstractTreeNode.getValue - } } diff --git a/plugin/src/main/java/com/intellij/java/impl/ig/performance/ClassInitializerMayBeStaticInspection.java b/plugin/src/main/java/com/intellij/java/impl/ig/performance/ClassInitializerMayBeStaticInspection.java index 85ef72df35..653f50d8ee 100644 --- a/plugin/src/main/java/com/intellij/java/impl/ig/performance/ClassInitializerMayBeStaticInspection.java +++ b/plugin/src/main/java/com/intellij/java/impl/ig/performance/ClassInitializerMayBeStaticInspection.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.intellij.java.impl.ig.performance; import com.intellij.java.impl.ig.fixes.ChangeModifierFix; @@ -38,62 +37,62 @@ */ @ExtensionImpl public class ClassInitializerMayBeStaticInspection extends BaseInspection { - @Override - public boolean isEnabledByDefault() { - return true; - } - - @Override - @Nonnull - protected String buildErrorString(Object... infos) { - return InspectionGadgetsLocalize.classInitializerMayBeStaticProblemDescriptor().get(); - } + @Override + public boolean isEnabledByDefault() { + return true; + } - @Override - protected InspectionGadgetsFix buildFix(Object... infos) { - return new ChangeModifierFix(PsiModifier.STATIC); - } + @Override + @Nonnull + protected String buildErrorString(Object... infos) { + return InspectionGadgetsLocalize.classInitializerMayBeStaticProblemDescriptor().get(); + } - @Override - @Nls - @Nonnull - public String getDisplayName() { - return InspectionGadgetsLocalize.classInitializerMayBeStaticDisplayName().get(); - } + @Override + protected InspectionGadgetsFix buildFix(Object... infos) { + return new ChangeModifierFix(PsiModifier.STATIC); + } - @Override - public BaseInspectionVisitor buildVisitor() { - return new ClassInitializerCanBeStaticVisitor(); - } + @Override + @Nls + @Nonnull + public String getDisplayName() { + return InspectionGadgetsLocalize.classInitializerMayBeStaticDisplayName().get(); + } - private static class ClassInitializerCanBeStaticVisitor extends BaseInspectionVisitor { @Override - public void visitClassInitializer(PsiClassInitializer initializer) { - if (initializer.hasModifierProperty(PsiModifier.STATIC)) { - return; - } + public BaseInspectionVisitor buildVisitor() { + return new ClassInitializerCanBeStaticVisitor(); + } - final PsiClass containingClass = ClassUtils.getContainingClass(initializer); - if (containingClass == null) { - return; - } - for (CantBeStaticCondition addin : JavaExtensionPoints.CANT_BE_STATIC_EP_NAME.getExtensions()) { - if (addin.cantBeStatic(initializer)) { - return; - } - } - final PsiElement scope = containingClass.getScope(); - if (!(scope instanceof PsiJavaFile) && !containingClass.hasModifierProperty(PsiModifier.STATIC)) { - return; - } + private static class ClassInitializerCanBeStaticVisitor extends BaseInspectionVisitor { + @Override + public void visitClassInitializer(PsiClassInitializer initializer) { + if (initializer.hasModifierProperty(PsiModifier.STATIC)) { + return; + } + + final PsiClass containingClass = ClassUtils.getContainingClass(initializer); + if (containingClass == null) { + return; + } + for (CantBeStaticCondition addin : JavaExtensionPoints.CANT_BE_STATIC_EP_NAME.getExtensions()) { + if (addin.cantBeStatic(initializer)) { + return; + } + } + final PsiElement scope = containingClass.getScope(); + if (!(scope instanceof PsiJavaFile) && !containingClass.hasModifierProperty(PsiModifier.STATIC)) { + return; + } - final MethodReferenceVisitor visitor = new MethodReferenceVisitor(initializer); - initializer.accept(visitor); - if (!visitor.areReferencesStaticallyAccessible()) { - return; - } + final MethodReferenceVisitor visitor = new MethodReferenceVisitor(initializer); + initializer.accept(visitor); + if (!visitor.areReferencesStaticallyAccessible()) { + return; + } - registerClassInitializerError(initializer); + registerClassInitializerError(initializer); + } } - } } diff --git a/plugin/src/main/java/com/intellij/java/impl/ig/performance/MethodMayBeStaticInspection.java b/plugin/src/main/java/com/intellij/java/impl/ig/performance/MethodMayBeStaticInspection.java index 2bb6fc88bc..3786e9a5e9 100644 --- a/plugin/src/main/java/com/intellij/java/impl/ig/performance/MethodMayBeStaticInspection.java +++ b/plugin/src/main/java/com/intellij/java/impl/ig/performance/MethodMayBeStaticInspection.java @@ -39,187 +39,187 @@ @ExtensionImpl public class MethodMayBeStaticInspection extends BaseInspection { - /** - * @noinspection PublicField - */ - public boolean m_onlyPrivateOrFinal = false; - /** - * @noinspection PublicField - */ - public boolean m_ignoreEmptyMethods = true; + /** + * @noinspection PublicField + */ + public boolean m_onlyPrivateOrFinal = false; + /** + * @noinspection PublicField + */ + public boolean m_ignoreEmptyMethods = true; - @Override - @Nonnull - public String getDisplayName() { - return InspectionGadgetsLocalize.methodMayBeStaticDisplayName().get(); - } - - @Override - @Nonnull - protected String buildErrorString(Object... infos) { - return InspectionGadgetsLocalize.methodMayBeStaticProblemDescriptor().get(); - } - - @Override - protected InspectionGadgetsFix buildFix(Object... infos) { - return new ChangeModifierFix(PsiModifier.STATIC); - } + @Override + @Nonnull + public String getDisplayName() { + return InspectionGadgetsLocalize.methodMayBeStaticDisplayName().get(); + } - @Override - public JComponent createOptionsPanel() { - final MultipleCheckboxOptionsPanel optionsPanel = new MultipleCheckboxOptionsPanel(this); - optionsPanel.addCheckbox(InspectionGadgetsLocalize.methodMayBeStaticOnlyOption().get(), "m_onlyPrivateOrFinal"); - optionsPanel.addCheckbox(InspectionGadgetsLocalize.methodMayBeStaticEmptyOption().get(), "m_ignoreEmptyMethods"); - return optionsPanel; - } + @Override + @Nonnull + protected String buildErrorString(Object... infos) { + return InspectionGadgetsLocalize.methodMayBeStaticProblemDescriptor().get(); + } - @Override - public BaseInspectionVisitor buildVisitor() { - return new MethodCanBeStaticVisitor(); - } + @Override + protected InspectionGadgetsFix buildFix(Object... infos) { + return new ChangeModifierFix(PsiModifier.STATIC); + } - private class MethodCanBeStaticVisitor extends BaseInspectionVisitor { + @Override + public JComponent createOptionsPanel() { + final MultipleCheckboxOptionsPanel optionsPanel = new MultipleCheckboxOptionsPanel(this); + optionsPanel.addCheckbox(InspectionGadgetsLocalize.methodMayBeStaticOnlyOption().get(), "m_onlyPrivateOrFinal"); + optionsPanel.addCheckbox(InspectionGadgetsLocalize.methodMayBeStaticEmptyOption().get(), "m_ignoreEmptyMethods"); + return optionsPanel; + } @Override - public void visitMethod(@Nonnull PsiMethod method) { - super.visitMethod(method); - if (method.hasModifierProperty(PsiModifier.STATIC) || - method.hasModifierProperty(PsiModifier.ABSTRACT) || - method.hasModifierProperty(PsiModifier.SYNCHRONIZED) || - method.hasModifierProperty(PsiModifier.NATIVE)) { - return; - } - if (method.isConstructor() || method.getNameIdentifier() == null) { - return; - } - if (m_ignoreEmptyMethods && MethodUtils.isEmpty(method)) { - return; - } - final PsiClass containingClass = ClassUtils.getContainingClass(method); - if (containingClass == null) { - return; - } - for (CantBeStaticCondition addin : JavaExtensionPoints.CANT_BE_STATIC_EP_NAME.getExtensions()) { - if (addin.cantBeStatic(method)) { - return; - } - } - final PsiElement scope = containingClass.getScope(); - if (!(scope instanceof PsiJavaFile) && !containingClass.hasModifierProperty(PsiModifier.STATIC)) { - return; - } - if (m_onlyPrivateOrFinal && !method.hasModifierProperty(PsiModifier.FINAL) && !method.hasModifierProperty(PsiModifier.PRIVATE)) { - return; - } - if (isExcluded(method) || MethodUtils.hasSuper(method) || MethodUtils.isOverridden(method)) { - return; - } - if (implementsSurprisingInterface(method)) { - return; - } - final MethodReferenceVisitor visitor = new MethodReferenceVisitor(method); - method.accept(visitor); - if (!visitor.areReferencesStaticallyAccessible()) { - return; - } - registerMethodError(method); + public BaseInspectionVisitor buildVisitor() { + return new MethodCanBeStaticVisitor(); } - private boolean implementsSurprisingInterface(final PsiMethod method) { - final PsiClass containingClass = method.getContainingClass(); - if (containingClass == null) { - return false; - } - final Query search = ClassInheritorsSearch.search(containingClass, method.getUseScope(), true, true, false); - final boolean[] result = new boolean[1]; - search.forEach(new Processor() { - int count = 0; + private class MethodCanBeStaticVisitor extends BaseInspectionVisitor { @Override - public boolean process(PsiClass subClass) { - if (++count > 5) { - result[0] = true; - return false; - } - final PsiReferenceList list = subClass.getImplementsList(); - if (list == null) { - return true; - } - final PsiJavaCodeReferenceElement[] referenceElements = list.getReferenceElements(); - for (PsiJavaCodeReferenceElement referenceElement : referenceElements) { - final PsiElement target = referenceElement.resolve(); - if (!(target instanceof PsiClass)) { - result[0] = true; - return false; + public void visitMethod(@Nonnull PsiMethod method) { + super.visitMethod(method); + if (method.hasModifierProperty(PsiModifier.STATIC) || + method.hasModifierProperty(PsiModifier.ABSTRACT) || + method.hasModifierProperty(PsiModifier.SYNCHRONIZED) || + method.hasModifierProperty(PsiModifier.NATIVE)) { + return; + } + if (method.isConstructor() || method.getNameIdentifier() == null) { + return; + } + if (m_ignoreEmptyMethods && MethodUtils.isEmpty(method)) { + return; + } + final PsiClass containingClass = ClassUtils.getContainingClass(method); + if (containingClass == null) { + return; + } + for (CantBeStaticCondition addin : JavaExtensionPoints.CANT_BE_STATIC_EP_NAME.getExtensions()) { + if (addin.cantBeStatic(method)) { + return; + } + } + final PsiElement scope = containingClass.getScope(); + if (!(scope instanceof PsiJavaFile) && !containingClass.hasModifierProperty(PsiModifier.STATIC)) { + return; + } + if (m_onlyPrivateOrFinal && !method.hasModifierProperty(PsiModifier.FINAL) && !method.hasModifierProperty(PsiModifier.PRIVATE)) { + return; } - final PsiClass aClass = (PsiClass) target; - if (!aClass.isInterface()) { - result[0] = true; - return false; + if (isExcluded(method) || MethodUtils.hasSuper(method) || MethodUtils.isOverridden(method)) { + return; } - if (aClass.findMethodBySignature(method, true) != null) { - result[0] = true; - return false; + if (implementsSurprisingInterface(method)) { + return; } - } - return true; + final MethodReferenceVisitor visitor = new MethodReferenceVisitor(method); + method.accept(visitor); + if (!visitor.areReferencesStaticallyAccessible()) { + return; + } + registerMethodError(method); } - }); - return result[0]; - } - private boolean isExcluded(PsiMethod method) { - @NonNls final String name = method.getName(); - if ("writeObject".equals(name)) { - if (!method.hasModifierProperty(PsiModifier.PRIVATE)) { - return false; - } - if (!MethodUtils.hasInThrows(method, "java.io.IOException")) { - return false; - } - final PsiType returnType = method.getReturnType(); - if (!PsiType.VOID.equals(returnType)) { - return false; - } - final PsiParameterList parameterList = method.getParameterList(); - if (parameterList.getParametersCount() != 1) { - return false; - } - final PsiParameter parameter = parameterList.getParameters()[0]; - final PsiType type = parameter.getType(); - return type.equalsToText("java.io.ObjectOutputStream"); - } - if ("readObject".equals(name)) { - if (!method.hasModifierProperty(PsiModifier.PRIVATE)) { - return false; - } - if (!MethodUtils.hasInThrows(method, "java.io.IOException", "java.lang.ClassNotFoundException")) { - return false; - } - final PsiType returnType = method.getReturnType(); - if (!PsiType.VOID.equals(returnType)) { - return false; - } - final PsiParameterList parameterList = method.getParameterList(); - if (parameterList.getParametersCount() != 1) { - return false; - } - final PsiParameter parameter = parameterList.getParameters()[0]; - final PsiType type = parameter.getType(); - return type.equalsToText("java.io.ObjectInputStream"); - } - if ("writeReplace".equals(name) || "readResolve".equals(name)) { - if (!MethodUtils.hasInThrows(method, "java.io.ObjectStreamException")) { - return false; + private boolean implementsSurprisingInterface(final PsiMethod method) { + final PsiClass containingClass = method.getContainingClass(); + if (containingClass == null) { + return false; + } + final Query search = ClassInheritorsSearch.search(containingClass, method.getUseScope(), true, true, false); + final boolean[] result = new boolean[1]; + search.forEach(new Processor() { + int count = 0; + + @Override + public boolean process(PsiClass subClass) { + if (++count > 5) { + result[0] = true; + return false; + } + final PsiReferenceList list = subClass.getImplementsList(); + if (list == null) { + return true; + } + final PsiJavaCodeReferenceElement[] referenceElements = list.getReferenceElements(); + for (PsiJavaCodeReferenceElement referenceElement : referenceElements) { + final PsiElement target = referenceElement.resolve(); + if (!(target instanceof PsiClass)) { + result[0] = true; + return false; + } + final PsiClass aClass = (PsiClass)target; + if (!aClass.isInterface()) { + result[0] = true; + return false; + } + if (aClass.findMethodBySignature(method, true) != null) { + result[0] = true; + return false; + } + } + return true; + } + }); + return result[0]; } - final PsiType returnType = method.getReturnType(); - if (returnType == null || !returnType.equalsToText(JavaClassNames.JAVA_LANG_OBJECT)) { - return false; + + private boolean isExcluded(PsiMethod method) { + @NonNls final String name = method.getName(); + if ("writeObject".equals(name)) { + if (!method.hasModifierProperty(PsiModifier.PRIVATE)) { + return false; + } + if (!MethodUtils.hasInThrows(method, "java.io.IOException")) { + return false; + } + final PsiType returnType = method.getReturnType(); + if (!PsiType.VOID.equals(returnType)) { + return false; + } + final PsiParameterList parameterList = method.getParameterList(); + if (parameterList.getParametersCount() != 1) { + return false; + } + final PsiParameter parameter = parameterList.getParameters()[0]; + final PsiType type = parameter.getType(); + return type.equalsToText("java.io.ObjectOutputStream"); + } + if ("readObject".equals(name)) { + if (!method.hasModifierProperty(PsiModifier.PRIVATE)) { + return false; + } + if (!MethodUtils.hasInThrows(method, "java.io.IOException", "java.lang.ClassNotFoundException")) { + return false; + } + final PsiType returnType = method.getReturnType(); + if (!PsiType.VOID.equals(returnType)) { + return false; + } + final PsiParameterList parameterList = method.getParameterList(); + if (parameterList.getParametersCount() != 1) { + return false; + } + final PsiParameter parameter = parameterList.getParameters()[0]; + final PsiType type = parameter.getType(); + return type.equalsToText("java.io.ObjectInputStream"); + } + if ("writeReplace".equals(name) || "readResolve".equals(name)) { + if (!MethodUtils.hasInThrows(method, "java.io.ObjectStreamException")) { + return false; + } + final PsiType returnType = method.getReturnType(); + if (returnType == null || !returnType.equalsToText(JavaClassNames.JAVA_LANG_OBJECT)) { + return false; + } + final PsiParameterList parameterList = method.getParameterList(); + return parameterList.getParametersCount() == 0; + } + return false; } - final PsiParameterList parameterList = method.getParameterList(); - return parameterList.getParametersCount() == 0; - } - return false; } - } } diff --git a/plugin/src/main/java/com/intellij/java/impl/openapi/roots/JavaProjectModelModifier.java b/plugin/src/main/java/com/intellij/java/impl/openapi/roots/JavaProjectModelModifier.java index f1b0d16a87..ba905b8ba8 100644 --- a/plugin/src/main/java/com/intellij/java/impl/openapi/roots/JavaProjectModelModifier.java +++ b/plugin/src/main/java/com/intellij/java/impl/openapi/roots/JavaProjectModelModifier.java @@ -27,6 +27,7 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.Collection; /** @@ -39,52 +40,56 @@ */ @ExtensionAPI(ComponentScope.PROJECT) public abstract class JavaProjectModelModifier { - public static final ExtensionPointName EP_NAME = ExtensionPointName.create(JavaProjectModelModifier.class); + public static final ExtensionPointName EP_NAME = ExtensionPointName.create(JavaProjectModelModifier.class); - /** - * Implementation of this method should add dependency from module {@code from} to module {@code to} with scope {@code scope} accordingly - * to this dependencies management system. If it takes some time to propagate changes in the external project configuration to IDEA's - * project model the method may schedule this work for asynchronous execution and return {@link AsyncResult} instance which will be fulfilled - * when the work is done. - * - * @return {@link AsyncResult} instance if dependencies between these modules can be handled by this dependencies management system or - * {@code null} otherwise - */ - @Nullable - public abstract AsyncResult addModuleDependency(@Nonnull Module from, @Nonnull Module to, @Nonnull DependencyScope scope); + /** + * Implementation of this method should add dependency from module {@code from} to module {@code to} with scope {@code scope} accordingly + * to this dependencies management system. If it takes some time to propagate changes in the external project configuration to IDEA's + * project model the method may schedule this work for asynchronous execution and return {@link AsyncResult} instance which will be fulfilled + * when the work is done. + * + * @return {@link AsyncResult} instance if dependencies between these modules can be handled by this dependencies management system or + * {@code null} otherwise + */ + @Nullable + public abstract AsyncResult addModuleDependency(@Nonnull Module from, @Nonnull Module to, @Nonnull DependencyScope scope); - /** - * Implementation of this method should add dependency from modules {@code modules} to an external library with scope {@code scope} accordingly - * to this dependencies management system. If it takes some time to propagate changes in the external project configuration to IDEA's - * project model the method may schedule this work for asynchronous execution and return {@link AsyncResult} instance which will be fulfilled - * when the work is done. - * - * @return {@link AsyncResult} instance if dependencies of these modules can be handled by this dependencies management system or - * {@code null} otherwise - */ - @Nullable - public abstract AsyncResult addExternalLibraryDependency(@Nonnull Collection modules, @Nonnull ExternalLibraryDescriptor descriptor, @Nonnull DependencyScope scope); + /** + * Implementation of this method should add dependency from modules {@code modules} to an external library with scope {@code scope} accordingly + * to this dependencies management system. If it takes some time to propagate changes in the external project configuration to IDEA's + * project model the method may schedule this work for asynchronous execution and return {@link AsyncResult} instance which will be fulfilled + * when the work is done. + * + * @return {@link AsyncResult} instance if dependencies of these modules can be handled by this dependencies management system or + * {@code null} otherwise + */ + @Nullable + public abstract AsyncResult addExternalLibraryDependency( + @Nonnull Collection modules, + @Nonnull ExternalLibraryDescriptor descriptor, + @Nonnull DependencyScope scope + ); - /** - * Implementation of this method should add dependency from module {@code from} to {@code library} with scope {@code scope} accordingly - * to this dependencies management system. If it takes some time to propagate changes in the external project configuration to IDEA's - * project model the method may schedule this work for asynchronous execution and return {@link AsyncResult} instance which will be fulfilled - * when the work is done. - * - * @return {@link AsyncResult} instance if dependencies between these modules can be handled by this dependencies management system or - * {@code null} otherwise - */ - @Nullable - public abstract AsyncResult addLibraryDependency(@Nonnull Module from, @Nonnull Library library, @Nonnull DependencyScope scope); + /** + * Implementation of this method should add dependency from module {@code from} to {@code library} with scope {@code scope} accordingly + * to this dependencies management system. If it takes some time to propagate changes in the external project configuration to IDEA's + * project model the method may schedule this work for asynchronous execution and return {@link AsyncResult} instance which will be fulfilled + * when the work is done. + * + * @return {@link AsyncResult} instance if dependencies between these modules can be handled by this dependencies management system or + * {@code null} otherwise + */ + @Nullable + public abstract AsyncResult addLibraryDependency(@Nonnull Module from, @Nonnull Library library, @Nonnull DependencyScope scope); - /** - * Implementation of this method should set language level for module {@code module} to the specified value accordingly - * to this dependencies management system. If it takes some time to propagate changes in the external project configuration to IDEA's - * project model the method may schedule this work for asynchronous execution and return {@link AsyncResult} instance which will be fulfilled - * when the work is done. - * - * @return {@link AsyncResult} instance if language level can be set by this dependencies management system or {@code null} otherwise - */ - @Nullable - public abstract AsyncResult changeLanguageLevel(@Nonnull Module module, @Nonnull LanguageLevel level); + /** + * Implementation of this method should set language level for module {@code module} to the specified value accordingly + * to this dependencies management system. If it takes some time to propagate changes in the external project configuration to IDEA's + * project model the method may schedule this work for asynchronous execution and return {@link AsyncResult} instance which will be fulfilled + * when the work is done. + * + * @return {@link AsyncResult} instance if language level can be set by this dependencies management system or {@code null} otherwise + */ + @Nullable + public abstract AsyncResult changeLanguageLevel(@Nonnull Module module, @Nonnull LanguageLevel level); } diff --git a/plugin/src/main/java/com/intellij/java/impl/openapi/roots/impl/JavaProjectModelModificationServiceImpl.java b/plugin/src/main/java/com/intellij/java/impl/openapi/roots/impl/JavaProjectModelModificationServiceImpl.java index d216ad543a..a5b40579ba 100644 --- a/plugin/src/main/java/com/intellij/java/impl/openapi/roots/impl/JavaProjectModelModificationServiceImpl.java +++ b/plugin/src/main/java/com/intellij/java/impl/openapi/roots/impl/JavaProjectModelModificationServiceImpl.java @@ -29,6 +29,7 @@ import jakarta.inject.Singleton; import jakarta.annotation.Nonnull; + import java.util.Collection; /** @@ -37,61 +38,63 @@ @Singleton @ServiceImpl public class JavaProjectModelModificationServiceImpl extends JavaProjectModelModificationService { - private final Project myProject; + private final Project myProject; - @Inject - public JavaProjectModelModificationServiceImpl(Project project) { - myProject = project; - } + @Inject + public JavaProjectModelModificationServiceImpl(Project project) { + myProject = project; + } - @Override - public AsyncResult addDependency(@Nonnull Module from, @Nonnull Module to, @Nonnull DependencyScope scope) { - for (JavaProjectModelModifier modifier : getModelModifiers()) { - AsyncResult asyncResult = modifier.addModuleDependency(from, to, scope); - if (asyncResult != null) { - return asyncResult; - } + @Override + public AsyncResult addDependency(@Nonnull Module from, @Nonnull Module to, @Nonnull DependencyScope scope) { + for (JavaProjectModelModifier modifier : getModelModifiers()) { + AsyncResult asyncResult = modifier.addModuleDependency(from, to, scope); + if (asyncResult != null) { + return asyncResult; + } + } + return AsyncResult.rejected(); } - return AsyncResult.rejected(); - } - @Override - public AsyncResult addDependency(@Nonnull Collection from, - @Nonnull ExternalLibraryDescriptor libraryDescriptor, - @Nonnull DependencyScope scope) { - for (JavaProjectModelModifier modifier : getModelModifiers()) { - AsyncResult asyncResult = modifier.addExternalLibraryDependency(from, libraryDescriptor, scope); - if (asyncResult != null) { - return asyncResult; - } + @Override + public AsyncResult addDependency( + @Nonnull Collection from, + @Nonnull ExternalLibraryDescriptor libraryDescriptor, + @Nonnull DependencyScope scope + ) { + for (JavaProjectModelModifier modifier : getModelModifiers()) { + AsyncResult asyncResult = modifier.addExternalLibraryDependency(from, libraryDescriptor, scope); + if (asyncResult != null) { + return asyncResult; + } + } + return AsyncResult.rejected(); } - return AsyncResult.rejected(); - } - @Override - public AsyncResult addDependency(@Nonnull Module from, @Nonnull Library library, @Nonnull DependencyScope scope) { - for (JavaProjectModelModifier modifier : getModelModifiers()) { - AsyncResult asyncResult = modifier.addLibraryDependency(from, library, scope); - if (asyncResult != null) { - return asyncResult; - } + @Override + public AsyncResult addDependency(@Nonnull Module from, @Nonnull Library library, @Nonnull DependencyScope scope) { + for (JavaProjectModelModifier modifier : getModelModifiers()) { + AsyncResult asyncResult = modifier.addLibraryDependency(from, library, scope); + if (asyncResult != null) { + return asyncResult; + } + } + return AsyncResult.rejected(); } - return AsyncResult.rejected(); - } - @Override - public AsyncResult changeLanguageLevel(@Nonnull Module module, @Nonnull LanguageLevel languageLevel) { - for (JavaProjectModelModifier modifier : getModelModifiers()) { - AsyncResult asyncResult = modifier.changeLanguageLevel(module, languageLevel); - if (asyncResult != null) { - return asyncResult; - } + @Override + public AsyncResult changeLanguageLevel(@Nonnull Module module, @Nonnull LanguageLevel languageLevel) { + for (JavaProjectModelModifier modifier : getModelModifiers()) { + AsyncResult asyncResult = modifier.changeLanguageLevel(module, languageLevel); + if (asyncResult != null) { + return asyncResult; + } + } + return AsyncResult.rejected(); } - return AsyncResult.rejected(); - } - @Nonnull - private JavaProjectModelModifier[] getModelModifiers() { - return JavaProjectModelModifier.EP_NAME.getExtensions(myProject); - } + @Nonnull + private JavaProjectModelModifier[] getModelModifiers() { + return JavaProjectModelModifier.EP_NAME.getExtensions(myProject); + } } diff --git a/plugin/src/main/java/com/intellij/java/impl/psi/NonClasspathResolveScopeEnlarger.java b/plugin/src/main/java/com/intellij/java/impl/psi/NonClasspathResolveScopeEnlarger.java index c06cc1c743..2582c169d3 100644 --- a/plugin/src/main/java/com/intellij/java/impl/psi/NonClasspathResolveScopeEnlarger.java +++ b/plugin/src/main/java/com/intellij/java/impl/psi/NonClasspathResolveScopeEnlarger.java @@ -22,26 +22,26 @@ */ @ExtensionImpl public class NonClasspathResolveScopeEnlarger extends ResolveScopeEnlarger { - @Override - public SearchScope getAdditionalResolveScope(@Nonnull VirtualFile file, Project project) { - ProjectFileIndex index = ProjectFileIndex.getInstance(project); - if (index.isInLibraryClasses(file) || index.isInContent(file)) { - return null; - } + @Override + public SearchScope getAdditionalResolveScope(@Nonnull VirtualFile file, Project project) { + ProjectFileIndex index = ProjectFileIndex.getInstance(project); + if (index.isInLibraryClasses(file) || index.isInContent(file)) { + return null; + } - FileType fileType = file.getFileType(); - if (fileType == JavaFileType.INSTANCE || fileType == JavaClassFileType.INSTANCE) { - for (PsiElementFinder finder : PsiElementFinder.EP_NAME.getExtensionList(project)) { - if (finder instanceof NonClasspathClassFinder) { - final List roots = ((NonClasspathClassFinder) finder).getClassRoots(); - for (VirtualFile root : roots) { - if (VfsUtilCore.isAncestor(root, file, true)) { - return NonClasspathDirectoriesScope.compose(roots); + FileType fileType = file.getFileType(); + if (fileType == JavaFileType.INSTANCE || fileType == JavaClassFileType.INSTANCE) { + for (PsiElementFinder finder : PsiElementFinder.EP_NAME.getExtensionList(project)) { + if (finder instanceof NonClasspathClassFinder) { + final List roots = ((NonClasspathClassFinder)finder).getClassRoots(); + for (VirtualFile root : roots) { + if (VfsUtilCore.isAncestor(root, file, true)) { + return NonClasspathDirectoriesScope.compose(roots); + } + } + } } - } } - } + return null; } - return null; - } } diff --git a/plugin/src/main/java/com/intellij/java/impl/refactoring/introduceParameter/IntroduceParameterMethodUsagesProcessor.java b/plugin/src/main/java/com/intellij/java/impl/refactoring/introduceParameter/IntroduceParameterMethodUsagesProcessor.java index e808267ff5..38628aa716 100644 --- a/plugin/src/main/java/com/intellij/java/impl/refactoring/introduceParameter/IntroduceParameterMethodUsagesProcessor.java +++ b/plugin/src/main/java/com/intellij/java/impl/refactoring/introduceParameter/IntroduceParameterMethodUsagesProcessor.java @@ -25,22 +25,23 @@ /** * @author Maxim.Medvedev - * Date: Apr 17, 2009 5:16:10 PM + * Date: Apr 17, 2009 5:16:10 PM */ @ExtensionAPI(ComponentScope.APPLICATION) public interface IntroduceParameterMethodUsagesProcessor { - ExtensionPointName EP_NAME = - ExtensionPointName.create(IntroduceParameterMethodUsagesProcessor.class); + ExtensionPointName EP_NAME = + ExtensionPointName.create(IntroduceParameterMethodUsagesProcessor.class); - boolean isMethodUsage(UsageInfo usage); + boolean isMethodUsage(UsageInfo usage); - void findConflicts(IntroduceParameterData data, UsageInfo[] usages, MultiMap conflicts); + void findConflicts(IntroduceParameterData data, UsageInfo[] usages, MultiMap conflicts); - boolean processChangeMethodUsage(IntroduceParameterData data, UsageInfo usage, UsageInfo[] usages) throws IncorrectOperationException; + boolean processChangeMethodUsage(IntroduceParameterData data, UsageInfo usage, UsageInfo[] usages) throws IncorrectOperationException; - boolean processChangeMethodSignature(IntroduceParameterData data, UsageInfo usage, UsageInfo[] usages) throws IncorrectOperationException; + boolean processChangeMethodSignature(IntroduceParameterData data, UsageInfo usage, UsageInfo[] usages) + throws IncorrectOperationException; - boolean processAddDefaultConstructor(IntroduceParameterData data, UsageInfo usage, UsageInfo[] usages); + boolean processAddDefaultConstructor(IntroduceParameterData data, UsageInfo usage, UsageInfo[] usages); - boolean processAddSuperCall(IntroduceParameterData data, UsageInfo usage, UsageInfo[] usages) throws IncorrectOperationException; + boolean processAddSuperCall(IntroduceParameterData data, UsageInfo usage, UsageInfo[] usages) throws IncorrectOperationException; } diff --git a/plugin/src/main/java/com/intellij/java/impl/refactoring/introduceParameter/IntroduceParameterProcessor.java b/plugin/src/main/java/com/intellij/java/impl/refactoring/introduceParameter/IntroduceParameterProcessor.java index 57dbdf2af5..3019911084 100644 --- a/plugin/src/main/java/com/intellij/java/impl/refactoring/introduceParameter/IntroduceParameterProcessor.java +++ b/plugin/src/main/java/com/intellij/java/impl/refactoring/introduceParameter/IntroduceParameterProcessor.java @@ -68,682 +68,562 @@ import java.util.HashSet; import java.util.Set; -public class IntroduceParameterProcessor extends BaseRefactoringProcessor implements IntroduceParameterData -{ - private static final Logger LOG = Logger.getInstance(IntroduceParameterProcessor.class); - - private final PsiMethod myMethodToReplaceIn; - private final PsiMethod myMethodToSearchFor; - private PsiExpression myParameterInitializer; - private final PsiExpression myExpressionToSearch; - private final PsiLocalVariable myLocalVariable; - private final boolean myRemoveLocalVariable; - private final String myParameterName; - private final boolean myReplaceAllOccurences; - - private int myReplaceFieldsWithGetters; - private final boolean myDeclareFinal; - private final boolean myGenerateDelegate; - private PsiType myForcedType; - private final IntList myParametersToRemove; - private final PsiManager myManager; - private JavaExpressionWrapper myInitializerWrapper; - private boolean myHasConflicts; - - /** - * if expressionToSearch is null, search for localVariable - */ - public IntroduceParameterProcessor(@Nonnull Project project, - PsiMethod methodToReplaceIn, - @Nonnull PsiMethod methodToSearchFor, - PsiExpression parameterInitializer, - PsiExpression expressionToSearch, - PsiLocalVariable localVariable, - boolean removeLocalVariable, - String parameterName, - boolean replaceAllOccurences, - int replaceFieldsWithGetters, - boolean declareFinal, - boolean generateDelegate, - PsiType forcedType, - @Nonnull IntList parametersToRemove) - { - super(project); - - myMethodToReplaceIn = methodToReplaceIn; - myMethodToSearchFor = methodToSearchFor; - myParameterInitializer = parameterInitializer; - myExpressionToSearch = expressionToSearch; - - myLocalVariable = localVariable; - myRemoveLocalVariable = removeLocalVariable; - myParameterName = parameterName; - myReplaceAllOccurences = replaceAllOccurences; - myReplaceFieldsWithGetters = replaceFieldsWithGetters; - myDeclareFinal = declareFinal; - myGenerateDelegate = generateDelegate; - myForcedType = forcedType; - myManager = PsiManager.getInstance(project); - - myParametersToRemove = parametersToRemove; - - myInitializerWrapper = expressionToSearch == null ? null : new JavaExpressionWrapper(expressionToSearch); - } - - @Nonnull - protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) - { - return new IntroduceParameterViewDescriptor(myMethodToSearchFor); - } - - @Nonnull - public PsiType getForcedType() - { - return myForcedType; - } - - public void setForcedType(PsiType forcedType) - { - myForcedType = forcedType; - } - - public int getReplaceFieldsWithGetters() - { - return myReplaceFieldsWithGetters; - } - - public void setReplaceFieldsWithGetters(int replaceFieldsWithGetters) - { - myReplaceFieldsWithGetters = replaceFieldsWithGetters; - } - - @Nonnull - protected UsageInfo[] findUsages() - { - ArrayList result = new ArrayList(); - - PsiMethod[] overridingMethods = - OverridingMethodsSearch.search(myMethodToSearchFor, true).toArray(PsiMethod.EMPTY_ARRAY); - for(PsiMethod overridingMethod : overridingMethods) - { - result.add(new UsageInfo(overridingMethod)); - } - if(!myGenerateDelegate) - { - PsiReference[] refs = - MethodReferencesSearch.search(myMethodToSearchFor, GlobalSearchScope.projectScope(myProject), true).toArray(PsiReference.EMPTY_ARRAY); - - - for(PsiReference ref1 : refs) - { - PsiElement ref = ref1.getElement(); - if(ref instanceof PsiMethod && ((PsiMethod) ref).isConstructor()) - { - DefaultConstructorImplicitUsageInfo implicitUsageInfo = - new DefaultConstructorImplicitUsageInfo((PsiMethod) ref, ((PsiMethod) ref).getContainingClass(), myMethodToSearchFor); - result.add(implicitUsageInfo); - } - else if(ref instanceof PsiClass) - { - result.add(new NoConstructorClassUsageInfo((PsiClass) ref)); - } - else if(!IntroduceParameterUtil.insideMethodToBeReplaced(ref, myMethodToReplaceIn)) - { - result.add(new ExternalUsageInfo(ref)); - } - else - { - result.add(new ChangedMethodCallInfo(ref)); - } - } - } - - if(myReplaceAllOccurences) - { - for(PsiElement expr : getOccurrences()) - { - result.add(new InternalUsageInfo(expr)); - } - } - else - { - if(myExpressionToSearch != null && myExpressionToSearch.isValid()) - { - result.add(new InternalUsageInfo(myExpressionToSearch)); - } - } - - final UsageInfo[] usageInfos = result.toArray(new UsageInfo[result.size()]); - return UsageViewUtil.removeDuplicatedUsages(usageInfos); - } - - protected PsiElement[] getOccurrences() - { - final OccurrenceManager occurrenceManager; - if(myLocalVariable == null) - { - occurrenceManager = new ExpressionOccurrenceManager(myExpressionToSearch, myMethodToReplaceIn, null); - } - else - { - occurrenceManager = new LocalVariableOccurrenceManager(myLocalVariable, null); - } - return occurrenceManager.getOccurrences(); - } - - public boolean hasConflicts() - { - return myHasConflicts; - } - - private static class ReferencedElementsCollector extends JavaRecursiveElementWalkingVisitor - { - private final Set myResult = new HashSet(); - - @Override - public void visitReferenceExpression(PsiReferenceExpression expression) - { - visitReferenceElement(expression); - } - - @Override - public void visitReferenceElement(PsiJavaCodeReferenceElement reference) - { - super.visitReferenceElement(reference); - final PsiElement element = reference.resolve(); - if(element != null) - { - myResult.add(element); - } - } - } - - protected boolean preprocessUsages(Ref refUsages) - { - UsageInfo[] usagesIn = refUsages.get(); - MultiMap conflicts = new MultiMap(); - - AnySameNameVariables anySameNameVariables = new AnySameNameVariables(); - myMethodToReplaceIn.accept(anySameNameVariables); - final Pair conflictPair = anySameNameVariables.getConflict(); - if(conflictPair != null) - { - conflicts.putValue(conflictPair.first, conflictPair.second); - } - - if(!myGenerateDelegate) - { - detectAccessibilityConflicts(usagesIn, conflicts); - } - - if(myParameterInitializer != null && !myMethodToReplaceIn.hasModifierProperty(PsiModifier.PRIVATE)) - { - final AnySupers anySupers = new AnySupers(); - myParameterInitializer.accept(anySupers); - if(anySupers.isResult()) - { - for(UsageInfo usageInfo : usagesIn) - { - if(!(usageInfo.getElement() instanceof PsiMethod) && !(usageInfo instanceof InternalUsageInfo)) - { - if(!PsiTreeUtil.isAncestor(myMethodToReplaceIn.getContainingClass(), usageInfo.getElement(), false)) - { - conflicts.putValue( - myParameterInitializer, - RefactoringLocalize.parameterInitializerContains0ButNotAllCallsToMethodAreInItsClass( - CommonRefactoringUtil.htmlEmphasize(PsiKeyword.SUPER) - ).get() - ); - break; - } - } - } - } - } - - for(IntroduceParameterMethodUsagesProcessor processor : IntroduceParameterMethodUsagesProcessor.EP_NAME.getExtensions()) - { - processor.findConflicts(this, refUsages.get(), conflicts); - } - - myHasConflicts = !conflicts.isEmpty(); - return showConflicts(conflicts, usagesIn); - } - - private void detectAccessibilityConflicts(final UsageInfo[] usageArray, MultiMap conflicts) - { - if(myParameterInitializer != null) - { - final ReferencedElementsCollector collector = new ReferencedElementsCollector(); - myParameterInitializer.accept(collector); - final Set result = collector.myResult; - if(!result.isEmpty()) - { - for(final UsageInfo usageInfo : usageArray) - { - if(usageInfo instanceof ExternalUsageInfo && IntroduceParameterUtil.isMethodUsage(usageInfo)) - { - final PsiElement place = usageInfo.getElement(); - for(PsiElement element : result) - { - if(element instanceof PsiField && myReplaceFieldsWithGetters != IntroduceParameterRefactoring.REPLACE_FIELDS_WITH_GETTERS_NONE) - { //check getter access instead - final PsiClass psiClass = ((PsiField) element).getContainingClass(); - LOG.assertTrue(psiClass != null); - final PsiMethod method = psiClass.findMethodBySignature(PropertyUtil.generateGetterPrototype((PsiField) element), true); - if(method != null) - { - element = method; - } - } - if(element instanceof PsiMember && - !JavaPsiFacade.getInstance(myProject).getResolveHelper().isAccessible((PsiMember) element, place, null)) - { - LocalizeValue message = RefactoringLocalize.zeroIsNotAccessibleFrom1ValueForIntroducedParameterInThatMethodCallWillBeIncorrect( - RefactoringUIUtil.getDescription(element, true), - RefactoringUIUtil.getDescription(ConflictsUtil.getContainer(place), true) - ); - conflicts.putValue(element, message.get()); - } - } - } - } - } - } - } - - public static class AnySupers extends JavaRecursiveElementWalkingVisitor - { - private boolean myResult = false; - - @Override - public void visitSuperExpression(PsiSuperExpression expression) - { - myResult = true; - } - - public boolean isResult() - { - return myResult; - } - - @Override - public void visitReferenceExpression(PsiReferenceExpression expression) - { - visitElement(expression); - } - } - - public class AnySameNameVariables extends JavaRecursiveElementWalkingVisitor - { - private Pair conflict = null; - - public Pair getConflict() - { - return conflict; - } - - @Override - public void visitVariable(PsiVariable variable) - { - if(variable == myLocalVariable) - { - return; - } - if(myParameterName.equals(variable.getName())) - { - LocalizeValue descr = RefactoringLocalize.thereIsAlreadyA0ItWillConflictWithAnIntroducedParameter( - RefactoringUIUtil.getDescription(variable, true) - ); - - conflict = Pair.create(variable, CommonRefactoringUtil.capitalize(descr.get())); - } - } - - @Override - public void visitReferenceExpression(PsiReferenceExpression expression) - { - } - - @Override - public void visitElement(PsiElement element) - { - if(conflict != null) - { - return; - } - super.visitElement(element); - } - } - - protected void performRefactoring(UsageInfo[] usages) - { - try - { - PsiElementFactory factory = JavaPsiFacade.getInstance(myManager.getProject()).getElementFactory(); - PsiType initializerType = getInitializerType(myForcedType, myParameterInitializer, myLocalVariable); - setForcedType(initializerType); - - // Converting myParameterInitializer - if(myParameterInitializer == null) - { - LOG.assertTrue(myLocalVariable != null); - myParameterInitializer = factory.createExpressionFromText(myLocalVariable.getName(), myLocalVariable); - } - else if(myParameterInitializer instanceof PsiArrayInitializerExpression) - { - final PsiExpression newExprArrayInitializer = - RefactoringUtil.createNewExpressionFromArrayInitializer((PsiArrayInitializerExpression) myParameterInitializer, initializerType); - myParameterInitializer = (PsiExpression) myParameterInitializer.replace(newExprArrayInitializer); - } - - myInitializerWrapper = new JavaExpressionWrapper(myParameterInitializer); - - // Changing external occurences (the tricky part) - - IntroduceParameterUtil.processUsages(usages, this); - - if(myGenerateDelegate) - { - generateDelegate(myMethodToReplaceIn); - if(myMethodToReplaceIn != myMethodToSearchFor) - { - final PsiMethod method = generateDelegate(myMethodToSearchFor); - if(method.getContainingClass().isInterface()) - { - final PsiCodeBlock block = method.getBody(); - if(block != null) - { - block.delete(); - } - } - } - } - - // Changing signature of initial method - // (signature of myMethodToReplaceIn will be either changed now or have already been changed) - LOG.assertTrue(initializerType.isValid()); - final FieldConflictsResolver fieldConflictsResolver = new FieldConflictsResolver(myParameterName, myMethodToReplaceIn.getBody()); - IntroduceParameterUtil.changeMethodSignatureAndResolveFieldConflicts(new UsageInfo(myMethodToReplaceIn), usages, this); - if(myMethodToSearchFor != myMethodToReplaceIn) - { - IntroduceParameterUtil.changeMethodSignatureAndResolveFieldConflicts(new UsageInfo(myMethodToSearchFor), usages, this); - } - ChangeContextUtil.clearContextInfo(myParameterInitializer); - - // Replacing expression occurrences - for(UsageInfo usage : usages) - { - if(usage instanceof ChangedMethodCallInfo) - { - PsiElement element = usage.getElement(); - - processChangedMethodCall(element); - } - else if(usage instanceof InternalUsageInfo) - { - PsiElement element = usage.getElement(); - if(element instanceof PsiExpression) - { - element = RefactoringUtil.outermostParenthesizedExpression((PsiExpression) element); - } - if(element != null) - { - if(element.getParent() instanceof PsiExpressionStatement) - { - element.getParent().delete(); - } - else - { - PsiExpression newExpr = factory.createExpressionFromText(myParameterName, element); - IntroduceVariableBase.replace((PsiExpression) element, newExpr, myProject); - } - } - } - } - - if(myLocalVariable != null && myRemoveLocalVariable) - { - myLocalVariable.normalizeDeclaration(); - myLocalVariable.getParent().delete(); - } - fieldConflictsResolver.fix(); - } - catch(IncorrectOperationException ex) - { - LOG.error(ex); - } - } - - private PsiMethod generateDelegate(final PsiMethod methodToReplaceIn) throws IncorrectOperationException - { - final PsiMethod delegate = (PsiMethod) methodToReplaceIn.copy(); - final PsiElementFactory elementFactory = JavaPsiFacade.getInstance(myManager.getProject()).getElementFactory(); - ChangeSignatureProcessor.makeEmptyBody(elementFactory, delegate); - final PsiCallExpression callExpression = ChangeSignatureProcessor.addDelegatingCallTemplate(delegate, delegate.getName()); - final PsiExpressionList argumentList = callExpression.getArgumentList(); - assert argumentList != null; - final PsiParameter[] psiParameters = methodToReplaceIn.getParameterList().getParameters(); - - final PsiParameter anchorParameter = getAnchorParameter(methodToReplaceIn); - if(psiParameters.length == 0) - { - argumentList.add(myParameterInitializer); - } - else - { - if(anchorParameter == null) - { - argumentList.add(myParameterInitializer); - } - for(int i = 0; i < psiParameters.length; i++) - { - PsiParameter psiParameter = psiParameters[i]; - if(!myParametersToRemove.contains(i)) - { - final PsiExpression expression = elementFactory.createExpressionFromText(psiParameter.getName(), delegate); - argumentList.add(expression); - } - if(psiParameter == anchorParameter) - { - argumentList.add(myParameterInitializer); - } - } - } - - return (PsiMethod) methodToReplaceIn.getContainingClass().addBefore(delegate, methodToReplaceIn); - } - - static PsiType getInitializerType(PsiType forcedType, PsiExpression parameterInitializer, PsiLocalVariable localVariable) - { - final PsiType initializerType; - if(forcedType == null) - { - if(parameterInitializer == null) - { - if(localVariable != null) - { - initializerType = localVariable.getType(); - } - else - { - LOG.assertTrue(false); - initializerType = null; - } - } - else - { - if(localVariable == null) - { - initializerType = RefactoringUtil.getTypeByExpressionWithExpectedType(parameterInitializer); - } - else - { - initializerType = localVariable.getType(); - } - } - } - else - { - initializerType = forcedType; - } - return initializerType; - } - - private void processChangedMethodCall(PsiElement element) throws IncorrectOperationException - { - if(element.getParent() instanceof PsiMethodCallExpression) - { - PsiMethodCallExpression methodCall = (PsiMethodCallExpression) element.getParent(); - - if(myMethodToReplaceIn == myMethodToSearchFor && PsiTreeUtil.isAncestor(methodCall, myParameterInitializer, false)) - { - return; - } - - PsiElementFactory factory = JavaPsiFacade.getInstance(methodCall.getProject()).getElementFactory(); - PsiExpression expression = factory.createExpressionFromText(myParameterName, null); - final PsiExpressionList argList = methodCall.getArgumentList(); - final PsiExpression[] exprs = argList.getExpressions(); - - boolean first = false; - PsiElement anchor = null; - if(myMethodToSearchFor.isVarArgs()) - { - final int oldParamCount = myMethodToSearchFor.getParameterList().getParametersCount() - 1; - if(exprs.length >= oldParamCount) - { - if(oldParamCount > 1) - { - anchor = exprs[oldParamCount - 2]; - } - else - { - first = true; - anchor = null; - } - } - else - { - anchor = exprs[exprs.length - 1]; - } - } - else if(exprs.length > 0) - { - anchor = exprs[exprs.length - 1]; - } - - if(anchor != null) - { - argList.addAfter(expression, anchor); - } - else - { - if(first && exprs.length > 0) - { - argList.addBefore(expression, exprs[0]); - } - else - { - argList.add(expression); - } - } - - removeParametersFromCall(argList); - } - else - { - LOG.error(element.getParent()); - } - } - - private void removeParametersFromCall(final PsiExpressionList argList) - { - final PsiExpression[] exprs = argList.getExpressions(); - for(int i = myParametersToRemove.size() - 1; i >= 0; i--) - { - int paramNum = myParametersToRemove.get(i); - if(paramNum < exprs.length) - { - try - { - exprs[paramNum].delete(); - } - catch(IncorrectOperationException e) - { - LOG.error(e); - } - } - } - } - - protected String getCommandName() - { - return RefactoringLocalize.introduceParameterCommand(DescriptiveNameUtil.getDescriptiveName(myMethodToReplaceIn)).get(); - } - - @Nullable - private static PsiParameter getAnchorParameter(PsiMethod methodToReplaceIn) - { - PsiParameterList parameterList = methodToReplaceIn.getParameterList(); - final PsiParameter anchorParameter; - final PsiParameter[] parameters = parameterList.getParameters(); - final int length = parameters.length; - if(!methodToReplaceIn.isVarArgs()) - { - anchorParameter = length > 0 ? parameters[length - 1] : null; - } - else - { - LOG.assertTrue(length > 0); - LOG.assertTrue(parameters[length - 1].isVarArgs()); - anchorParameter = length > 1 ? parameters[length - 2] : null; - } - return anchorParameter; - } - - public PsiMethod getMethodToReplaceIn() - { - return myMethodToReplaceIn; - } - - @Nonnull - public PsiMethod getMethodToSearchFor() - { - return myMethodToSearchFor; - } - - public JavaExpressionWrapper getParameterInitializer() - { - return myInitializerWrapper; - } - - @Nonnull - public String getParameterName() - { - return myParameterName; - } - - public boolean isDeclareFinal() - { - return myDeclareFinal; - } - - public boolean isGenerateDelegate() - { - return myGenerateDelegate; - } - - @Nonnull - public IntList getParametersToRemove() - { - return myParametersToRemove; - } - - @Nonnull - public Project getProject() - { - return myProject; - } - +public class IntroduceParameterProcessor extends BaseRefactoringProcessor implements IntroduceParameterData { + private static final Logger LOG = Logger.getInstance(IntroduceParameterProcessor.class); + + private final PsiMethod myMethodToReplaceIn; + private final PsiMethod myMethodToSearchFor; + private PsiExpression myParameterInitializer; + private final PsiExpression myExpressionToSearch; + private final PsiLocalVariable myLocalVariable; + private final boolean myRemoveLocalVariable; + private final String myParameterName; + private final boolean myReplaceAllOccurences; + + private int myReplaceFieldsWithGetters; + private final boolean myDeclareFinal; + private final boolean myGenerateDelegate; + private PsiType myForcedType; + private final IntList myParametersToRemove; + private final PsiManager myManager; + private JavaExpressionWrapper myInitializerWrapper; + private boolean myHasConflicts; + + /** + * if expressionToSearch is null, search for localVariable + */ + public IntroduceParameterProcessor( + @Nonnull Project project, + PsiMethod methodToReplaceIn, + @Nonnull PsiMethod methodToSearchFor, + PsiExpression parameterInitializer, + PsiExpression expressionToSearch, + PsiLocalVariable localVariable, + boolean removeLocalVariable, + String parameterName, + boolean replaceAllOccurences, + int replaceFieldsWithGetters, + boolean declareFinal, + boolean generateDelegate, + PsiType forcedType, + @Nonnull IntList parametersToRemove + ) { + super(project); + + myMethodToReplaceIn = methodToReplaceIn; + myMethodToSearchFor = methodToSearchFor; + myParameterInitializer = parameterInitializer; + myExpressionToSearch = expressionToSearch; + + myLocalVariable = localVariable; + myRemoveLocalVariable = removeLocalVariable; + myParameterName = parameterName; + myReplaceAllOccurences = replaceAllOccurences; + myReplaceFieldsWithGetters = replaceFieldsWithGetters; + myDeclareFinal = declareFinal; + myGenerateDelegate = generateDelegate; + myForcedType = forcedType; + myManager = PsiManager.getInstance(project); + + myParametersToRemove = parametersToRemove; + + myInitializerWrapper = expressionToSearch == null ? null : new JavaExpressionWrapper(expressionToSearch); + } + + @Nonnull + protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) { + return new IntroduceParameterViewDescriptor(myMethodToSearchFor); + } + + @Nonnull + public PsiType getForcedType() { + return myForcedType; + } + + public void setForcedType(PsiType forcedType) { + myForcedType = forcedType; + } + + public int getReplaceFieldsWithGetters() { + return myReplaceFieldsWithGetters; + } + + public void setReplaceFieldsWithGetters(int replaceFieldsWithGetters) { + myReplaceFieldsWithGetters = replaceFieldsWithGetters; + } + + @Nonnull + protected UsageInfo[] findUsages() { + ArrayList result = new ArrayList(); + + PsiMethod[] overridingMethods = + OverridingMethodsSearch.search(myMethodToSearchFor, true).toArray(PsiMethod.EMPTY_ARRAY); + for (PsiMethod overridingMethod : overridingMethods) { + result.add(new UsageInfo(overridingMethod)); + } + if (!myGenerateDelegate) { + PsiReference[] refs = MethodReferencesSearch.search(myMethodToSearchFor, GlobalSearchScope.projectScope(myProject), true) + .toArray(PsiReference.EMPTY_ARRAY); + + + for (PsiReference ref1 : refs) { + PsiElement ref = ref1.getElement(); + if (ref instanceof PsiMethod && ((PsiMethod)ref).isConstructor()) { + DefaultConstructorImplicitUsageInfo implicitUsageInfo = + new DefaultConstructorImplicitUsageInfo((PsiMethod)ref, ((PsiMethod)ref).getContainingClass(), myMethodToSearchFor); + result.add(implicitUsageInfo); + } + else if (ref instanceof PsiClass) { + result.add(new NoConstructorClassUsageInfo((PsiClass)ref)); + } + else if (!IntroduceParameterUtil.insideMethodToBeReplaced(ref, myMethodToReplaceIn)) { + result.add(new ExternalUsageInfo(ref)); + } + else { + result.add(new ChangedMethodCallInfo(ref)); + } + } + } + + if (myReplaceAllOccurences) { + for (PsiElement expr : getOccurrences()) { + result.add(new InternalUsageInfo(expr)); + } + } + else { + if (myExpressionToSearch != null && myExpressionToSearch.isValid()) { + result.add(new InternalUsageInfo(myExpressionToSearch)); + } + } + + final UsageInfo[] usageInfos = result.toArray(new UsageInfo[result.size()]); + return UsageViewUtil.removeDuplicatedUsages(usageInfos); + } + + protected PsiElement[] getOccurrences() { + final OccurrenceManager occurrenceManager; + if (myLocalVariable == null) { + occurrenceManager = new ExpressionOccurrenceManager(myExpressionToSearch, myMethodToReplaceIn, null); + } + else { + occurrenceManager = new LocalVariableOccurrenceManager(myLocalVariable, null); + } + return occurrenceManager.getOccurrences(); + } + + public boolean hasConflicts() { + return myHasConflicts; + } + + private static class ReferencedElementsCollector extends JavaRecursiveElementWalkingVisitor { + private final Set myResult = new HashSet(); + + @Override + public void visitReferenceExpression(PsiReferenceExpression expression) { + visitReferenceElement(expression); + } + + @Override + public void visitReferenceElement(PsiJavaCodeReferenceElement reference) { + super.visitReferenceElement(reference); + final PsiElement element = reference.resolve(); + if (element != null) { + myResult.add(element); + } + } + } + + protected boolean preprocessUsages(Ref refUsages) { + UsageInfo[] usagesIn = refUsages.get(); + MultiMap conflicts = new MultiMap(); + + AnySameNameVariables anySameNameVariables = new AnySameNameVariables(); + myMethodToReplaceIn.accept(anySameNameVariables); + final Pair conflictPair = anySameNameVariables.getConflict(); + if (conflictPair != null) { + conflicts.putValue(conflictPair.first, conflictPair.second); + } + + if (!myGenerateDelegate) { + detectAccessibilityConflicts(usagesIn, conflicts); + } + + if (myParameterInitializer != null && !myMethodToReplaceIn.hasModifierProperty(PsiModifier.PRIVATE)) { + final AnySupers anySupers = new AnySupers(); + myParameterInitializer.accept(anySupers); + if (anySupers.isResult()) { + for (UsageInfo usageInfo : usagesIn) { + if (!(usageInfo.getElement() instanceof PsiMethod) && !(usageInfo instanceof InternalUsageInfo)) { + if (!PsiTreeUtil.isAncestor(myMethodToReplaceIn.getContainingClass(), usageInfo.getElement(), false)) { + conflicts.putValue( + myParameterInitializer, + RefactoringLocalize.parameterInitializerContains0ButNotAllCallsToMethodAreInItsClass( + CommonRefactoringUtil.htmlEmphasize(PsiKeyword.SUPER) + ).get() + ); + break; + } + } + } + } + } + + for (IntroduceParameterMethodUsagesProcessor processor : IntroduceParameterMethodUsagesProcessor.EP_NAME.getExtensions()) { + processor.findConflicts(this, refUsages.get(), conflicts); + } + + myHasConflicts = !conflicts.isEmpty(); + return showConflicts(conflicts, usagesIn); + } + + private void detectAccessibilityConflicts(final UsageInfo[] usageArray, MultiMap conflicts) { + if (myParameterInitializer != null) { + final ReferencedElementsCollector collector = new ReferencedElementsCollector(); + myParameterInitializer.accept(collector); + final Set result = collector.myResult; + if (!result.isEmpty()) { + for (final UsageInfo usageInfo : usageArray) { + if (usageInfo instanceof ExternalUsageInfo && IntroduceParameterUtil.isMethodUsage(usageInfo)) { + final PsiElement place = usageInfo.getElement(); + for (PsiElement element : result) { + if (element instanceof PsiField && myReplaceFieldsWithGetters != IntroduceParameterRefactoring.REPLACE_FIELDS_WITH_GETTERS_NONE) { //check getter access instead + final PsiClass psiClass = ((PsiField)element).getContainingClass(); + LOG.assertTrue(psiClass != null); + final PsiMethod method = + psiClass.findMethodBySignature(PropertyUtil.generateGetterPrototype((PsiField)element), true); + if (method != null) { + element = method; + } + } + if (element instanceof PsiMember && + !JavaPsiFacade.getInstance(myProject).getResolveHelper().isAccessible((PsiMember)element, place, null)) { + LocalizeValue message = + RefactoringLocalize.zeroIsNotAccessibleFrom1ValueForIntroducedParameterInThatMethodCallWillBeIncorrect( + RefactoringUIUtil.getDescription(element, true), + RefactoringUIUtil.getDescription(ConflictsUtil.getContainer(place), true) + ); + conflicts.putValue(element, message.get()); + } + } + } + } + } + } + } + + public static class AnySupers extends JavaRecursiveElementWalkingVisitor { + private boolean myResult = false; + + @Override + public void visitSuperExpression(PsiSuperExpression expression) { + myResult = true; + } + + public boolean isResult() { + return myResult; + } + + @Override + public void visitReferenceExpression(PsiReferenceExpression expression) { + visitElement(expression); + } + } + + public class AnySameNameVariables extends JavaRecursiveElementWalkingVisitor { + private Pair conflict = null; + + public Pair getConflict() { + return conflict; + } + + @Override + public void visitVariable(PsiVariable variable) { + if (variable == myLocalVariable) { + return; + } + if (myParameterName.equals(variable.getName())) { + LocalizeValue descr = RefactoringLocalize.thereIsAlreadyA0ItWillConflictWithAnIntroducedParameter( + RefactoringUIUtil.getDescription(variable, true) + ); + + conflict = Pair.create(variable, CommonRefactoringUtil.capitalize(descr.get())); + } + } + + @Override + public void visitReferenceExpression(PsiReferenceExpression expression) { + } + + @Override + public void visitElement(PsiElement element) { + if (conflict != null) { + return; + } + super.visitElement(element); + } + } + + protected void performRefactoring(UsageInfo[] usages) { + try { + PsiElementFactory factory = JavaPsiFacade.getInstance(myManager.getProject()).getElementFactory(); + PsiType initializerType = getInitializerType(myForcedType, myParameterInitializer, myLocalVariable); + setForcedType(initializerType); + + // Converting myParameterInitializer + if (myParameterInitializer == null) { + LOG.assertTrue(myLocalVariable != null); + myParameterInitializer = factory.createExpressionFromText(myLocalVariable.getName(), myLocalVariable); + } + else if (myParameterInitializer instanceof PsiArrayInitializerExpression) { + final PsiExpression newExprArrayInitializer = RefactoringUtil.createNewExpressionFromArrayInitializer( + (PsiArrayInitializerExpression)myParameterInitializer, + initializerType + ); + myParameterInitializer = (PsiExpression)myParameterInitializer.replace(newExprArrayInitializer); + } + + myInitializerWrapper = new JavaExpressionWrapper(myParameterInitializer); + + // Changing external occurences (the tricky part) + + IntroduceParameterUtil.processUsages(usages, this); + + if (myGenerateDelegate) { + generateDelegate(myMethodToReplaceIn); + if (myMethodToReplaceIn != myMethodToSearchFor) { + final PsiMethod method = generateDelegate(myMethodToSearchFor); + if (method.getContainingClass().isInterface()) { + final PsiCodeBlock block = method.getBody(); + if (block != null) { + block.delete(); + } + } + } + } + + // Changing signature of initial method + // (signature of myMethodToReplaceIn will be either changed now or have already been changed) + LOG.assertTrue(initializerType.isValid()); + final FieldConflictsResolver fieldConflictsResolver = + new FieldConflictsResolver(myParameterName, myMethodToReplaceIn.getBody()); + IntroduceParameterUtil.changeMethodSignatureAndResolveFieldConflicts(new UsageInfo(myMethodToReplaceIn), usages, this); + if (myMethodToSearchFor != myMethodToReplaceIn) { + IntroduceParameterUtil.changeMethodSignatureAndResolveFieldConflicts(new UsageInfo(myMethodToSearchFor), usages, this); + } + ChangeContextUtil.clearContextInfo(myParameterInitializer); + + // Replacing expression occurrences + for (UsageInfo usage : usages) { + if (usage instanceof ChangedMethodCallInfo) { + PsiElement element = usage.getElement(); + + processChangedMethodCall(element); + } + else if (usage instanceof InternalUsageInfo) { + PsiElement element = usage.getElement(); + if (element instanceof PsiExpression) { + element = RefactoringUtil.outermostParenthesizedExpression((PsiExpression)element); + } + if (element != null) { + if (element.getParent() instanceof PsiExpressionStatement) { + element.getParent().delete(); + } + else { + PsiExpression newExpr = factory.createExpressionFromText(myParameterName, element); + IntroduceVariableBase.replace((PsiExpression)element, newExpr, myProject); + } + } + } + } + + if (myLocalVariable != null && myRemoveLocalVariable) { + myLocalVariable.normalizeDeclaration(); + myLocalVariable.getParent().delete(); + } + fieldConflictsResolver.fix(); + } + catch (IncorrectOperationException ex) { + LOG.error(ex); + } + } + + private PsiMethod generateDelegate(final PsiMethod methodToReplaceIn) throws IncorrectOperationException { + final PsiMethod delegate = (PsiMethod)methodToReplaceIn.copy(); + final PsiElementFactory elementFactory = JavaPsiFacade.getInstance(myManager.getProject()).getElementFactory(); + ChangeSignatureProcessor.makeEmptyBody(elementFactory, delegate); + final PsiCallExpression callExpression = ChangeSignatureProcessor.addDelegatingCallTemplate(delegate, delegate.getName()); + final PsiExpressionList argumentList = callExpression.getArgumentList(); + assert argumentList != null; + final PsiParameter[] psiParameters = methodToReplaceIn.getParameterList().getParameters(); + + final PsiParameter anchorParameter = getAnchorParameter(methodToReplaceIn); + if (psiParameters.length == 0) { + argumentList.add(myParameterInitializer); + } + else { + if (anchorParameter == null) { + argumentList.add(myParameterInitializer); + } + for (int i = 0; i < psiParameters.length; i++) { + PsiParameter psiParameter = psiParameters[i]; + if (!myParametersToRemove.contains(i)) { + final PsiExpression expression = elementFactory.createExpressionFromText(psiParameter.getName(), delegate); + argumentList.add(expression); + } + if (psiParameter == anchorParameter) { + argumentList.add(myParameterInitializer); + } + } + } + + return (PsiMethod)methodToReplaceIn.getContainingClass().addBefore(delegate, methodToReplaceIn); + } + + static PsiType getInitializerType(PsiType forcedType, PsiExpression parameterInitializer, PsiLocalVariable localVariable) { + final PsiType initializerType; + if (forcedType == null) { + if (parameterInitializer == null) { + if (localVariable != null) { + initializerType = localVariable.getType(); + } + else { + LOG.assertTrue(false); + initializerType = null; + } + } + else if (localVariable == null) { + initializerType = RefactoringUtil.getTypeByExpressionWithExpectedType(parameterInitializer); + } + else { + initializerType = localVariable.getType(); + } + } + else { + initializerType = forcedType; + } + return initializerType; + } + + private void processChangedMethodCall(PsiElement element) throws IncorrectOperationException { + if (element.getParent() instanceof PsiMethodCallExpression) { + PsiMethodCallExpression methodCall = (PsiMethodCallExpression)element.getParent(); + + if (myMethodToReplaceIn == myMethodToSearchFor && PsiTreeUtil.isAncestor(methodCall, myParameterInitializer, false)) { + return; + } + + PsiElementFactory factory = JavaPsiFacade.getInstance(methodCall.getProject()).getElementFactory(); + PsiExpression expression = factory.createExpressionFromText(myParameterName, null); + final PsiExpressionList argList = methodCall.getArgumentList(); + final PsiExpression[] exprs = argList.getExpressions(); + + boolean first = false; + PsiElement anchor = null; + if (myMethodToSearchFor.isVarArgs()) { + final int oldParamCount = myMethodToSearchFor.getParameterList().getParametersCount() - 1; + if (exprs.length >= oldParamCount) { + if (oldParamCount > 1) { + anchor = exprs[oldParamCount - 2]; + } + else { + first = true; + anchor = null; + } + } + else { + anchor = exprs[exprs.length - 1]; + } + } + else if (exprs.length > 0) { + anchor = exprs[exprs.length - 1]; + } + + if (anchor != null) { + argList.addAfter(expression, anchor); + } + else if (first && exprs.length > 0) { + argList.addBefore(expression, exprs[0]); + } + else { + argList.add(expression); + } + + removeParametersFromCall(argList); + } + else { + LOG.error(element.getParent()); + } + } + + private void removeParametersFromCall(final PsiExpressionList argList) { + final PsiExpression[] exprs = argList.getExpressions(); + for (int i = myParametersToRemove.size() - 1; i >= 0; i--) { + int paramNum = myParametersToRemove.get(i); + if (paramNum < exprs.length) { + try { + exprs[paramNum].delete(); + } + catch (IncorrectOperationException e) { + LOG.error(e); + } + } + } + } + + protected String getCommandName() { + return RefactoringLocalize.introduceParameterCommand(DescriptiveNameUtil.getDescriptiveName(myMethodToReplaceIn)).get(); + } + + @Nullable + private static PsiParameter getAnchorParameter(PsiMethod methodToReplaceIn) { + PsiParameterList parameterList = methodToReplaceIn.getParameterList(); + final PsiParameter anchorParameter; + final PsiParameter[] parameters = parameterList.getParameters(); + final int length = parameters.length; + if (!methodToReplaceIn.isVarArgs()) { + anchorParameter = length > 0 ? parameters[length - 1] : null; + } + else { + LOG.assertTrue(length > 0); + LOG.assertTrue(parameters[length - 1].isVarArgs()); + anchorParameter = length > 1 ? parameters[length - 2] : null; + } + return anchorParameter; + } + + public PsiMethod getMethodToReplaceIn() { + return myMethodToReplaceIn; + } + + @Nonnull + public PsiMethod getMethodToSearchFor() { + return myMethodToSearchFor; + } + + public JavaExpressionWrapper getParameterInitializer() { + return myInitializerWrapper; + } + + @Nonnull + public String getParameterName() { + return myParameterName; + } + + public boolean isDeclareFinal() { + return myDeclareFinal; + } + + public boolean isGenerateDelegate() { + return myGenerateDelegate; + } + + @Nonnull + public IntList getParametersToRemove() { + return myParametersToRemove; + } + + @Nonnull + public Project getProject() { + return myProject; + } } diff --git a/plugin/src/main/java/com/intellij/java/impl/refactoring/introduceParameter/IntroduceParameterUtil.java b/plugin/src/main/java/com/intellij/java/impl/refactoring/introduceParameter/IntroduceParameterUtil.java index 44fef1dee5..121108ac17 100644 --- a/plugin/src/main/java/com/intellij/java/impl/refactoring/introduceParameter/IntroduceParameterUtil.java +++ b/plugin/src/main/java/com/intellij/java/impl/refactoring/introduceParameter/IntroduceParameterUtil.java @@ -30,97 +30,111 @@ * @author Maxim.Medvedev */ public class IntroduceParameterUtil { - private IntroduceParameterUtil() { - } + private IntroduceParameterUtil() { + } - public static boolean insideMethodToBeReplaced(PsiElement methodUsage, final PsiMethod methodToReplaceIn) { - PsiElement parent = methodUsage.getParent(); - while (parent != null) { - if (parent.equals(methodToReplaceIn)) { - return true; - } - parent = parent.getParent(); + public static boolean insideMethodToBeReplaced(PsiElement methodUsage, final PsiMethod methodToReplaceIn) { + PsiElement parent = methodUsage.getParent(); + while (parent != null) { + if (parent.equals(methodToReplaceIn)) { + return true; + } + parent = parent.getParent(); + } + return false; } - return false; - } - public static boolean isMethodUsage(UsageInfo usageInfo) { - for (IntroduceParameterMethodUsagesProcessor processor : IntroduceParameterMethodUsagesProcessor.EP_NAME.getExtensions()) { - if (processor.isMethodUsage(usageInfo)) return true; + public static boolean isMethodUsage(UsageInfo usageInfo) { + for (IntroduceParameterMethodUsagesProcessor processor : IntroduceParameterMethodUsagesProcessor.EP_NAME.getExtensions()) { + if (processor.isMethodUsage(usageInfo)) { + return true; + } + } + return false; } - return false; - } - public static void addSuperCall(UsageInfo usage, UsageInfo[] usages, final IntroduceParameterData data) - throws IncorrectOperationException { - for (IntroduceParameterMethodUsagesProcessor processor : IntroduceParameterMethodUsagesProcessor.EP_NAME.getExtensions()) { - if (!processor.processAddSuperCall(data, usage, usages)) break; + public static void addSuperCall(UsageInfo usage, UsageInfo[] usages, final IntroduceParameterData data) + throws IncorrectOperationException { + for (IntroduceParameterMethodUsagesProcessor processor : IntroduceParameterMethodUsagesProcessor.EP_NAME.getExtensions()) { + if (!processor.processAddSuperCall(data, usage, usages)) { + break; + } + } } - } - public static void addDefaultConstructor(UsageInfo usage, UsageInfo[] usages, final IntroduceParameterData data) - throws IncorrectOperationException { - for (IntroduceParameterMethodUsagesProcessor processor : IntroduceParameterMethodUsagesProcessor.EP_NAME.getExtensions()) { - if (!processor.processAddDefaultConstructor(data, usage, usages)) break; + public static void addDefaultConstructor(UsageInfo usage, UsageInfo[] usages, final IntroduceParameterData data) + throws IncorrectOperationException { + for (IntroduceParameterMethodUsagesProcessor processor : IntroduceParameterMethodUsagesProcessor.EP_NAME.getExtensions()) { + if (!processor.processAddDefaultConstructor(data, usage, usages)) { + break; + } + } } - } - public static void changeExternalUsage(UsageInfo usage, UsageInfo[] usages, final IntroduceParameterData data) - throws IncorrectOperationException { - for (IntroduceParameterMethodUsagesProcessor processor : IntroduceParameterMethodUsagesProcessor.EP_NAME.getExtensions()) { - if (!processor.processChangeMethodUsage(data, usage, usages)) break; + public static void changeExternalUsage(UsageInfo usage, UsageInfo[] usages, final IntroduceParameterData data) + throws IncorrectOperationException { + for (IntroduceParameterMethodUsagesProcessor processor : IntroduceParameterMethodUsagesProcessor.EP_NAME.getExtensions()) { + if (!processor.processChangeMethodUsage(data, usage, usages)) { + break; + } + } } - } - - public static void changeMethodSignatureAndResolveFieldConflicts(UsageInfo usage, - UsageInfo[] usages, - final IntroduceParameterData data) - throws IncorrectOperationException { - for (IntroduceParameterMethodUsagesProcessor processor : IntroduceParameterMethodUsagesProcessor.EP_NAME.getExtensions()) { - if (!processor.processChangeMethodSignature(data, usage, usages)) break; + + public static void changeMethodSignatureAndResolveFieldConflicts( + UsageInfo usage, + UsageInfo[] usages, + final IntroduceParameterData data + ) + throws IncorrectOperationException { + for (IntroduceParameterMethodUsagesProcessor processor : IntroduceParameterMethodUsagesProcessor.EP_NAME.getExtensions()) { + if (!processor.processChangeMethodSignature(data, usage, usages)) { + break; + } + } } - } - - public static void processUsages(UsageInfo[] usages, IntroduceParameterData data) { - PsiManager manager = PsiManager.getInstance(data.getProject()); - - List methodUsages = new ArrayList(); - - for (UsageInfo usage : usages) { - if (usage instanceof InternalUsageInfo) continue; - - if (usage instanceof DefaultConstructorImplicitUsageInfo) { - addSuperCall(usage, usages, data); - } - else if (usage instanceof NoConstructorClassUsageInfo) { - addDefaultConstructor(usage, usages, data); - } - else { - PsiElement element = usage.getElement(); - if (element instanceof PsiMethod) { - if (!manager.areElementsEquivalent(element, data.getMethodToReplaceIn())) { - methodUsages.add(usage); - } + + public static void processUsages(UsageInfo[] usages, IntroduceParameterData data) { + PsiManager manager = PsiManager.getInstance(data.getProject()); + + List methodUsages = new ArrayList(); + + for (UsageInfo usage : usages) { + if (usage instanceof InternalUsageInfo) { + continue; + } + + if (usage instanceof DefaultConstructorImplicitUsageInfo) { + addSuperCall(usage, usages, data); + } + else if (usage instanceof NoConstructorClassUsageInfo) { + addDefaultConstructor(usage, usages, data); + } + else { + PsiElement element = usage.getElement(); + if (element instanceof PsiMethod) { + if (!manager.areElementsEquivalent(element, data.getMethodToReplaceIn())) { + methodUsages.add(usage); + } + } + else if (!data.isGenerateDelegate()) { + changeExternalUsage(usage, usages, data); + } + } } - else if (!data.isGenerateDelegate()) { - changeExternalUsage(usage, usages, data); + + for (UsageInfo usage : methodUsages) { + changeMethodSignatureAndResolveFieldConflicts(usage, usages, data); } - } } - for (UsageInfo usage : methodUsages) { - changeMethodSignatureAndResolveFieldConflicts(usage, usages, data); - } - } - - public static boolean isMethodInUsages(IntroduceParameterData data, PsiMethod method, UsageInfo[] usages) { - PsiManager manager = PsiManager.getInstance(data.getProject()); - for (UsageInfo info : usages) { - if (!(info instanceof DefaultConstructorImplicitUsageInfo) && manager.areElementsEquivalent(info.getElement(), method)) { - return true; - } + public static boolean isMethodInUsages(IntroduceParameterData data, PsiMethod method, UsageInfo[] usages) { + PsiManager manager = PsiManager.getInstance(data.getProject()); + for (UsageInfo info : usages) { + if (!(info instanceof DefaultConstructorImplicitUsageInfo) && manager.areElementsEquivalent(info.getElement(), method)) { + return true; + } + } + return false; } - return false; - } } diff --git a/plugin/src/main/java/com/intellij/java/impl/refactoring/migration/MigrationMapSet.java b/plugin/src/main/java/com/intellij/java/impl/refactoring/migration/MigrationMapSet.java index 17890f4829..39e9537381 100644 --- a/plugin/src/main/java/com/intellij/java/impl/refactoring/migration/MigrationMapSet.java +++ b/plugin/src/main/java/com/intellij/java/impl/refactoring/migration/MigrationMapSet.java @@ -32,6 +32,7 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -39,391 +40,324 @@ import java.net.URL; import java.util.*; -public class MigrationMapSet -{ - private static final Logger LOG = Logger.getInstance(MigrationMapSet.class); - - private ArrayList myMaps; - @NonNls - private static final String MIGRATION_MAP = "migrationMap"; - @NonNls - private static final String ENTRY = "entry"; - @NonNls - private static final String NAME = "name"; - @NonNls - private static final String OLD_NAME = "oldName"; - @NonNls - private static final String NEW_NAME = "newName"; - @NonNls - private static final String DESCRIPTION = "description"; - @NonNls - private static final String VALUE = "value"; - @NonNls - private static final String TYPE = "type"; - @NonNls - private static final String PACKAGE_TYPE = "package"; - @NonNls - private static final String CLASS_TYPE = "class"; - @NonNls - private static final String RECURSIVE = "recursive"; - - @NonNls - private static final String[] DEFAULT_MAPS = new String[]{ - "/com/intellij/refactoring/migration/res/Swing__1_0_3____1_1_.xml", - }; - private Set myDeletedMaps = new TreeSet<>(); - - public MigrationMapSet() - { - } - - public void addMap(MigrationMap map) - { - if(myMaps == null) - { - loadMaps(); - } - myMaps.add(map); - // saveMaps(); - } - - @Nullable - public MigrationMap findMigrationMap(@Nonnull String name) - { - if(myMaps == null) - { - loadMaps(); - } - for(MigrationMap map : myMaps) - { - if(name.equals(map.getName())) - { - return map; - } - } - return null; - } - - public void replaceMap(MigrationMap oldMap, MigrationMap newMap) - { - for(int i = 0; i < myMaps.size(); i++) - { - if(myMaps.get(i) == oldMap) - { - myMaps.set(i, newMap); - } - } - } - - public void removeMap(MigrationMap map) - { - if(myMaps == null) - { - loadMaps(); - } - myMaps.remove(map); - String name = map.getFileName(); - if(isPredefined(name)) - { - myDeletedMaps.add(name); - } - } - - private static boolean isPredefined(String name) - { - for(PredefinedMigrationProvider provider : Extensions.getExtensions(PredefinedMigrationProvider.EP_NAME)) - { - URL migrationMap = provider.getMigrationMap(); - String fileName = FileUtil.getNameWithoutExtension(new File(migrationMap.getFile())); - if(fileName.equals(name)) - { - return true; - } - } - - for(String defaultTemplate : DEFAULT_MAPS) - { - String fileName = FileUtil.getNameWithoutExtension(StringUtil.getShortName(defaultTemplate, '/')); - - if(fileName.equals(name)) - { - return true; - } - } - return false; - } - - public MigrationMap[] getMaps() - { - if(myMaps == null) - { - loadMaps(); - } - MigrationMap[] ret = new MigrationMap[myMaps.size()]; - for(int i = 0; i < myMaps.size(); i++) - { - ret[i] = myMaps.get(i); - } - return ret; - } - - private static File getMapDirectory() - { - File dir = new File(ContainerPathManager.get().getConfigPath() + File.separator + "migration"); - - if(!dir.exists() && !dir.mkdir()) - { - LOG.error("cannot create directory: " + dir.getAbsolutePath()); - return null; - } - - return dir; - } - - private void copyPredefinedMaps(File dir) - { - File deletedFiles = new File(dir, "deleted.txt"); - if(deletedFiles.isFile()) - { - try - { - myDeletedMaps.addAll(Arrays.asList(FileUtil.loadFile(deletedFiles, true).split("\n"))); - } - catch(IOException e) - { - LOG.error(e); - } - } - - PredefinedMigrationProvider.EP_NAME.forEachExtensionSafe(provider -> - { - URL migrationMap = provider.getMigrationMap(); - String fileName = new File(migrationMap.getFile()).getName(); - if(myDeletedMaps.contains(FileUtil.getNameWithoutExtension(fileName))) - { - return; - } - copyMap(dir, migrationMap, fileName); - }); - - for(String defaultTemplate : DEFAULT_MAPS) - { - URL url = MigrationMapSet.class.getResource(defaultTemplate); - LOG.assertTrue(url != null); - String fileName = defaultTemplate.substring(defaultTemplate.lastIndexOf("/") + 1); - if(myDeletedMaps.contains(FileUtil.getNameWithoutExtension(fileName))) - { - continue; - } - copyMap(dir, url, fileName); - } - } - - private static void copyMap(File dir, URL url, String fileName) - { - File targetFile = new File(dir, fileName); - if(targetFile.isFile()) - { - return; - } - - try - { - FileOutputStream outputStream = new FileOutputStream(targetFile); - InputStream inputStream = url.openStream(); - - try - { - FileUtil.copy(inputStream, outputStream); - } - finally - { - outputStream.close(); - inputStream.close(); - } - } - catch(Exception e) - { - LOG.error(e); - } - } - - private static File[] getMapFiles(final File dir) - { - if(dir == null) - { - return new File[0]; - } - File[] ret = dir.listFiles((f) -> FileUtilRt.extensionEquals(f.getPath(), "xml")); - if(ret == null) - { - LOG.error("cannot read directory: " + dir.getAbsolutePath()); - return new File[0]; - } - return ret; - } - - private void loadMaps() - { - myMaps = new ArrayList<>(); - - - File dir = getMapDirectory(); - copyPredefinedMaps(dir); - - File[] files = getMapFiles(dir); - for(int i = 0; i < files.length; i++) - { - try - { - MigrationMap map = readMap(files[i]); - if(map != null) - { - map.setFileName(FileUtil.getNameWithoutExtension(files[i])); - myMaps.add(map); - } - } - catch(InvalidDataException | JDOMException e) - { - LOG.error("Invalid data in file: " + files[i].getAbsolutePath()); - } - catch(IOException e) - { - LOG.error(e); - } - } - } - - private static MigrationMap readMap(File file) throws JDOMException, InvalidDataException, IOException - { - if(!file.exists()) - { - return null; - } - - Element root = JDOMUtil.load(file); - if(!MIGRATION_MAP.equals(root.getName())) - { - throw new InvalidDataException(); - } - - MigrationMap map = new MigrationMap(); - - for(Iterator i = root.getChildren().iterator(); i.hasNext(); ) - { - Element node = (Element) i.next(); - if(NAME.equals(node.getName())) - { - String name = node.getAttributeValue(VALUE); - map.setName(name); - } - if(DESCRIPTION.equals(node.getName())) - { - String description = node.getAttributeValue(VALUE); - map.setDescription(description); - } - - if(ENTRY.equals(node.getName())) - { - MigrationMapEntry entry = new MigrationMapEntry(); - String oldName = node.getAttributeValue(OLD_NAME); - if(oldName == null) - { - throw new InvalidDataException(); - } - entry.setOldName(oldName); - String newName = node.getAttributeValue(NEW_NAME); - if(newName == null) - { - throw new InvalidDataException(); - } - entry.setNewName(newName); - String typeStr = node.getAttributeValue(TYPE); - if(typeStr == null) - { - throw new InvalidDataException(); - } - entry.setType(MigrationMapEntry.CLASS); - if(typeStr.equals(PACKAGE_TYPE)) - { - entry.setType(MigrationMapEntry.PACKAGE); - @NonNls String isRecursiveStr = node.getAttributeValue(RECURSIVE); - if(isRecursiveStr != null && isRecursiveStr.equals("true")) - { - entry.setRecursive(true); - } - else - { - entry.setRecursive(false); - } - } - map.addEntry(entry); - } - } - - return map; - } - - public void saveMaps() throws IOException - { - File dir = getMapDirectory(); - if(dir == null) - { - return; - } - - File[] files = getMapFiles(dir); - - @NonNls String[] filePaths = new String[myMaps.size()]; - Document[] documents = new Document[myMaps.size()]; - - UniqueNameGenerator namesProvider = new UniqueNameGenerator(); - for(int i = 0; i < myMaps.size(); i++) - { - MigrationMap map = myMaps.get(i); - - filePaths[i] = dir + File.separator + namesProvider.generateUniqueName(map.getFileName()) + ".xml"; - documents[i] = saveMap(map); - } - - JDOMUtil.updateFileSet(files, filePaths, documents, CodeStyleSettingsManager.getSettings(null).getLineSeparator()); - - if(!myDeletedMaps.isEmpty()) - { - FileUtil.writeToFile(new File(dir, "deleted.txt"), StringUtil.join(myDeletedMaps, "\n")); - } - } - - private static Document saveMap(MigrationMap map) - { - Element root = new Element(MIGRATION_MAP); - - Element nameElement = new Element(NAME); - nameElement.setAttribute(VALUE, map.getName()); - root.addContent(nameElement); - - Element descriptionElement = new Element(DESCRIPTION); - descriptionElement.setAttribute(VALUE, StringUtil.notNullize(map.getDescription())); - root.addContent(descriptionElement); - - for(int i = 0; i < map.getEntryCount(); i++) - { - MigrationMapEntry entry = map.getEntryAt(i); - Element element = new Element(ENTRY); - element.setAttribute(OLD_NAME, entry.getOldName()); - element.setAttribute(NEW_NAME, entry.getNewName()); - if(entry.getType() == MigrationMapEntry.PACKAGE) - { - element.setAttribute(TYPE, PACKAGE_TYPE); - element.setAttribute(RECURSIVE, Boolean.valueOf(entry.isRecursive()).toString()); - } - else - { - element.setAttribute(TYPE, CLASS_TYPE); - } - root.addContent(element); - } - - return new Document(root); - } +public class MigrationMapSet { + private static final Logger LOG = Logger.getInstance(MigrationMapSet.class); + + private ArrayList myMaps; + @NonNls + private static final String MIGRATION_MAP = "migrationMap"; + @NonNls + private static final String ENTRY = "entry"; + @NonNls + private static final String NAME = "name"; + @NonNls + private static final String OLD_NAME = "oldName"; + @NonNls + private static final String NEW_NAME = "newName"; + @NonNls + private static final String DESCRIPTION = "description"; + @NonNls + private static final String VALUE = "value"; + @NonNls + private static final String TYPE = "type"; + @NonNls + private static final String PACKAGE_TYPE = "package"; + @NonNls + private static final String CLASS_TYPE = "class"; + @NonNls + private static final String RECURSIVE = "recursive"; + + @NonNls + private static final String[] DEFAULT_MAPS = new String[]{ + "/com/intellij/refactoring/migration/res/Swing__1_0_3____1_1_.xml", + }; + private Set myDeletedMaps = new TreeSet<>(); + + public MigrationMapSet() { + } + + public void addMap(MigrationMap map) { + if (myMaps == null) { + loadMaps(); + } + myMaps.add(map); + // saveMaps(); + } + + @Nullable + public MigrationMap findMigrationMap(@Nonnull String name) { + if (myMaps == null) { + loadMaps(); + } + for (MigrationMap map : myMaps) { + if (name.equals(map.getName())) { + return map; + } + } + return null; + } + + public void replaceMap(MigrationMap oldMap, MigrationMap newMap) { + for (int i = 0; i < myMaps.size(); i++) { + if (myMaps.get(i) == oldMap) { + myMaps.set(i, newMap); + } + } + } + + public void removeMap(MigrationMap map) { + if (myMaps == null) { + loadMaps(); + } + myMaps.remove(map); + String name = map.getFileName(); + if (isPredefined(name)) { + myDeletedMaps.add(name); + } + } + + private static boolean isPredefined(String name) { + for (PredefinedMigrationProvider provider : Extensions.getExtensions(PredefinedMigrationProvider.EP_NAME)) { + URL migrationMap = provider.getMigrationMap(); + String fileName = FileUtil.getNameWithoutExtension(new File(migrationMap.getFile())); + if (fileName.equals(name)) { + return true; + } + } + + for (String defaultTemplate : DEFAULT_MAPS) { + String fileName = FileUtil.getNameWithoutExtension(StringUtil.getShortName(defaultTemplate, '/')); + + if (fileName.equals(name)) { + return true; + } + } + return false; + } + + public MigrationMap[] getMaps() { + if (myMaps == null) { + loadMaps(); + } + MigrationMap[] ret = new MigrationMap[myMaps.size()]; + for (int i = 0; i < myMaps.size(); i++) { + ret[i] = myMaps.get(i); + } + return ret; + } + + private static File getMapDirectory() { + File dir = new File(ContainerPathManager.get().getConfigPath() + File.separator + "migration"); + + if (!dir.exists() && !dir.mkdir()) { + LOG.error("cannot create directory: " + dir.getAbsolutePath()); + return null; + } + + return dir; + } + + private void copyPredefinedMaps(File dir) { + File deletedFiles = new File(dir, "deleted.txt"); + if (deletedFiles.isFile()) { + try { + myDeletedMaps.addAll(Arrays.asList(FileUtil.loadFile(deletedFiles, true).split("\n"))); + } + catch (IOException e) { + LOG.error(e); + } + } + + PredefinedMigrationProvider.EP_NAME.forEachExtensionSafe(provider -> + { + URL migrationMap = provider.getMigrationMap(); + String fileName = new File(migrationMap.getFile()).getName(); + if (myDeletedMaps.contains(FileUtil.getNameWithoutExtension(fileName))) { + return; + } + copyMap(dir, migrationMap, fileName); + }); + + for (String defaultTemplate : DEFAULT_MAPS) { + URL url = MigrationMapSet.class.getResource(defaultTemplate); + LOG.assertTrue(url != null); + String fileName = defaultTemplate.substring(defaultTemplate.lastIndexOf("/") + 1); + if (myDeletedMaps.contains(FileUtil.getNameWithoutExtension(fileName))) { + continue; + } + copyMap(dir, url, fileName); + } + } + + private static void copyMap(File dir, URL url, String fileName) { + File targetFile = new File(dir, fileName); + if (targetFile.isFile()) { + return; + } + + try { + FileOutputStream outputStream = new FileOutputStream(targetFile); + InputStream inputStream = url.openStream(); + + try { + FileUtil.copy(inputStream, outputStream); + } + finally { + outputStream.close(); + inputStream.close(); + } + } + catch (Exception e) { + LOG.error(e); + } + } + + private static File[] getMapFiles(final File dir) { + if (dir == null) { + return new File[0]; + } + File[] ret = dir.listFiles((f) -> FileUtilRt.extensionEquals(f.getPath(), "xml")); + if (ret == null) { + LOG.error("cannot read directory: " + dir.getAbsolutePath()); + return new File[0]; + } + return ret; + } + + private void loadMaps() { + myMaps = new ArrayList<>(); + + + File dir = getMapDirectory(); + copyPredefinedMaps(dir); + + File[] files = getMapFiles(dir); + for (int i = 0; i < files.length; i++) { + try { + MigrationMap map = readMap(files[i]); + if (map != null) { + map.setFileName(FileUtil.getNameWithoutExtension(files[i])); + myMaps.add(map); + } + } + catch (InvalidDataException | JDOMException e) { + LOG.error("Invalid data in file: " + files[i].getAbsolutePath()); + } + catch (IOException e) { + LOG.error(e); + } + } + } + + private static MigrationMap readMap(File file) throws JDOMException, InvalidDataException, IOException { + if (!file.exists()) { + return null; + } + + Element root = JDOMUtil.load(file); + if (!MIGRATION_MAP.equals(root.getName())) { + throw new InvalidDataException(); + } + + MigrationMap map = new MigrationMap(); + + for (Iterator i = root.getChildren().iterator(); i.hasNext(); ) { + Element node = (Element)i.next(); + if (NAME.equals(node.getName())) { + String name = node.getAttributeValue(VALUE); + map.setName(name); + } + if (DESCRIPTION.equals(node.getName())) { + String description = node.getAttributeValue(VALUE); + map.setDescription(description); + } + + if (ENTRY.equals(node.getName())) { + MigrationMapEntry entry = new MigrationMapEntry(); + String oldName = node.getAttributeValue(OLD_NAME); + if (oldName == null) { + throw new InvalidDataException(); + } + entry.setOldName(oldName); + String newName = node.getAttributeValue(NEW_NAME); + if (newName == null) { + throw new InvalidDataException(); + } + entry.setNewName(newName); + String typeStr = node.getAttributeValue(TYPE); + if (typeStr == null) { + throw new InvalidDataException(); + } + entry.setType(MigrationMapEntry.CLASS); + if (typeStr.equals(PACKAGE_TYPE)) { + entry.setType(MigrationMapEntry.PACKAGE); + @NonNls String isRecursiveStr = node.getAttributeValue(RECURSIVE); + if (isRecursiveStr != null && isRecursiveStr.equals("true")) { + entry.setRecursive(true); + } + else { + entry.setRecursive(false); + } + } + map.addEntry(entry); + } + } + + return map; + } + + public void saveMaps() throws IOException { + File dir = getMapDirectory(); + if (dir == null) { + return; + } + + File[] files = getMapFiles(dir); + + @NonNls String[] filePaths = new String[myMaps.size()]; + Document[] documents = new Document[myMaps.size()]; + + UniqueNameGenerator namesProvider = new UniqueNameGenerator(); + for (int i = 0; i < myMaps.size(); i++) { + MigrationMap map = myMaps.get(i); + + filePaths[i] = dir + File.separator + namesProvider.generateUniqueName(map.getFileName()) + ".xml"; + documents[i] = saveMap(map); + } + + JDOMUtil.updateFileSet(files, filePaths, documents, CodeStyleSettingsManager.getSettings(null).getLineSeparator()); + + if (!myDeletedMaps.isEmpty()) { + FileUtil.writeToFile(new File(dir, "deleted.txt"), StringUtil.join(myDeletedMaps, "\n")); + } + } + + private static Document saveMap(MigrationMap map) { + Element root = new Element(MIGRATION_MAP); + + Element nameElement = new Element(NAME); + nameElement.setAttribute(VALUE, map.getName()); + root.addContent(nameElement); + + Element descriptionElement = new Element(DESCRIPTION); + descriptionElement.setAttribute(VALUE, StringUtil.notNullize(map.getDescription())); + root.addContent(descriptionElement); + + for (int i = 0; i < map.getEntryCount(); i++) { + MigrationMapEntry entry = map.getEntryAt(i); + Element element = new Element(ENTRY); + element.setAttribute(OLD_NAME, entry.getOldName()); + element.setAttribute(NEW_NAME, entry.getNewName()); + if (entry.getType() == MigrationMapEntry.PACKAGE) { + element.setAttribute(TYPE, PACKAGE_TYPE); + element.setAttribute(RECURSIVE, Boolean.valueOf(entry.isRecursive()).toString()); + } + else { + element.setAttribute(TYPE, CLASS_TYPE); + } + root.addContent(element); + } + + return new Document(root); + } } diff --git a/plugin/src/main/java/com/intellij/java/impl/refactoring/migration/PredefinedMigrationProvider.java b/plugin/src/main/java/com/intellij/java/impl/refactoring/migration/PredefinedMigrationProvider.java index 311ffe08fd..f57d4e6d5c 100644 --- a/plugin/src/main/java/com/intellij/java/impl/refactoring/migration/PredefinedMigrationProvider.java +++ b/plugin/src/main/java/com/intellij/java/impl/refactoring/migration/PredefinedMigrationProvider.java @@ -24,16 +24,16 @@ @ExtensionAPI(ComponentScope.APPLICATION) public interface PredefinedMigrationProvider { - ExtensionPointName EP_NAME = ExtensionPointName.create(PredefinedMigrationProvider.class); + ExtensionPointName EP_NAME = ExtensionPointName.create(PredefinedMigrationProvider.class); - /** - * URL should point to the file with serialized migration map. - *

- * The simplest way to prepare such map: - * 1. Refactor|Migrate... - * 2. Create new migration map with all settings needed - * 3. Copy map's file from config/migration to the plugin's resources - */ - @Nonnull - URL getMigrationMap(); + /** + * URL should point to the file with serialized migration map. + *

+ * The simplest way to prepare such map: + * 1. Refactor|Migrate... + * 2. Create new migration map with all settings needed + * 3. Copy map's file from config/migration to the plugin's resources + */ + @Nonnull + URL getMigrationMap(); } diff --git a/plugin/src/main/java/com/intellij/java/impl/refactoring/move/moveClassesOrPackages/MoveAllClassesInFileHandler.java b/plugin/src/main/java/com/intellij/java/impl/refactoring/move/moveClassesOrPackages/MoveAllClassesInFileHandler.java index 4f523bacd4..7547ba85b8 100644 --- a/plugin/src/main/java/com/intellij/java/impl/refactoring/move/moveClassesOrPackages/MoveAllClassesInFileHandler.java +++ b/plugin/src/main/java/com/intellij/java/impl/refactoring/move/moveClassesOrPackages/MoveAllClassesInFileHandler.java @@ -22,14 +22,17 @@ import consulo.language.psi.PsiElement; import jakarta.annotation.Nonnull; + import java.util.Map; @ExtensionAPI(ComponentScope.APPLICATION) public abstract class MoveAllClassesInFileHandler { - public static ExtensionPointName EP_NAME = - ExtensionPointName.create(MoveAllClassesInFileHandler.class); + public static ExtensionPointName EP_NAME = + ExtensionPointName.create(MoveAllClassesInFileHandler.class); - public abstract void processMoveAllClassesInFile(@Nonnull Map allClasses, - PsiClass psiClass, - PsiElement... elementsToMove); + public abstract void processMoveAllClassesInFile( + @Nonnull Map allClasses, + PsiClass psiClass, + PsiElement... elementsToMove + ); } diff --git a/plugin/src/main/java/com/intellij/java/impl/refactoring/move/moveClassesOrPackages/MoveClassHandler.java b/plugin/src/main/java/com/intellij/java/impl/refactoring/move/moveClassesOrPackages/MoveClassHandler.java index 6afd81732c..bf319b2c9b 100644 --- a/plugin/src/main/java/com/intellij/java/impl/refactoring/move/moveClassesOrPackages/MoveClassHandler.java +++ b/plugin/src/main/java/com/intellij/java/impl/refactoring/move/moveClassesOrPackages/MoveClassHandler.java @@ -33,23 +33,23 @@ */ @ExtensionAPI(ComponentScope.APPLICATION) public interface MoveClassHandler { - ExtensionPointName EP_NAME = ExtensionPointName.create(MoveClassHandler.class); + ExtensionPointName EP_NAME = ExtensionPointName.create(MoveClassHandler.class); - void prepareMove(@Nonnull PsiClass aClass); + void prepareMove(@Nonnull PsiClass aClass); - void finishMoveClass(@Nonnull PsiClass aClass); + void finishMoveClass(@Nonnull PsiClass aClass); - /** - * @return null if it cannot move aClass - */ - @Nullable - PsiClass doMoveClass(@Nonnull PsiClass aClass, @Nonnull PsiDirectory moveDestination) throws IncorrectOperationException; + /** + * @return null if it cannot move aClass + */ + @Nullable + PsiClass doMoveClass(@Nonnull PsiClass aClass, @Nonnull PsiDirectory moveDestination) throws IncorrectOperationException; - /** - * @param clazz psiClass - * @return null, if this instance of FileNameForPsiProvider cannot provide name for clazz - */ - String getName(PsiClass clazz); + /** + * @param clazz psiClass + * @return null, if this instance of FileNameForPsiProvider cannot provide name for clazz + */ + String getName(PsiClass clazz); - void preprocessUsages(Collection results); + void preprocessUsages(Collection results); } diff --git a/plugin/src/main/java/com/intellij/java/impl/refactoring/move/moveClassesOrPackages/MoveClassToInnerHandler.java b/plugin/src/main/java/com/intellij/java/impl/refactoring/move/moveClassesOrPackages/MoveClassToInnerHandler.java index 9cf8df1673..f21dc63dea 100644 --- a/plugin/src/main/java/com/intellij/java/impl/refactoring/move/moveClassesOrPackages/MoveClassToInnerHandler.java +++ b/plugin/src/main/java/com/intellij/java/impl/refactoring/move/moveClassesOrPackages/MoveClassToInnerHandler.java @@ -27,6 +27,7 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; + import java.util.List; import java.util.Map; @@ -35,19 +36,19 @@ */ @ExtensionAPI(ComponentScope.APPLICATION) public interface MoveClassToInnerHandler { - ExtensionPointName EP_NAME = ExtensionPointName.create(MoveClassToInnerHandler.class); + ExtensionPointName EP_NAME = ExtensionPointName.create(MoveClassToInnerHandler.class); - @Nullable - PsiClass moveClass(@Nonnull PsiClass aClass, @Nonnull PsiClass targetClass); + @Nullable + PsiClass moveClass(@Nonnull PsiClass aClass, @Nonnull PsiClass targetClass); - /** - * filters out import usages from results. Returns all found import usages - */ - List filterImports(@Nonnull List usageInfos, @Nonnull Project project); + /** + * filters out import usages from results. Returns all found import usages + */ + List filterImports(@Nonnull List usageInfos, @Nonnull Project project); - void retargetClassRefsInMoved(@Nonnull Map mapping); + void retargetClassRefsInMoved(@Nonnull Map mapping); - void retargetNonCodeUsages(@Nonnull final Map oldToNewElementMap, @Nonnull NonCodeUsageInfo[] myNonCodeUsages); + void retargetNonCodeUsages(@Nonnull final Map oldToNewElementMap, @Nonnull NonCodeUsageInfo[] myNonCodeUsages); - void removeRedundantImports(PsiFile targetClassFile); + void removeRedundantImports(PsiFile targetClassFile); } diff --git a/plugin/src/main/java/com/intellij/java/impl/refactoring/move/moveClassesOrPackages/MoveClassToInnerProcessor.java b/plugin/src/main/java/com/intellij/java/impl/refactoring/move/moveClassesOrPackages/MoveClassToInnerProcessor.java index 1682ec02a4..2778bde2c3 100644 --- a/plugin/src/main/java/com/intellij/java/impl/refactoring/move/moveClassesOrPackages/MoveClassToInnerProcessor.java +++ b/plugin/src/main/java/com/intellij/java/impl/refactoring/move/moveClassesOrPackages/MoveClassToInnerProcessor.java @@ -56,314 +56,333 @@ * @author yole */ public class MoveClassToInnerProcessor extends BaseRefactoringProcessor { - private static final Logger LOG = Logger.getInstance(MoveClassToInnerProcessor.class); - public static final Key> ourNonCodeUsageKey = Key.create("MoveClassToInner.NonCodeUsage"); - - private PsiClass[] myClassesToMove; - private final PsiClass myTargetClass; - private PsiJavaPackage[] mySourcePackage; - private final PsiJavaPackage myTargetPackage; - private String[] mySourceVisibility; - private final boolean mySearchInComments; - private final boolean mySearchInNonJavaFiles; - private NonCodeUsageInfo[] myNonCodeUsages; - private final MoveCallback myMoveCallback; - - public MoveClassToInnerProcessor(Project project, - final PsiClass[] classesToMove, - @Nonnull final PsiClass targetClass, - boolean searchInComments, - boolean searchInNonJavaFiles, - MoveCallback moveCallback) { - super(project); - setClassesToMove(classesToMove); - myTargetClass = targetClass; - mySearchInComments = searchInComments; - mySearchInNonJavaFiles = searchInNonJavaFiles; - myMoveCallback = moveCallback; - myTargetPackage = JavaDirectoryService.getInstance().getPackage(myTargetClass.getContainingFile().getContainingDirectory()); - } - - private void setClassesToMove(final PsiClass[] classesToMove) { - myClassesToMove = classesToMove; - mySourcePackage = new PsiJavaPackage[classesToMove.length]; - mySourceVisibility = new String[classesToMove.length]; - for (int i = 0; i < classesToMove.length; i++) { - PsiClass psiClass = classesToMove[i]; - mySourceVisibility[i] = VisibilityUtil.getVisibilityModifier(psiClass.getModifierList()); - mySourcePackage[i] = JavaDirectoryService.getInstance().getPackage(psiClass.getContainingFile().getContainingDirectory()); - } - } - - @Nonnull - protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) { - return new MoveMultipleElementsViewDescriptor(myClassesToMove, myTargetClass.getQualifiedName()); - } - - @Nonnull - public UsageInfo[] findUsages() { - final List usages = new ArrayList(); - for (PsiClass classToMove : myClassesToMove) { - final String newName = myTargetClass.getQualifiedName() + "." + classToMove.getName(); - Collections.addAll(usages, MoveClassesOrPackagesUtil.findUsages(classToMove, mySearchInComments, mySearchInNonJavaFiles, newName)); + private static final Logger LOG = Logger.getInstance(MoveClassToInnerProcessor.class); + public static final Key> ourNonCodeUsageKey = Key.create("MoveClassToInner.NonCodeUsage"); + + private PsiClass[] myClassesToMove; + private final PsiClass myTargetClass; + private PsiJavaPackage[] mySourcePackage; + private final PsiJavaPackage myTargetPackage; + private String[] mySourceVisibility; + private final boolean mySearchInComments; + private final boolean mySearchInNonJavaFiles; + private NonCodeUsageInfo[] myNonCodeUsages; + private final MoveCallback myMoveCallback; + + public MoveClassToInnerProcessor( + Project project, + final PsiClass[] classesToMove, + @Nonnull final PsiClass targetClass, + boolean searchInComments, + boolean searchInNonJavaFiles, + MoveCallback moveCallback + ) { + super(project); + setClassesToMove(classesToMove); + myTargetClass = targetClass; + mySearchInComments = searchInComments; + mySearchInNonJavaFiles = searchInNonJavaFiles; + myMoveCallback = moveCallback; + myTargetPackage = JavaDirectoryService.getInstance().getPackage(myTargetClass.getContainingFile().getContainingDirectory()); } - return usages.toArray(new UsageInfo[usages.size()]); - } - - protected boolean preprocessUsages(final Ref refUsages) { - final UsageInfo[] usages = refUsages.get(); - return showConflicts(getConflicts(usages), usages); - } - - protected void refreshElements(final PsiElement[] elements) { - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - final PsiClass[] classesToMove = new PsiClass[elements.length]; + + private void setClassesToMove(final PsiClass[] classesToMove) { + myClassesToMove = classesToMove; + mySourcePackage = new PsiJavaPackage[classesToMove.length]; + mySourceVisibility = new String[classesToMove.length]; for (int i = 0; i < classesToMove.length; i++) { - classesToMove[i] = (PsiClass) elements[i]; + PsiClass psiClass = classesToMove[i]; + mySourceVisibility[i] = VisibilityUtil.getVisibilityModifier(psiClass.getModifierList()); + mySourcePackage[i] = JavaDirectoryService.getInstance().getPackage(psiClass.getContainingFile().getContainingDirectory()); } - setClassesToMove(classesToMove); - } - }); - } + } - protected void performRefactoring(UsageInfo[] usages) { - if (!prepareWritable(usages)) return; + @Nonnull + protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) { + return new MoveMultipleElementsViewDescriptor(myClassesToMove, myTargetClass.getQualifiedName()); + } - MoveClassToInnerHandler[] handlers = MoveClassToInnerHandler.EP_NAME.getExtensions(); + @Nonnull + public UsageInfo[] findUsages() { + final List usages = new ArrayList(); + for (PsiClass classToMove : myClassesToMove) { + final String newName = myTargetClass.getQualifiedName() + "." + classToMove.getName(); + Collections.addAll( + usages, + MoveClassesOrPackagesUtil.findUsages(classToMove, mySearchInComments, mySearchInNonJavaFiles, newName) + ); + } + return usages.toArray(new UsageInfo[usages.size()]); + } - ArrayList usageList = new ArrayList(Arrays.asList(usages)); - List importStatements = new ArrayList(); - for (MoveClassToInnerHandler handler : handlers) { - importStatements.addAll(handler.filterImports(usageList, myProject)); + protected boolean preprocessUsages(final Ref refUsages) { + final UsageInfo[] usages = refUsages.get(); + return showConflicts(getConflicts(usages), usages); } - usages = usageList.toArray(new UsageInfo[usageList.size()]); + protected void refreshElements(final PsiElement[] elements) { + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + final PsiClass[] classesToMove = new PsiClass[elements.length]; + for (int i = 0; i < classesToMove.length; i++) { + classesToMove[i] = (PsiClass)elements[i]; + } + setClassesToMove(classesToMove); + } + }); + } + + protected void performRefactoring(UsageInfo[] usages) { + if (!prepareWritable(usages)) { + return; + } - saveNonCodeUsages(usages); - final Map oldToNewElementsMapping = new HashMap(); - try { - for (PsiClass classToMove : myClassesToMove) { - PsiClass newClass = null; + MoveClassToInnerHandler[] handlers = MoveClassToInnerHandler.EP_NAME.getExtensions(); + + ArrayList usageList = new ArrayList(Arrays.asList(usages)); + List importStatements = new ArrayList(); for (MoveClassToInnerHandler handler : handlers) { - newClass = handler.moveClass(classToMove, myTargetClass); - if (newClass != null) break; + importStatements.addAll(handler.filterImports(usageList, myProject)); + } + + usages = usageList.toArray(new UsageInfo[usageList.size()]); + + saveNonCodeUsages(usages); + final Map oldToNewElementsMapping = new HashMap(); + try { + for (PsiClass classToMove : myClassesToMove) { + PsiClass newClass = null; + for (MoveClassToInnerHandler handler : handlers) { + newClass = handler.moveClass(classToMove, myTargetClass); + if (newClass != null) { + break; + } + } + LOG.assertTrue( + newClass != null, + "There is no appropriate MoveClassToInnerHandler for " + myTargetClass + "; " + classToMove + ); + oldToNewElementsMapping.put(classToMove, newClass); + } + + myNonCodeUsages = CommonMoveUtil.retargetUsages(usages, oldToNewElementsMapping); + for (MoveClassToInnerHandler handler : handlers) { + handler.retargetNonCodeUsages(oldToNewElementsMapping, myNonCodeUsages); + } + + for (MoveClassToInnerHandler handler : handlers) { + handler.retargetClassRefsInMoved(oldToNewElementsMapping); + } + + for (MoveClassToInnerHandler handler : handlers) { + handler.removeRedundantImports(myTargetClass.getContainingFile()); + } + + for (PsiClass classToMove : myClassesToMove) { + classToMove.delete(); + } + + for (PsiElement element : importStatements) { + if (element.isValid()) { + element.delete(); + } + } } - LOG.assertTrue(newClass != null, "There is no appropriate MoveClassToInnerHandler for " + myTargetClass + "; " + classToMove); - oldToNewElementsMapping.put(classToMove, newClass); - } - - myNonCodeUsages = CommonMoveUtil.retargetUsages(usages, oldToNewElementsMapping); - for (MoveClassToInnerHandler handler : handlers) { - handler.retargetNonCodeUsages(oldToNewElementsMapping, myNonCodeUsages); - } - - for (MoveClassToInnerHandler handler : handlers) { - handler.retargetClassRefsInMoved(oldToNewElementsMapping); - } - - for (MoveClassToInnerHandler handler : handlers) { - handler.removeRedundantImports(myTargetClass.getContainingFile()); - } - - for (PsiClass classToMove : myClassesToMove) { - classToMove.delete(); - } - - for (PsiElement element : importStatements) { - if (element.isValid()) { - element.delete(); + catch (IncorrectOperationException e) { + LOG.error(e); } - } - } catch (IncorrectOperationException e) { - LOG.error(e); - } - } - - private boolean prepareWritable(final UsageInfo[] usages) { - Set elementsToMakeWritable = new HashSet(); - Collections.addAll(elementsToMakeWritable, myClassesToMove); - elementsToMakeWritable.add(myTargetClass); - for (UsageInfo usage : usages) { - PsiElement element = usage.getElement(); - if (element != null) { - elementsToMakeWritable.add(element); - } } - if (!CommonRefactoringUtil.checkReadOnlyStatus(myProject, PsiUtilBase.toPsiElementArray(elementsToMakeWritable))) { - return false; - } - return true; - } - - private void saveNonCodeUsages(final UsageInfo[] usages) { - for (PsiClass classToMove : myClassesToMove) { - for (UsageInfo usageInfo : usages) { - if (usageInfo instanceof NonCodeUsageInfo) { - final NonCodeUsageInfo nonCodeUsage = (NonCodeUsageInfo) usageInfo; - PsiElement element = nonCodeUsage.getElement(); - if (element != null && PsiTreeUtil.isAncestor(classToMove, element, false)) { - List list = element.getCopyableUserData(ourNonCodeUsageKey); - if (list == null) { - list = new ArrayList(); - element.putCopyableUserData(ourNonCodeUsageKey, list); + + private boolean prepareWritable(final UsageInfo[] usages) { + Set elementsToMakeWritable = new HashSet(); + Collections.addAll(elementsToMakeWritable, myClassesToMove); + elementsToMakeWritable.add(myTargetClass); + for (UsageInfo usage : usages) { + PsiElement element = usage.getElement(); + if (element != null) { + elementsToMakeWritable.add(element); } - list.add(nonCodeUsage); - } } - } + if (!CommonRefactoringUtil.checkReadOnlyStatus(myProject, PsiUtilBase.toPsiElementArray(elementsToMakeWritable))) { + return false; + } + return true; } - } - protected void performPsiSpoilingRefactoring() { - if (myNonCodeUsages != null) { - RenameUtil.renameNonCodeUsages(myProject, myNonCodeUsages); + private void saveNonCodeUsages(final UsageInfo[] usages) { + for (PsiClass classToMove : myClassesToMove) { + for (UsageInfo usageInfo : usages) { + if (usageInfo instanceof NonCodeUsageInfo) { + final NonCodeUsageInfo nonCodeUsage = (NonCodeUsageInfo)usageInfo; + PsiElement element = nonCodeUsage.getElement(); + if (element != null && PsiTreeUtil.isAncestor(classToMove, element, false)) { + List list = element.getCopyableUserData(ourNonCodeUsageKey); + if (list == null) { + list = new ArrayList(); + element.putCopyableUserData(ourNonCodeUsageKey, list); + } + list.add(nonCodeUsage); + } + } + } + } } - if (myMoveCallback != null) { - if (myMoveCallback instanceof MoveClassesOrPackagesCallback) { - ((MoveClassesOrPackagesCallback) myMoveCallback).classesMovedToInner(myTargetClass); - } - myMoveCallback.refactoringCompleted(); + + protected void performPsiSpoilingRefactoring() { + if (myNonCodeUsages != null) { + RenameUtil.renameNonCodeUsages(myProject, myNonCodeUsages); + } + if (myMoveCallback != null) { + if (myMoveCallback instanceof MoveClassesOrPackagesCallback) { + ((MoveClassesOrPackagesCallback)myMoveCallback).classesMovedToInner(myTargetClass); + } + myMoveCallback.refactoringCompleted(); + } } - } - - @Nonnull - protected String getCommandName() { - return RefactoringLocalize.moveClassToInnerCommandName( - (myClassesToMove.length > 1 ? "classes " : "class ") + StringUtil.join( - myClassesToMove, - PsiNamedElement::getName, - ", " - ), - myTargetClass.getQualifiedName() - ).get(); - } - - @Nonnull - protected Collection getElementsToWrite(@Nonnull final UsageViewDescriptor descriptor) { - List result = new ArrayList(); - result.addAll(super.getElementsToWrite(descriptor)); - result.add(myTargetClass); - return result; - } - - public MultiMap getConflicts(final UsageInfo[] usages) { - MultiMap conflicts = new MultiMap(); - - for (PsiClass classToMove : myClassesToMove) { - final PsiClass innerClass = myTargetClass.findInnerClassByName(classToMove.getName(), false); - if (innerClass != null) { - conflicts.putValue( - innerClass, - RefactoringLocalize.moveToInnerDuplicateInnerClass( - CommonRefactoringUtil.htmlEmphasize(myTargetClass.getQualifiedName()), - CommonRefactoringUtil.htmlEmphasize(classToMove.getName()) - ).get() - ); - } + + @Nonnull + protected String getCommandName() { + return RefactoringLocalize.moveClassToInnerCommandName( + (myClassesToMove.length > 1 ? "classes " : "class ") + StringUtil.join( + myClassesToMove, + PsiNamedElement::getName, + ", " + ), + myTargetClass.getQualifiedName() + ).get(); } - for (int i = 0; i < myClassesToMove.length; i++) { - PsiClass classToMove = myClassesToMove[i]; - String classToMoveVisibility = VisibilityUtil.getVisibilityModifier(classToMove.getModifierList()); - String targetClassVisibility = VisibilityUtil.getVisibilityModifier(myTargetClass.getModifierList()); - - boolean moveToOtherPackage = !Comparing.equal(mySourcePackage[i], myTargetPackage); - if (moveToOtherPackage) { - classToMove.accept(new PackageLocalsUsageCollector(myClassesToMove, new PackageWrapper(myTargetPackage), conflicts)); - } - - ConflictsCollector collector = new ConflictsCollector(classToMove, conflicts); - if ((moveToOtherPackage && - (classToMoveVisibility.equals(PsiModifier.PACKAGE_LOCAL) || targetClassVisibility.equals(PsiModifier.PACKAGE_LOCAL))) || - targetClassVisibility.equals(PsiModifier.PRIVATE)) { - detectInaccessibleClassUsages(usages, collector, mySourceVisibility[i]); - } - if (moveToOtherPackage) { - detectInaccessibleMemberUsages(collector); - } + @Nonnull + protected Collection getElementsToWrite(@Nonnull final UsageViewDescriptor descriptor) { + List result = new ArrayList(); + result.addAll(super.getElementsToWrite(descriptor)); + result.add(myTargetClass); + return result; } - return conflicts; - } + public MultiMap getConflicts(final UsageInfo[] usages) { + MultiMap conflicts = new MultiMap(); + + for (PsiClass classToMove : myClassesToMove) { + final PsiClass innerClass = myTargetClass.findInnerClassByName(classToMove.getName(), false); + if (innerClass != null) { + conflicts.putValue( + innerClass, + RefactoringLocalize.moveToInnerDuplicateInnerClass( + CommonRefactoringUtil.htmlEmphasize(myTargetClass.getQualifiedName()), + CommonRefactoringUtil.htmlEmphasize(classToMove.getName()) + ).get() + ); + } + } - private void detectInaccessibleClassUsages(final UsageInfo[] usages, final ConflictsCollector collector, final String visibility) { - for (UsageInfo usage : usages) { - if (usage instanceof MoveRenameUsageInfo && !(usage instanceof NonCodeUsageInfo)) { - PsiElement element = usage.getElement(); - if (element == null || PsiTreeUtil.getParentOfType(element, PsiImportStatement.class) != null) continue; - if (isInaccessibleFromTarget(element, visibility)) { - collector.addConflict(collector.getClassToMove(), element); + for (int i = 0; i < myClassesToMove.length; i++) { + PsiClass classToMove = myClassesToMove[i]; + String classToMoveVisibility = VisibilityUtil.getVisibilityModifier(classToMove.getModifierList()); + String targetClassVisibility = VisibilityUtil.getVisibilityModifier(myTargetClass.getModifierList()); + + boolean moveToOtherPackage = !Comparing.equal(mySourcePackage[i], myTargetPackage); + if (moveToOtherPackage) { + classToMove.accept(new PackageLocalsUsageCollector(myClassesToMove, new PackageWrapper(myTargetPackage), conflicts)); + } + + ConflictsCollector collector = new ConflictsCollector(classToMove, conflicts); + if ((moveToOtherPackage && + (classToMoveVisibility.equals(PsiModifier.PACKAGE_LOCAL) || targetClassVisibility.equals(PsiModifier.PACKAGE_LOCAL))) || + targetClassVisibility.equals(PsiModifier.PRIVATE)) { + detectInaccessibleClassUsages(usages, collector, mySourceVisibility[i]); + } + if (moveToOtherPackage) { + detectInaccessibleMemberUsages(collector); + } } - } + + return conflicts; } - } - - private boolean isInaccessibleFromTarget(final PsiElement element, final String visibility) { - final PsiJavaPackage elementPackage = JavaDirectoryService.getInstance().getPackage(element.getContainingFile().getContainingDirectory()); - return !PsiUtil.isAccessible(myTargetClass, element, null) || - (!myTargetClass.isInterface() && visibility.equals(PsiModifier.PACKAGE_LOCAL) && !Comparing.equal(elementPackage, myTargetPackage)); - } - - private void detectInaccessibleMemberUsages(final ConflictsCollector collector) { - PsiElement[] members = collectPackageLocalMembers(collector.getClassToMove()); - for (PsiElement member : members) { - ReferencesSearch.search(member).forEach(new Processor() { - public boolean process(final PsiReference psiReference) { - PsiElement element = psiReference.getElement(); - for (PsiClass psiClass : myClassesToMove) { - if (PsiTreeUtil.isAncestor(psiClass, element, false)) return true; - } - if (isInaccessibleFromTarget(element, PsiModifier.PACKAGE_LOCAL)) { - collector.addConflict(psiReference.resolve(), element); - } - return true; + + private void detectInaccessibleClassUsages(final UsageInfo[] usages, final ConflictsCollector collector, final String visibility) { + for (UsageInfo usage : usages) { + if (usage instanceof MoveRenameUsageInfo && !(usage instanceof NonCodeUsageInfo)) { + PsiElement element = usage.getElement(); + if (element == null || PsiTreeUtil.getParentOfType(element, PsiImportStatement.class) != null) { + continue; + } + if (isInaccessibleFromTarget(element, visibility)) { + collector.addConflict(collector.getClassToMove(), element); + } + } } - }); } - } - - private static PsiElement[] collectPackageLocalMembers(PsiElement classToMove) { - return PsiTreeUtil.collectElements(classToMove, new PsiElementFilter() { - public boolean isAccepted(final PsiElement element) { - if (element instanceof PsiMember) { - PsiMember member = (PsiMember) element; - if (VisibilityUtil.getVisibilityModifier(member.getModifierList()) == PsiModifier.PACKAGE_LOCAL) { - return true; - } + + private boolean isInaccessibleFromTarget(final PsiElement element, final String visibility) { + final PsiJavaPackage elementPackage = + JavaDirectoryService.getInstance().getPackage(element.getContainingFile().getContainingDirectory()); + return !PsiUtil.isAccessible(myTargetClass, element, null) + || (!myTargetClass.isInterface() && visibility.equals(PsiModifier.PACKAGE_LOCAL) + && !Comparing.equal(elementPackage, myTargetPackage)); + } + + private void detectInaccessibleMemberUsages(final ConflictsCollector collector) { + PsiElement[] members = collectPackageLocalMembers(collector.getClassToMove()); + for (PsiElement member : members) { + ReferencesSearch.search(member).forEach(new Processor() { + public boolean process(final PsiReference psiReference) { + PsiElement element = psiReference.getElement(); + for (PsiClass psiClass : myClassesToMove) { + if (PsiTreeUtil.isAncestor(psiClass, element, false)) { + return true; + } + } + if (isInaccessibleFromTarget(element, PsiModifier.PACKAGE_LOCAL)) { + collector.addConflict(psiReference.resolve(), element); + } + return true; + } + }); } - return false; - } - }); - } - - private static class ConflictsCollector { - private final PsiClass myClassToMove; - private final MultiMap myConflicts; - private final Set myReportedContainers = new HashSet(); - - public ConflictsCollector(PsiClass classToMove, final MultiMap conflicts) { - myClassToMove = classToMove; - myConflicts = conflicts; } - public synchronized void addConflict(final PsiElement targetElement, final PsiElement sourceElement) { - PsiElement container = ConflictsUtil.getContainer(sourceElement); - if (!myReportedContainers.contains(container)) { - myReportedContainers.add(container); - String targetDescription = (targetElement == myClassToMove) - ? "Class " + CommonRefactoringUtil.htmlEmphasize(myClassToMove.getName()) - : StringUtil.capitalize(RefactoringUIUtil.getDescription(targetElement, true)); - final String message = RefactoringLocalize.elementWillNoLongerBeAccessible( - targetDescription, - RefactoringUIUtil.getDescription(container, true) - ).get(); - myConflicts.putValue(targetElement, message); - } + private static PsiElement[] collectPackageLocalMembers(PsiElement classToMove) { + return PsiTreeUtil.collectElements(classToMove, new PsiElementFilter() { + public boolean isAccepted(final PsiElement element) { + if (element instanceof PsiMember) { + PsiMember member = (PsiMember)element; + if (VisibilityUtil.getVisibilityModifier(member.getModifierList()) == PsiModifier.PACKAGE_LOCAL) { + return true; + } + } + return false; + } + }); } - public PsiElement getClassToMove() { - return myClassToMove; + private static class ConflictsCollector { + private final PsiClass myClassToMove; + private final MultiMap myConflicts; + private final Set myReportedContainers = new HashSet(); + + public ConflictsCollector(PsiClass classToMove, final MultiMap conflicts) { + myClassToMove = classToMove; + myConflicts = conflicts; + } + + public synchronized void addConflict(final PsiElement targetElement, final PsiElement sourceElement) { + PsiElement container = ConflictsUtil.getContainer(sourceElement); + if (!myReportedContainers.contains(container)) { + myReportedContainers.add(container); + String targetDescription = (targetElement == myClassToMove) + ? "Class " + CommonRefactoringUtil.htmlEmphasize(myClassToMove.getName()) + : StringUtil.capitalize(RefactoringUIUtil.getDescription(targetElement, true)); + final String message = RefactoringLocalize.elementWillNoLongerBeAccessible( + targetDescription, + RefactoringUIUtil.getDescription(container, true) + ).get(); + myConflicts.putValue(targetElement, message); + } + } + + public PsiElement getClassToMove() { + return myClassToMove; + } } - } } diff --git a/plugin/src/main/java/com/intellij/java/impl/refactoring/move/moveClassesOrPackages/MoveClassesOrPackagesImpl.java b/plugin/src/main/java/com/intellij/java/impl/refactoring/move/moveClassesOrPackages/MoveClassesOrPackagesImpl.java index 9396d2de9e..655d61e933 100644 --- a/plugin/src/main/java/com/intellij/java/impl/refactoring/move/moveClassesOrPackages/MoveClassesOrPackagesImpl.java +++ b/plugin/src/main/java/com/intellij/java/impl/refactoring/move/moveClassesOrPackages/MoveClassesOrPackagesImpl.java @@ -16,6 +16,7 @@ /** * created at Nov 27, 2001 + * * @author Jeka */ package com.intellij.java.impl.refactoring.move.moveClassesOrPackages; @@ -68,351 +69,409 @@ import java.util.List; public class MoveClassesOrPackagesImpl { - private static final Logger LOG = Logger.getInstance(MoveClassesOrPackagesImpl.class); - - public static void doMove( - final Project project, - PsiElement[] adjustedElements, - PsiElement initialTargetElement, - final MoveCallback moveCallback - ) { - if (!CommonRefactoringUtil.checkReadOnlyStatusRecursively(project, Arrays.asList(adjustedElements), true)) { - return; - } - - final String initialTargetPackageName = getInitialTargetPackageName(initialTargetElement, adjustedElements); - final PsiDirectory initialTargetDirectory = getInitialTargetDirectory(initialTargetElement, adjustedElements); - final boolean isTargetDirectoryFixed = initialTargetDirectory == null; - - boolean searchTextOccurences = false; - for (int i = 0; i < adjustedElements.length && !searchTextOccurences; i++) { - PsiElement psiElement = adjustedElements[i]; - searchTextOccurences = TextOccurrencesUtil.isSearchTextOccurencesEnabled(psiElement); - } - final MoveClassesOrPackagesDialog moveDialog = - new MoveClassesOrPackagesDialog(project, searchTextOccurences, adjustedElements, initialTargetElement, moveCallback); - boolean searchInComments = JavaRefactoringSettings.getInstance().MOVE_SEARCH_IN_COMMENTS; - boolean searchForTextOccurences = JavaRefactoringSettings.getInstance().MOVE_SEARCH_FOR_TEXT; - moveDialog.setData(adjustedElements, initialTargetPackageName, initialTargetDirectory, isTargetDirectoryFixed, initialTargetElement == null, searchInComments, - searchForTextOccurences, HelpID.getMoveHelpID(adjustedElements[0])); - moveDialog.show(); - } + private static final Logger LOG = Logger.getInstance(MoveClassesOrPackagesImpl.class); - @Nullable - @RequiredUIAccess - public static PsiElement[] adjustForMove(final Project project, final PsiElement[] elements, final PsiElement targetElement) { - final PsiElement[] psiElements = new PsiElement[elements.length]; - List names = new ArrayList(); - for (int idx = 0; idx < elements.length; idx++) { - PsiElement element = elements[idx]; - if (element instanceof PsiDirectory directory) { - PsiJavaPackage aPackage = JavaDirectoryService.getInstance().getPackage(directory); - LOG.assertTrue(aPackage != null); - if (aPackage.getQualifiedName().length() == 0) { //is default package - String message = RefactoringLocalize.movePackageRefactoringCannotBeAppliedToDefaultPackage().get(); - CommonRefactoringUtil.showErrorMessage(RefactoringLocalize.moveTitle().get(), message, HelpID.getMoveHelpID(element), project); - return null; - } - if (!checkNesting(project, aPackage, targetElement, true)) return null; - if (!isAlreadyChecked(psiElements, idx, aPackage) && !checkMovePackage(project, aPackage)) return null; - element = aPackage; - } - else if (element instanceof PsiJavaPackage psiPackage) { - if (!checkNesting(project, psiPackage, targetElement, true) - || !checkMovePackage(project, psiPackage)) { - return null; - } - } - else if (element instanceof PsiClass aClass) { - if (aClass instanceof PsiAnonymousClass) { - String message = RefactoringLocalize.moveClassRefactoringCannotBeAppliedToAnonymousClasses().get(); - CommonRefactoringUtil.showErrorMessage(RefactoringLocalize.moveTitle().get(), message, HelpID.getMoveHelpID(element), project); - return null; - } - if (isClassInnerOrLocal(aClass)) { - String message = RefactoringBundle.getCannotRefactorMessage(RefactoringLocalize.movingLocalClassesIsNotSupported().get()); - CommonRefactoringUtil.showErrorMessage(RefactoringLocalize.moveTitle().get(), message, HelpID.getMoveHelpID(element), project); - return null; + public static void doMove( + final Project project, + PsiElement[] adjustedElements, + PsiElement initialTargetElement, + final MoveCallback moveCallback + ) { + if (!CommonRefactoringUtil.checkReadOnlyStatusRecursively(project, Arrays.asList(adjustedElements), true)) { + return; } - String name = null; - for (MoveClassHandler nameProvider : MoveClassHandler.EP_NAME.getExtensionList()) { - name = nameProvider.getName(aClass); - if (name != null) break; - } - if (name == null) name = aClass.getContainingFile().getName(); + final String initialTargetPackageName = getInitialTargetPackageName(initialTargetElement, adjustedElements); + final PsiDirectory initialTargetDirectory = getInitialTargetDirectory(initialTargetElement, adjustedElements); + final boolean isTargetDirectoryFixed = initialTargetDirectory == null; - if (names.contains(name)) { - String message = RefactoringBundle - .getCannotRefactorMessage(RefactoringLocalize.thereAreGoingToBeMultipleDestinationFilesWithTheSameName().get()); - CommonRefactoringUtil.showErrorMessage(RefactoringLocalize.moveTitle().get(), message, HelpID.getMoveHelpID(element), project); - return null; + boolean searchTextOccurences = false; + for (int i = 0; i < adjustedElements.length && !searchTextOccurences; i++) { + PsiElement psiElement = adjustedElements[i]; + searchTextOccurences = TextOccurrencesUtil.isSearchTextOccurencesEnabled(psiElement); } - - names.add(name); - } - psiElements[idx] = element; + final MoveClassesOrPackagesDialog moveDialog = + new MoveClassesOrPackagesDialog(project, searchTextOccurences, adjustedElements, initialTargetElement, moveCallback); + boolean searchInComments = JavaRefactoringSettings.getInstance().MOVE_SEARCH_IN_COMMENTS; + boolean searchForTextOccurences = JavaRefactoringSettings.getInstance().MOVE_SEARCH_FOR_TEXT; + moveDialog.setData( + adjustedElements, + initialTargetPackageName, + initialTargetDirectory, + isTargetDirectoryFixed, + initialTargetElement == null, + searchInComments, + searchForTextOccurences, + HelpID.getMoveHelpID(adjustedElements[0]) + ); + moveDialog.show(); } - return psiElements; - } + @Nullable + @RequiredUIAccess + public static PsiElement[] adjustForMove(final Project project, final PsiElement[] elements, final PsiElement targetElement) { + final PsiElement[] psiElements = new PsiElement[elements.length]; + List names = new ArrayList(); + for (int idx = 0; idx < elements.length; idx++) { + PsiElement element = elements[idx]; + if (element instanceof PsiDirectory directory) { + PsiJavaPackage aPackage = JavaDirectoryService.getInstance().getPackage(directory); + LOG.assertTrue(aPackage != null); + if (aPackage.getQualifiedName().length() == 0) { //is default package + String message = RefactoringLocalize.movePackageRefactoringCannotBeAppliedToDefaultPackage().get(); + CommonRefactoringUtil.showErrorMessage( + RefactoringLocalize.moveTitle().get(), + message, + HelpID.getMoveHelpID(element), + project + ); + return null; + } + if (!checkNesting(project, aPackage, targetElement, true)) { + return null; + } + if (!isAlreadyChecked(psiElements, idx, aPackage) && !checkMovePackage(project, aPackage)) { + return null; + } + element = aPackage; + } + else if (element instanceof PsiJavaPackage psiPackage) { + if (!checkNesting(project, psiPackage, targetElement, true) + || !checkMovePackage(project, psiPackage)) { + return null; + } + } + else if (element instanceof PsiClass aClass) { + if (aClass instanceof PsiAnonymousClass) { + String message = RefactoringLocalize.moveClassRefactoringCannotBeAppliedToAnonymousClasses().get(); + CommonRefactoringUtil.showErrorMessage( + RefactoringLocalize.moveTitle().get(), + message, + HelpID.getMoveHelpID(element), + project + ); + return null; + } + if (isClassInnerOrLocal(aClass)) { + String message = + RefactoringBundle.getCannotRefactorMessage(RefactoringLocalize.movingLocalClassesIsNotSupported().get()); + CommonRefactoringUtil.showErrorMessage( + RefactoringLocalize.moveTitle().get(), + message, + HelpID.getMoveHelpID(element), + project + ); + return null; + } - static boolean isClassInnerOrLocal(PsiClass aClass) { - return aClass.getContainingClass() != null || aClass.getQualifiedName() == null; - } + String name = null; + for (MoveClassHandler nameProvider : MoveClassHandler.EP_NAME.getExtensionList()) { + name = nameProvider.getName(aClass); + if (name != null) { + break; + } + } + if (name == null) { + name = aClass.getContainingFile().getName(); + } - private static boolean isAlreadyChecked(PsiElement[] psiElements, int idx, PsiJavaPackage aPackage) { - for (int i = 0; i < idx; i++) { - if (Comparing.equal(psiElements[i], aPackage)) { - return true; - } + if (names.contains(name)) { + String message = RefactoringBundle + .getCannotRefactorMessage(RefactoringLocalize.thereAreGoingToBeMultipleDestinationFilesWithTheSameName().get()); + CommonRefactoringUtil.showErrorMessage( + RefactoringLocalize.moveTitle().get(), + message, + HelpID.getMoveHelpID(element), + project + ); + return null; + } + + names.add(name); + } + psiElements[idx] = element; + } + + return psiElements; } - return false; - } - @RequiredUIAccess - private static boolean checkMovePackage(Project project, PsiJavaPackage aPackage) { - final PsiDirectory[] directories = aPackage.getDirectories(); - final VirtualFile[] virtualFiles = aPackage.occursInPackagePrefixes(); - if (directories.length > 1 || virtualFiles.length > 0) { - final StringBuffer message = new StringBuffer(); - RenameUtil.buildPackagePrefixChangedMessage(virtualFiles, message, aPackage.getQualifiedName()); - if (directories.length > 1) { - DirectoryAsPackageRenameHandlerBase.buildMultipleDirectoriesInPackageMessage(message, aPackage.getQualifiedName(), directories); - message.append("\n\n"); - LocalizeValue report = - RefactoringLocalize.allTheseDirectoriesWillBeMovedAndAllReferencesTo0WillBeChanged(aPackage.getQualifiedName()); - message.append(report.get()); - } - message.append("\n"); - message.append(RefactoringLocalize.doYouWishToContinue()); - int ret = Messages.showYesNoDialog(project, message.toString(), RefactoringLocalize.warningTitle().get(), UIUtil.getWarningIcon()); - if (ret != 0) { - return false; - } + static boolean isClassInnerOrLocal(PsiClass aClass) { + return aClass.getContainingClass() != null || aClass.getQualifiedName() == null; } - return true; - } - @RequiredUIAccess - static boolean checkNesting(final Project project, final PsiJavaPackage srcPackage, final PsiElement targetElement, boolean showError) { - final PsiJavaPackage targetPackage = targetElement instanceof PsiJavaPackage javaPackage - ? javaPackage - : targetElement instanceof PsiDirectory directory - ? JavaDirectoryService.getInstance().getPackage(directory) - : null; - for (PsiJavaPackage curPackage = targetPackage; curPackage != null; curPackage = curPackage.getParentPackage()) { - if (curPackage.equals(srcPackage)) { - if (showError) { - CommonRefactoringUtil.showErrorMessage( - RefactoringLocalize.moveTitle().get(), - RefactoringLocalize.cannotMovePackageIntoItself().get(), - HelpID.getMoveHelpID(srcPackage), project - ); + private static boolean isAlreadyChecked(PsiElement[] psiElements, int idx, PsiJavaPackage aPackage) { + for (int i = 0; i < idx; i++) { + if (Comparing.equal(psiElements[i], aPackage)) { + return true; + } } return false; - } } - return true; - } - public static String getInitialTargetPackageName(PsiElement initialTargetElement, final PsiElement[] movedElements) { - String name = getContainerPackageName(initialTargetElement); - if (name == null) { - if (movedElements != null) { - name = getTargetPackageNameForMovedElement(movedElements[0]); - } - if (name == null) { - final PsiDirectory commonDirectory = getCommonDirectory(movedElements); - if (commonDirectory != null && JavaDirectoryService.getInstance().getPackage(commonDirectory) != null) { - name = JavaDirectoryService.getInstance().getPackage(commonDirectory).getQualifiedName(); + @RequiredUIAccess + private static boolean checkMovePackage(Project project, PsiJavaPackage aPackage) { + final PsiDirectory[] directories = aPackage.getDirectories(); + final VirtualFile[] virtualFiles = aPackage.occursInPackagePrefixes(); + if (directories.length > 1 || virtualFiles.length > 0) { + final StringBuffer message = new StringBuffer(); + RenameUtil.buildPackagePrefixChangedMessage(virtualFiles, message, aPackage.getQualifiedName()); + if (directories.length > 1) { + DirectoryAsPackageRenameHandlerBase.buildMultipleDirectoriesInPackageMessage( + message, + aPackage.getQualifiedName(), + directories + ); + message.append("\n\n"); + LocalizeValue report = + RefactoringLocalize.allTheseDirectoriesWillBeMovedAndAllReferencesTo0WillBeChanged(aPackage.getQualifiedName()); + message.append(report.get()); + } + message.append("\n"); + message.append(RefactoringLocalize.doYouWishToContinue()); + int ret = + Messages.showYesNoDialog(project, message.toString(), RefactoringLocalize.warningTitle().get(), UIUtil.getWarningIcon()); + if (ret != 0) { + return false; + } } - } - } - if (name == null) { - name = ""; + return true; } - return name; - } - - @Nullable - private static PsiDirectory getCommonDirectory(PsiElement[] movedElements) { - PsiDirectory commonDirectory = null; - for (PsiElement movedElement : movedElements) { - final PsiFile containingFile = movedElement.getContainingFile(); - if (containingFile != null) { - final PsiDirectory containingDirectory = containingFile.getContainingDirectory(); - if (containingDirectory != null) { - if (commonDirectory == null) { - commonDirectory = containingDirectory; - } - else { - if (commonDirectory != containingDirectory) { - return null; + @RequiredUIAccess + static boolean checkNesting(final Project project, final PsiJavaPackage srcPackage, final PsiElement targetElement, boolean showError) { + final PsiJavaPackage targetPackage = targetElement instanceof PsiJavaPackage javaPackage + ? javaPackage + : targetElement instanceof PsiDirectory directory + ? JavaDirectoryService.getInstance().getPackage(directory) + : null; + for (PsiJavaPackage curPackage = targetPackage; curPackage != null; curPackage = curPackage.getParentPackage()) { + if (curPackage.equals(srcPackage)) { + if (showError) { + CommonRefactoringUtil.showErrorMessage( + RefactoringLocalize.moveTitle().get(), + RefactoringLocalize.cannotMovePackageIntoItself().get(), + HelpID.getMoveHelpID(srcPackage), project + ); + } + return false; } - } } - } - } - if (commonDirectory != null) { - return commonDirectory; - } - else { - return null; - } - } - - private static String getContainerPackageName(final PsiElement psiElement) { - if (psiElement instanceof PsiJavaPackage javaPackage) { - return javaPackage.getQualifiedName(); - } - else if (psiElement instanceof PsiDirectory directory) { - PsiJavaPackage aPackage = JavaDirectoryService.getInstance().getPackage(directory); - return aPackage != null ? aPackage.getQualifiedName() : ""; - } - else if (psiElement != null) { - PsiJavaPackage aPackage = JavaDirectoryService.getInstance().getPackage(psiElement.getContainingFile().getContainingDirectory()); - return aPackage != null ? aPackage.getQualifiedName() : ""; - } - else { - return null; + return true; } - } - private static String getTargetPackageNameForMovedElement(final PsiElement psiElement) { - if (psiElement instanceof PsiJavaPackage psiPackage) { - final PsiJavaPackage parentPackage = psiPackage.getParentPackage(); - return parentPackage != null ? parentPackage.getQualifiedName() : ""; - } - else if (psiElement instanceof PsiDirectory directory) { - PsiJavaPackage aPackage = JavaDirectoryService.getInstance().getPackage(directory); - return aPackage != null ? getTargetPackageNameForMovedElement(aPackage) : ""; - } - else if (psiElement != null) { - PsiJavaPackage aPackage = JavaDirectoryService.getInstance().getPackage(psiElement.getContainingFile().getContainingDirectory()); - return aPackage != null ? aPackage.getQualifiedName() : ""; - } - else { - return null; + public static String getInitialTargetPackageName(PsiElement initialTargetElement, final PsiElement[] movedElements) { + String name = getContainerPackageName(initialTargetElement); + if (name == null) { + if (movedElements != null) { + name = getTargetPackageNameForMovedElement(movedElements[0]); + } + if (name == null) { + final PsiDirectory commonDirectory = getCommonDirectory(movedElements); + if (commonDirectory != null && JavaDirectoryService.getInstance().getPackage(commonDirectory) != null) { + name = JavaDirectoryService.getInstance().getPackage(commonDirectory).getQualifiedName(); + } + } + } + if (name == null) { + name = ""; + } + return name; } - } + @Nullable + private static PsiDirectory getCommonDirectory(PsiElement[] movedElements) { + PsiDirectory commonDirectory = null; - public static PsiDirectory getInitialTargetDirectory(PsiElement initialTargetElement, final PsiElement[] movedElements) { - PsiDirectory initialTargetDirectory = getContainerDirectory(initialTargetElement); - if (initialTargetDirectory == null) { - if (movedElements != null) { - final PsiDirectory commonDirectory = getCommonDirectory(movedElements); + for (PsiElement movedElement : movedElements) { + final PsiFile containingFile = movedElement.getContainingFile(); + if (containingFile != null) { + final PsiDirectory containingDirectory = containingFile.getContainingDirectory(); + if (containingDirectory != null) { + if (commonDirectory == null) { + commonDirectory = containingDirectory; + } + else { + if (commonDirectory != containingDirectory) { + return null; + } + } + } + } + } if (commonDirectory != null) { - initialTargetDirectory = commonDirectory; + return commonDirectory; } else { - initialTargetDirectory = getContainerDirectory(movedElements[0]); + return null; } - } } - return initialTargetDirectory; - } - @Nullable - public static PsiDirectory getContainerDirectory(final PsiElement psiElement) { - if (psiElement instanceof PsiJavaPackage javaPackage) { - final PsiDirectory[] directories = javaPackage.getDirectories(); - return directories.length == 1 ? directories[0] : null; //?? - } - if (psiElement instanceof PsiDirectory directory) { - return directory; - } - if (psiElement != null) { - return psiElement.getContainingFile().getContainingDirectory(); + private static String getContainerPackageName(final PsiElement psiElement) { + if (psiElement instanceof PsiJavaPackage javaPackage) { + return javaPackage.getQualifiedName(); + } + else if (psiElement instanceof PsiDirectory directory) { + PsiJavaPackage aPackage = JavaDirectoryService.getInstance().getPackage(directory); + return aPackage != null ? aPackage.getQualifiedName() : ""; + } + else if (psiElement != null) { + PsiJavaPackage aPackage = + JavaDirectoryService.getInstance().getPackage(psiElement.getContainingFile().getContainingDirectory()); + return aPackage != null ? aPackage.getQualifiedName() : ""; + } + else { + return null; + } } - return null; - } - @RequiredUIAccess - public static void doRearrangePackage(final Project project, final PsiDirectory[] directories) { - if (!CommonRefactoringUtil.checkReadOnlyStatusRecursively(project, Arrays.asList(directories), true)) { - return; + private static String getTargetPackageNameForMovedElement(final PsiElement psiElement) { + if (psiElement instanceof PsiJavaPackage psiPackage) { + final PsiJavaPackage parentPackage = psiPackage.getParentPackage(); + return parentPackage != null ? parentPackage.getQualifiedName() : ""; + } + else if (psiElement instanceof PsiDirectory directory) { + PsiJavaPackage aPackage = JavaDirectoryService.getInstance().getPackage(directory); + return aPackage != null ? getTargetPackageNameForMovedElement(aPackage) : ""; + } + else if (psiElement != null) { + PsiJavaPackage aPackage = + JavaDirectoryService.getInstance().getPackage(psiElement.getContainingFile().getContainingDirectory()); + return aPackage != null ? aPackage.getQualifiedName() : ""; + } + else { + return null; + } } - List sourceRootDirectories = buildRearrangeTargetsList(project, directories); - DirectoryChooser chooser = new DirectoryChooser(project); - chooser.setTitle(RefactoringLocalize.selectSourceRootChooserTitle()); - chooser.fillList(sourceRootDirectories.toArray(new PsiDirectory[sourceRootDirectories.size()]), null, project, ""); - chooser.show(); - if (!chooser.isOK()) return; - final PsiDirectory selectedTarget = chooser.getSelectedDirectory(); - if (selectedTarget == null) return; - final MultiMap conflicts = new MultiMap<>(); - final Runnable analyzeConflicts = () -> - RefactoringConflictsUtil.analyzeModuleConflicts(project, Arrays.asList(directories), UsageInfo.EMPTY_ARRAY, selectedTarget, conflicts); - if (!ProgressManager.getInstance() - .runProcessWithProgressSynchronously(analyzeConflicts, "Analyze Module Conflicts...", true, project)) { - return; + + public static PsiDirectory getInitialTargetDirectory(PsiElement initialTargetElement, final PsiElement[] movedElements) { + PsiDirectory initialTargetDirectory = getContainerDirectory(initialTargetElement); + if (initialTargetDirectory == null) { + if (movedElements != null) { + final PsiDirectory commonDirectory = getCommonDirectory(movedElements); + if (commonDirectory != null) { + initialTargetDirectory = commonDirectory; + } + else { + initialTargetDirectory = getContainerDirectory(movedElements[0]); + } + } + } + return initialTargetDirectory; } - if (!conflicts.isEmpty()) { - if (project.getApplication().isUnitTestMode()) { - throw new BaseRefactoringProcessor.ConflictsInTestsException(conflicts.values()); - } - else { - final ConflictsDialog conflictsDialog = new ConflictsDialog(project, conflicts); - conflictsDialog.show(); - if (!conflictsDialog.isOK()) { - return; + + @Nullable + public static PsiDirectory getContainerDirectory(final PsiElement psiElement) { + if (psiElement instanceof PsiJavaPackage javaPackage) { + final PsiDirectory[] directories = javaPackage.getDirectories(); + return directories.length == 1 ? directories[0] : null; //?? } - } + if (psiElement instanceof PsiDirectory directory) { + return directory; + } + if (psiElement != null) { + return psiElement.getContainingFile().getContainingDirectory(); + } + return null; } - final Ref ex = Ref.create(null); - final String commandDescription = RefactoringLocalize.movingDirectoriesCommand().get(); - Runnable runnable = () -> project.getApplication().runWriteAction(() -> { - LocalHistoryAction a = LocalHistory.getInstance().startAction(commandDescription); - try { - rearrangeDirectoriesToTarget(directories, selectedTarget); - } - catch (IncorrectOperationException e) { - ex.set(e); - } - finally { - a.finish(); - } - }); - CommandProcessor.getInstance().executeCommand(project, runnable, commandDescription, null); - if (ex.get() != null) { - RefactoringUIUtil.processIncorrectOperation(project, ex.get()); + + @RequiredUIAccess + public static void doRearrangePackage(final Project project, final PsiDirectory[] directories) { + if (!CommonRefactoringUtil.checkReadOnlyStatusRecursively(project, Arrays.asList(directories), true)) { + return; + } + + List sourceRootDirectories = buildRearrangeTargetsList(project, directories); + DirectoryChooser chooser = new DirectoryChooser(project); + chooser.setTitle(RefactoringLocalize.selectSourceRootChooserTitle()); + chooser.fillList(sourceRootDirectories.toArray(new PsiDirectory[sourceRootDirectories.size()]), null, project, ""); + chooser.show(); + if (!chooser.isOK()) { + return; + } + final PsiDirectory selectedTarget = chooser.getSelectedDirectory(); + if (selectedTarget == null) { + return; + } + final MultiMap conflicts = new MultiMap<>(); + final Runnable analyzeConflicts = () -> + RefactoringConflictsUtil.analyzeModuleConflicts( + project, + Arrays.asList(directories), + UsageInfo.EMPTY_ARRAY, + selectedTarget, + conflicts + ); + if (!ProgressManager.getInstance() + .runProcessWithProgressSynchronously(analyzeConflicts, "Analyze Module Conflicts...", true, project)) { + return; + } + if (!conflicts.isEmpty()) { + if (project.getApplication().isUnitTestMode()) { + throw new BaseRefactoringProcessor.ConflictsInTestsException(conflicts.values()); + } + else { + final ConflictsDialog conflictsDialog = new ConflictsDialog(project, conflicts); + conflictsDialog.show(); + if (!conflictsDialog.isOK()) { + return; + } + } + } + final Ref ex = Ref.create(null); + final String commandDescription = RefactoringLocalize.movingDirectoriesCommand().get(); + Runnable runnable = () -> project.getApplication().runWriteAction(() -> { + LocalHistoryAction a = LocalHistory.getInstance().startAction(commandDescription); + try { + rearrangeDirectoriesToTarget(directories, selectedTarget); + } + catch (IncorrectOperationException e) { + ex.set(e); + } + finally { + a.finish(); + } + }); + CommandProcessor.getInstance().executeCommand(project, runnable, commandDescription, null); + if (ex.get() != null) { + RefactoringUIUtil.processIncorrectOperation(project, ex.get()); + } } - } - @RequiredUIAccess - private static List buildRearrangeTargetsList(final Project project, final PsiDirectory[] directories) { - final VirtualFile[] sourceRoots = ProjectRootManager.getInstance(project).getContentSourceRoots(); - List sourceRootDirectories = new ArrayList<>(); - sourceRoots: - for (final VirtualFile sourceRoot : sourceRoots) { - PsiDirectory sourceRootDirectory = PsiManager.getInstance(project).findDirectory(sourceRoot); - if (sourceRootDirectory == null) continue; - final PsiJavaPackage aPackage = JavaDirectoryService.getInstance().getPackage(sourceRootDirectory); - if (aPackage == null) continue; - final String packagePrefix = aPackage.getQualifiedName(); - for (final PsiDirectory directory : directories) { - String qualifiedName = JavaDirectoryService.getInstance().getPackage(directory).getQualifiedName(); - if (!qualifiedName.startsWith(packagePrefix)) { - continue sourceRoots; + @RequiredUIAccess + private static List buildRearrangeTargetsList(final Project project, final PsiDirectory[] directories) { + final VirtualFile[] sourceRoots = ProjectRootManager.getInstance(project).getContentSourceRoots(); + List sourceRootDirectories = new ArrayList<>(); + sourceRoots: + for (final VirtualFile sourceRoot : sourceRoots) { + PsiDirectory sourceRootDirectory = PsiManager.getInstance(project).findDirectory(sourceRoot); + if (sourceRootDirectory == null) { + continue; + } + final PsiJavaPackage aPackage = JavaDirectoryService.getInstance().getPackage(sourceRootDirectory); + if (aPackage == null) { + continue; + } + final String packagePrefix = aPackage.getQualifiedName(); + for (final PsiDirectory directory : directories) { + String qualifiedName = JavaDirectoryService.getInstance().getPackage(directory).getQualifiedName(); + if (!qualifiedName.startsWith(packagePrefix)) { + continue sourceRoots; + } + } + sourceRootDirectories.add(sourceRootDirectory); } - } - sourceRootDirectories.add(sourceRootDirectory); + return sourceRootDirectories; } - return sourceRootDirectories; - } - private static void rearrangeDirectoriesToTarget(PsiDirectory[] directories, PsiDirectory selectedTarget) - throws IncorrectOperationException { - final VirtualFile sourceRoot = selectedTarget.getVirtualFile(); - for (PsiDirectory directory : directories) { - final PsiJavaPackage parentPackage = JavaDirectoryService.getInstance().getPackage(directory).getParentPackage(); - final PackageWrapper wrapper = new PackageWrapper(parentPackage); - final PsiDirectory moveTarget = RefactoringUtil.createPackageDirectoryInSourceRoot(wrapper, sourceRoot); - MoveClassesOrPackagesUtil.moveDirectoryRecursively(directory, moveTarget); + private static void rearrangeDirectoriesToTarget(PsiDirectory[] directories, PsiDirectory selectedTarget) + throws IncorrectOperationException { + final VirtualFile sourceRoot = selectedTarget.getVirtualFile(); + for (PsiDirectory directory : directories) { + final PsiJavaPackage parentPackage = JavaDirectoryService.getInstance().getPackage(directory).getParentPackage(); + final PackageWrapper wrapper = new PackageWrapper(parentPackage); + final PsiDirectory moveTarget = RefactoringUtil.createPackageDirectoryInSourceRoot(wrapper, sourceRoot); + MoveClassesOrPackagesUtil.moveDirectoryRecursively(directory, moveTarget); + } } - } } diff --git a/plugin/src/main/java/com/intellij/java/impl/refactoring/move/moveClassesOrPackages/MoveClassesOrPackagesProcessor.java b/plugin/src/main/java/com/intellij/java/impl/refactoring/move/moveClassesOrPackages/MoveClassesOrPackagesProcessor.java index 02f8d95357..1c09cb5689 100644 --- a/plugin/src/main/java/com/intellij/java/impl/refactoring/move/moveClassesOrPackages/MoveClassesOrPackagesProcessor.java +++ b/plugin/src/main/java/com/intellij/java/impl/refactoring/move/moveClassesOrPackages/MoveClassesOrPackagesProcessor.java @@ -62,574 +62,615 @@ * @author Jeka, dsl */ public class MoveClassesOrPackagesProcessor extends BaseRefactoringProcessor { - private static final Logger LOG = Logger.getInstance(MoveClassesOrPackagesProcessor.class); - - private final PsiElement[] myElementsToMove; - private boolean mySearchInComments; - private boolean mySearchInNonJavaFiles; - private final PackageWrapper myTargetPackage; - private final MoveCallback myMoveCallback; - protected - @Nonnull - final MoveDestination myMoveDestination; - protected NonCodeUsageInfo[] myNonCodeUsages; - - public MoveClassesOrPackagesProcessor( - Project project, - PsiElement[] elements, - @Nonnull final MoveDestination moveDestination, - boolean searchInComments, - boolean searchInNonJavaFiles, - MoveCallback moveCallback - ) { - super(project); - final Set toMove = new LinkedHashSet<>(); - for (PsiElement element : elements) { - if (element instanceof PsiClassOwner classOwner) { - Collections.addAll(toMove, classOwner.getClasses()); - } else { - toMove.add(element); - } + private static final Logger LOG = Logger.getInstance(MoveClassesOrPackagesProcessor.class); + + private final PsiElement[] myElementsToMove; + private boolean mySearchInComments; + private boolean mySearchInNonJavaFiles; + private final PackageWrapper myTargetPackage; + private final MoveCallback myMoveCallback; + protected + @Nonnull + final MoveDestination myMoveDestination; + protected NonCodeUsageInfo[] myNonCodeUsages; + + public MoveClassesOrPackagesProcessor( + Project project, + PsiElement[] elements, + @Nonnull final MoveDestination moveDestination, + boolean searchInComments, + boolean searchInNonJavaFiles, + MoveCallback moveCallback + ) { + super(project); + final Set toMove = new LinkedHashSet<>(); + for (PsiElement element : elements) { + if (element instanceof PsiClassOwner classOwner) { + Collections.addAll(toMove, classOwner.getClasses()); + } + else { + toMove.add(element); + } + } + myElementsToMove = PsiUtilCore.toPsiElementArray(toMove); + Arrays.sort(myElementsToMove, (o1, o2) -> { + if (o1 instanceof PsiClass class1 && o2 instanceof PsiClass class2) { + final PsiFile containingFile = o1.getContainingFile(); + if (Comparing.equal(containingFile, o2.getContainingFile())) { + final VirtualFile virtualFile = containingFile.getVirtualFile(); + if (virtualFile != null) { + final String fileName = virtualFile.getNameWithoutExtension(); + if (Comparing.strEqual(fileName, class1.getName())) { + return -1; + } + if (Comparing.strEqual(fileName, class2.getName())) { + return 1; + } + } + } + } + return 0; + }); + myMoveDestination = moveDestination; + myTargetPackage = myMoveDestination.getTargetPackage(); + mySearchInComments = searchInComments; + mySearchInNonJavaFiles = searchInNonJavaFiles; + myMoveCallback = moveCallback; } - myElementsToMove = PsiUtilCore.toPsiElementArray(toMove); - Arrays.sort(myElementsToMove, (o1, o2) -> { - if (o1 instanceof PsiClass class1 && o2 instanceof PsiClass class2) { - final PsiFile containingFile = o1.getContainingFile(); - if (Comparing.equal(containingFile, o2.getContainingFile())) { - final VirtualFile virtualFile = containingFile.getVirtualFile(); - if (virtualFile != null) { - final String fileName = virtualFile.getNameWithoutExtension(); - if (Comparing.strEqual(fileName, class1.getName())) return -1; - if (Comparing.strEqual(fileName, class2.getName())) return 1; - } - } - } - return 0; - }); - myMoveDestination = moveDestination; - myTargetPackage = myMoveDestination.getTargetPackage(); - mySearchInComments = searchInComments; - mySearchInNonJavaFiles = searchInNonJavaFiles; - myMoveCallback = moveCallback; - } - - @Nonnull - protected UsageViewDescriptor createUsageViewDescriptor(@Nonnull UsageInfo[] usages) { - PsiElement[] elements = new PsiElement[myElementsToMove.length]; - System.arraycopy(myElementsToMove, 0, elements, 0, myElementsToMove.length); - return new MoveMultipleElementsViewDescriptor(elements, MoveClassesOrPackagesUtil.getPackageName(myTargetPackage)); - } - - @RequiredUIAccess - public boolean verifyValidPackageName() { - String qName = myTargetPackage.getQualifiedName(); - if (!StringUtil.isEmpty(qName)) { - PsiNameHelper helper = PsiNameHelper.getInstance(myProject); - if (!helper.isQualifiedName(qName)) { - Messages.showMessageDialog( - myProject, - RefactoringLocalize.invalidTargetPackageNameSpecified().get(), - "Invalid Package Name", - UIUtil.getErrorIcon() - ); - return false; - } + + @Nonnull + protected UsageViewDescriptor createUsageViewDescriptor(@Nonnull UsageInfo[] usages) { + PsiElement[] elements = new PsiElement[myElementsToMove.length]; + System.arraycopy(myElementsToMove, 0, elements, 0, myElementsToMove.length); + return new MoveMultipleElementsViewDescriptor(elements, MoveClassesOrPackagesUtil.getPackageName(myTargetPackage)); } - return true; - } - private boolean hasClasses() { - for (PsiElement element : getElements()) { - if (element instanceof PsiClass) { + @RequiredUIAccess + public boolean verifyValidPackageName() { + String qName = myTargetPackage.getQualifiedName(); + if (!StringUtil.isEmpty(qName)) { + PsiNameHelper helper = PsiNameHelper.getInstance(myProject); + if (!helper.isQualifiedName(qName)) { + Messages.showMessageDialog( + myProject, + RefactoringLocalize.invalidTargetPackageNameSpecified().get(), + "Invalid Package Name", + UIUtil.getErrorIcon() + ); + return false; + } + } return true; - } } - return false; - } - - public boolean isSearchInComments() { - return mySearchInComments; - } - - public boolean isSearchInNonJavaFiles() { - return mySearchInNonJavaFiles; - } - - public void setSearchInComments(boolean searchInComments) { - mySearchInComments = searchInComments; - } - - public void setSearchInNonJavaFiles(boolean searchInNonJavaFiles) { - mySearchInNonJavaFiles = searchInNonJavaFiles; - } - - @Nonnull - @RequiredReadAction - protected UsageInfo[] findUsages() { - List allUsages = new ArrayList<>(); - MultiMap conflicts = new MultiMap<>(); - for (PsiElement element : myElementsToMove) { - String newName = getNewQName(element); - if (newName == null) continue; - final UsageInfo[] usages = MoveClassesOrPackagesUtil.findUsages(element, mySearchInComments, mySearchInNonJavaFiles, newName); - allUsages.addAll(new ArrayList<>(Arrays.asList(usages))); - if (element instanceof PsiJavaPackage javaPackage) { - for (PsiDirectory directory : javaPackage.getDirectories()) { - final UsageInfo[] dirUsages = - MoveClassesOrPackagesUtil.findUsages(directory, mySearchInComments, mySearchInNonJavaFiles, newName); - allUsages.addAll(new ArrayList<>(Arrays.asList(dirUsages))); - } - } + + private boolean hasClasses() { + for (PsiElement element : getElements()) { + if (element instanceof PsiClass) { + return true; + } + } + return false; } - myMoveDestination.analyzeModuleConflicts(Arrays.asList(myElementsToMove), conflicts, - allUsages.toArray(new UsageInfo[allUsages.size()])); - final UsageInfo[] usageInfos = allUsages.toArray(new UsageInfo[allUsages.size()]); - detectPackageLocalsMoved(usageInfos, conflicts); - detectPackageLocalsUsed(conflicts); - if (!conflicts.isEmpty()) { - for (PsiElement element : conflicts.keySet()) { - allUsages.add(new ConflictsUsageInfo(element, conflicts.get(element))); - } + + public boolean isSearchInComments() { + return mySearchInComments; } - return UsageViewUtil.removeDuplicatedUsages(allUsages.toArray(new UsageInfo[allUsages.size()])); - } + public boolean isSearchInNonJavaFiles() { + return mySearchInNonJavaFiles; + } - public List getElements() { - return Collections.unmodifiableList(Arrays.asList(myElementsToMove)); - } + public void setSearchInComments(boolean searchInComments) { + mySearchInComments = searchInComments; + } - public PackageWrapper getTargetPackage() { - return myMoveDestination.getTargetPackage(); - } + public void setSearchInNonJavaFiles(boolean searchInNonJavaFiles) { + mySearchInNonJavaFiles = searchInNonJavaFiles; + } - protected static class ConflictsUsageInfo extends UsageInfo { - private final Collection myConflicts; + @Nonnull + @RequiredReadAction + protected UsageInfo[] findUsages() { + List allUsages = new ArrayList<>(); + MultiMap conflicts = new MultiMap<>(); + for (PsiElement element : myElementsToMove) { + String newName = getNewQName(element); + if (newName == null) { + continue; + } + final UsageInfo[] usages = MoveClassesOrPackagesUtil.findUsages(element, mySearchInComments, mySearchInNonJavaFiles, newName); + allUsages.addAll(new ArrayList<>(Arrays.asList(usages))); + if (element instanceof PsiJavaPackage javaPackage) { + for (PsiDirectory directory : javaPackage.getDirectories()) { + final UsageInfo[] dirUsages = + MoveClassesOrPackagesUtil.findUsages(directory, mySearchInComments, mySearchInNonJavaFiles, newName); + allUsages.addAll(new ArrayList<>(Arrays.asList(dirUsages))); + } + } + } + myMoveDestination.analyzeModuleConflicts( + Arrays.asList(myElementsToMove), + conflicts, + allUsages.toArray(new UsageInfo[allUsages.size()]) + ); + final UsageInfo[] usageInfos = allUsages.toArray(new UsageInfo[allUsages.size()]); + detectPackageLocalsMoved(usageInfos, conflicts); + detectPackageLocalsUsed(conflicts); + if (!conflicts.isEmpty()) { + for (PsiElement element : conflicts.keySet()) { + allUsages.add(new ConflictsUsageInfo(element, conflicts.get(element))); + } + } - public ConflictsUsageInfo(PsiElement pseudoElement, Collection conflicts) { - super(pseudoElement); - myConflicts = conflicts; + return UsageViewUtil.removeDuplicatedUsages(allUsages.toArray(new UsageInfo[allUsages.size()])); } - public Collection getConflicts() { - return myConflicts; + public List getElements() { + return Collections.unmodifiableList(Arrays.asList(myElementsToMove)); } - } - - protected boolean preprocessUsages(Ref refUsages) { - final UsageInfo[] usages = refUsages.get(); - final MultiMap conflicts = new MultiMap<>(); - ArrayList filteredUsages = new ArrayList<>(); - for (UsageInfo usage : usages) { - if (usage instanceof ConflictsUsageInfo info) { - final PsiElement element = info.getElement(); - conflicts.putValues(element, info.getConflicts()); - } else { - filteredUsages.add(usage); - } + + public PackageWrapper getTargetPackage() { + return myMoveDestination.getTargetPackage(); } - refUsages.set(filteredUsages.toArray(new UsageInfo[filteredUsages.size()])); - return showConflicts(conflicts, usages); - } + protected static class ConflictsUsageInfo extends UsageInfo { + private final Collection myConflicts; - private boolean isInsideMoved(PsiElement place) { - for (PsiElement element : myElementsToMove) { - if (element instanceof PsiClass && PsiTreeUtil.isAncestor(element, place, false)) { - return true; - } + public ConflictsUsageInfo(PsiElement pseudoElement, Collection conflicts) { + super(pseudoElement); + myConflicts = conflicts; + } + + public Collection getConflicts() { + return myConflicts; + } } - return false; - } - private void detectPackageLocalsUsed(final MultiMap conflicts) { - PackageLocalsUsageCollector visitor = new PackageLocalsUsageCollector(myElementsToMove, myTargetPackage, conflicts); + protected boolean preprocessUsages(Ref refUsages) { + final UsageInfo[] usages = refUsages.get(); + final MultiMap conflicts = new MultiMap<>(); + ArrayList filteredUsages = new ArrayList<>(); + for (UsageInfo usage : usages) { + if (usage instanceof ConflictsUsageInfo info) { + final PsiElement element = info.getElement(); + conflicts.putValues(element, info.getConflicts()); + } + else { + filteredUsages.add(usage); + } + } - for (PsiElement element : myElementsToMove) { - if (element instanceof PsiClass aClass) { - aClass.accept(visitor); - } + refUsages.set(filteredUsages.toArray(new UsageInfo[filteredUsages.size()])); + return showConflicts(conflicts, usages); } - } - @RequiredReadAction - private void detectPackageLocalsMoved(final UsageInfo[] usages, final MultiMap conflicts) { -// final HashSet reportedPackageLocalUsed = new HashSet(); - final HashSet movedClasses = new HashSet<>(); - final HashMap> reportedClassToContainers = new HashMap<>(); - final PackageWrapper aPackage = myTargetPackage; - for (UsageInfo usage : usages) { - PsiElement element = usage.getElement(); - if (element == null) continue; - if (usage instanceof MoveRenameUsageInfo moveRenameUsageInfo && !(usage instanceof NonCodeUsageInfo) - && moveRenameUsageInfo.getReferencedElement() instanceof PsiClass aClass) { - if (!movedClasses.contains(aClass)) { - movedClasses.add(aClass); - } - String visibility = VisibilityUtil.getVisibilityModifier(aClass.getModifierList()); - if (PsiModifier.PACKAGE_LOCAL.equals(visibility)) { - if (PsiTreeUtil.getParentOfType(element, PsiImportStatement.class) != null) continue; - PsiElement container = ConflictsUtil.getContainer(element); - HashSet reported = reportedClassToContainers.get(aClass); - if (reported == null) { - reported = new HashSet<>(); - reportedClassToContainers.put(aClass, reported); - } - - if (!reported.contains(container)) { - reported.add(container); - PsiFile containingFile = element.getContainingFile(); - if (containingFile != null && !isInsideMoved(element)) { - PsiDirectory directory = containingFile.getContainingDirectory(); - if (directory != null) { - PsiJavaPackage usagePackage = JavaDirectoryService.getInstance().getPackage(directory); - if (aPackage != null && usagePackage != null && !aPackage.equalToPackage(usagePackage)) { - - final LocalizeValue message = RefactoringLocalize.aPackageLocalClass0WillNoLongerBeAccessibleFrom1( - CommonRefactoringUtil.htmlEmphasize(aClass.getName()), - RefactoringUIUtil.getDescription(container, true) - ); - conflicts.putValue(aClass, message.get()); - } - } + private boolean isInsideMoved(PsiElement place) { + for (PsiElement element : myElementsToMove) { + if (element instanceof PsiClass && PsiTreeUtil.isAncestor(element, place, false)) { + return true; } - } } - } + return false; } - final MyClassInstanceReferenceVisitor instanceReferenceVisitor = new MyClassInstanceReferenceVisitor(conflicts); - for (final PsiClass aClass : movedClasses) { - String visibility = VisibilityUtil.getVisibilityModifier(aClass.getModifierList()); - if (PsiModifier.PACKAGE_LOCAL.equals(visibility)) { - findInstancesOfPackageLocal(aClass, usages, instanceReferenceVisitor); - } else { - // public classes - findPublicClassConflicts(aClass, instanceReferenceVisitor); - } + private void detectPackageLocalsUsed(final MultiMap conflicts) { + PackageLocalsUsageCollector visitor = new PackageLocalsUsageCollector(myElementsToMove, myTargetPackage, conflicts); + + for (PsiElement element : myElementsToMove) { + if (element instanceof PsiClass aClass) { + aClass.accept(visitor); + } + } } - } - static class ClassMemberWrapper { - final PsiNamedElement myElement; - final PsiModifierListOwner myMember; + @RequiredReadAction + private void detectPackageLocalsMoved(final UsageInfo[] usages, final MultiMap conflicts) { +// final HashSet reportedPackageLocalUsed = new HashSet(); + final HashSet movedClasses = new HashSet<>(); + final HashMap> reportedClassToContainers = new HashMap<>(); + final PackageWrapper aPackage = myTargetPackage; + for (UsageInfo usage : usages) { + PsiElement element = usage.getElement(); + if (element == null) { + continue; + } + if (usage instanceof MoveRenameUsageInfo moveRenameUsageInfo && !(usage instanceof NonCodeUsageInfo) + && moveRenameUsageInfo.getReferencedElement() instanceof PsiClass aClass) { + if (!movedClasses.contains(aClass)) { + movedClasses.add(aClass); + } + String visibility = VisibilityUtil.getVisibilityModifier(aClass.getModifierList()); + if (PsiModifier.PACKAGE_LOCAL.equals(visibility)) { + if (PsiTreeUtil.getParentOfType(element, PsiImportStatement.class) != null) { + continue; + } + PsiElement container = ConflictsUtil.getContainer(element); + HashSet reported = reportedClassToContainers.get(aClass); + if (reported == null) { + reported = new HashSet<>(); + reportedClassToContainers.put(aClass, reported); + } + + if (!reported.contains(container)) { + reported.add(container); + PsiFile containingFile = element.getContainingFile(); + if (containingFile != null && !isInsideMoved(element)) { + PsiDirectory directory = containingFile.getContainingDirectory(); + if (directory != null) { + PsiJavaPackage usagePackage = JavaDirectoryService.getInstance().getPackage(directory); + if (aPackage != null && usagePackage != null && !aPackage.equalToPackage(usagePackage)) { + + final LocalizeValue message = RefactoringLocalize.aPackageLocalClass0WillNoLongerBeAccessibleFrom1( + CommonRefactoringUtil.htmlEmphasize(aClass.getName()), + RefactoringUIUtil.getDescription(container, true) + ); + conflicts.putValue(aClass, message.get()); + } + } + } + } + } + } + } - public ClassMemberWrapper(PsiNamedElement element) { - myElement = element; - myMember = (PsiModifierListOwner) element; + final MyClassInstanceReferenceVisitor instanceReferenceVisitor = new MyClassInstanceReferenceVisitor(conflicts); + for (final PsiClass aClass : movedClasses) { + String visibility = VisibilityUtil.getVisibilityModifier(aClass.getModifierList()); + if (PsiModifier.PACKAGE_LOCAL.equals(visibility)) { + findInstancesOfPackageLocal(aClass, usages, instanceReferenceVisitor); + } + else { + // public classes + findPublicClassConflicts(aClass, instanceReferenceVisitor); + } + } } - PsiModifierListOwner getMember() { - return myMember; - } + static class ClassMemberWrapper { + final PsiNamedElement myElement; + final PsiModifierListOwner myMember; - @RequiredReadAction - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof ClassMemberWrapper)) return false; + public ClassMemberWrapper(PsiNamedElement element) { + myElement = element; + myMember = (PsiModifierListOwner)element; + } - ClassMemberWrapper wrapper = (ClassMemberWrapper) o; + PsiModifierListOwner getMember() { + return myMember; + } - if (myElement instanceof PsiMethod thisMethod) { - return wrapper.myElement instanceof PsiMethod thatMethod && MethodSignatureUtil.areSignaturesEqual(thisMethod, thatMethod); - } + @RequiredReadAction + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof ClassMemberWrapper)) { + return false; + } - return Comparing.equal(myElement.getName(), wrapper.myElement.getName()); - } + ClassMemberWrapper wrapper = (ClassMemberWrapper)o; - @RequiredReadAction - public int hashCode() { - final String name = myElement.getName(); - if (name != null) { - return name.hashCode(); - } else { - return 0; - } - } - } - - private static void findPublicClassConflicts(PsiClass aClass, final MyClassInstanceReferenceVisitor instanceReferenceVisitor) { - //noinspection MismatchedQueryAndUpdateOfCollection - NonPublicClassMemberWrappersSet members = new NonPublicClassMemberWrappersSet(); - - members.addElements(aClass.getFields()); - members.addElements(aClass.getMethods()); - members.addElements(aClass.getInnerClasses()); - - final RefactoringUtil.IsDescendantOf isDescendantOf = new RefactoringUtil.IsDescendantOf(aClass); - final PsiJavaPackage aPackage = JavaDirectoryService.getInstance().getPackage(aClass.getContainingFile().getContainingDirectory()); - final GlobalSearchScope packageScope = aPackage == null ? aClass.getResolveScope() : PackageScope.packageScopeWithoutLibraries(aPackage, false); - for (final ClassMemberWrapper memberWrapper : members) { - ReferencesSearch.search(memberWrapper.getMember(), packageScope, false).forEach(reference -> { - final PsiElement element = reference.getElement(); - if (element instanceof PsiReferenceExpression expression) { - final PsiExpression qualifierExpression = expression.getQualifierExpression(); - if (qualifierExpression != null) { - final PsiType type = qualifierExpression.getType(); - if (type != null) { - final PsiClass resolvedTypeClass = PsiUtil.resolveClassInType(type); - if (isDescendantOf.value(resolvedTypeClass)) { - instanceReferenceVisitor.visitMemberReference(memberWrapper.getMember(), expression, isDescendantOf); - } - } - } else { - instanceReferenceVisitor.visitMemberReference(memberWrapper.getMember(), expression, isDescendantOf); - } + if (myElement instanceof PsiMethod thisMethod) { + return wrapper.myElement instanceof PsiMethod thatMethod && MethodSignatureUtil.areSignaturesEqual(thisMethod, thatMethod); + } + + return Comparing.equal(myElement.getName(), wrapper.myElement.getName()); + } + + @RequiredReadAction + public int hashCode() { + final String name = myElement.getName(); + if (name != null) { + return name.hashCode(); + } + else { + return 0; + } } - return true; - }); - } - } - - private static void findInstancesOfPackageLocal( - final PsiClass aClass, - final UsageInfo[] usages, - final MyClassInstanceReferenceVisitor instanceReferenceVisitor - ) { - ClassReferenceScanner referenceScanner = new ClassReferenceScanner(aClass) { - public PsiReference[] findReferences() { - ArrayList result = new ArrayList<>(); - for (UsageInfo usage : usages) { - if (usage instanceof MoveRenameUsageInfo moveRenameUsageInfo && moveRenameUsageInfo.getReferencedElement() == aClass) { - final PsiReference reference = usage.getReference(); - if (reference != null) { - result.add(reference); - } - } - } - return result.toArray(new PsiReference[result.size()]); - } - }; - referenceScanner.processReferences(new ClassInstanceScanner(aClass, instanceReferenceVisitor)); - } - - - @Nullable - @RequiredReadAction - private String getNewQName(PsiElement element) { - final String qualifiedName = myTargetPackage.getQualifiedName(); - final String newQName; - final String oldQName; - if (element instanceof PsiClass psiClass) { - newQName = StringUtil.getQualifiedName(qualifiedName, psiClass.getName()); - oldQName = psiClass.getQualifiedName(); - } else if (element instanceof PsiJavaPackage javaPackage) { - newQName = StringUtil.getQualifiedName(qualifiedName, javaPackage.getName()); - oldQName = javaPackage.getQualifiedName(); - } else { - LOG.assertTrue(false); - newQName = null; - oldQName = null; } - if (Comparing.strEqual(newQName, oldQName)) return null; - return newQName; - } - - protected void refreshElements(PsiElement[] elements) { - LOG.assertTrue(elements.length == myElementsToMove.length); - System.arraycopy(elements, 0, myElementsToMove, 0, elements.length); - } - - protected boolean isPreviewUsages(@Nonnull UsageInfo[] usages) { - if (UsageViewUtil.hasNonCodeUsages(usages)) { - WindowManager.getInstance().getStatusBar(myProject) - .setInfo(RefactoringLocalize.occurrencesFoundInCommentsStringsAndNonJavaFiles().get()); - return true; - } else { - return super.isPreviewUsages(usages); + + private static void findPublicClassConflicts(PsiClass aClass, final MyClassInstanceReferenceVisitor instanceReferenceVisitor) { + //noinspection MismatchedQueryAndUpdateOfCollection + NonPublicClassMemberWrappersSet members = new NonPublicClassMemberWrappersSet(); + + members.addElements(aClass.getFields()); + members.addElements(aClass.getMethods()); + members.addElements(aClass.getInnerClasses()); + + final RefactoringUtil.IsDescendantOf isDescendantOf = new RefactoringUtil.IsDescendantOf(aClass); + final PsiJavaPackage aPackage = JavaDirectoryService.getInstance().getPackage(aClass.getContainingFile().getContainingDirectory()); + final GlobalSearchScope packageScope = + aPackage == null ? aClass.getResolveScope() : PackageScope.packageScopeWithoutLibraries(aPackage, false); + for (final ClassMemberWrapper memberWrapper : members) { + ReferencesSearch.search(memberWrapper.getMember(), packageScope, false).forEach(reference -> { + final PsiElement element = reference.getElement(); + if (element instanceof PsiReferenceExpression expression) { + final PsiExpression qualifierExpression = expression.getQualifierExpression(); + if (qualifierExpression != null) { + final PsiType type = qualifierExpression.getType(); + if (type != null) { + final PsiClass resolvedTypeClass = PsiUtil.resolveClassInType(type); + if (isDescendantOf.value(resolvedTypeClass)) { + instanceReferenceVisitor.visitMemberReference(memberWrapper.getMember(), expression, isDescendantOf); + } + } + } + else { + instanceReferenceVisitor.visitMemberReference(memberWrapper.getMember(), expression, isDescendantOf); + } + } + return true; + }); + } } - } - protected void performRefactoring(@Nonnull UsageInfo[] usages) { - // If files are being moved then I need to collect some information to delete these - // filese from CVS. I need to know all common parents of the moved files and releative - // paths. + private static void findInstancesOfPackageLocal( + final PsiClass aClass, + final UsageInfo[] usages, + final MyClassInstanceReferenceVisitor instanceReferenceVisitor + ) { + ClassReferenceScanner referenceScanner = new ClassReferenceScanner(aClass) { + public PsiReference[] findReferences() { + ArrayList result = new ArrayList<>(); + for (UsageInfo usage : usages) { + if (usage instanceof MoveRenameUsageInfo moveRenameUsageInfo && moveRenameUsageInfo.getReferencedElement() == aClass) { + final PsiReference reference = usage.getReference(); + if (reference != null) { + result.add(reference); + } + } + } + return result.toArray(new PsiReference[result.size()]); + } + }; + referenceScanner.processReferences(new ClassInstanceScanner(aClass, instanceReferenceVisitor)); + } - // Move files with correction of references. - try { - final Map allClasses = new HashMap<>(); - for (PsiElement element : myElementsToMove) { - if (element instanceof PsiClass psiClass) { - if (allClasses.containsKey(psiClass)) { - continue; - } - for (MoveAllClassesInFileHandler fileHandler : MoveAllClassesInFileHandler.EP_NAME.getExtensionList()) { - fileHandler.processMoveAllClassesInFile(allClasses, psiClass, myElementsToMove); - } - } - } - final Map oldToNewElementsMapping = new HashMap<>(); - for (int idx = 0; idx < myElementsToMove.length; idx++) { - PsiElement element = myElementsToMove[idx]; - final RefactoringElementListener elementListener = getTransaction().getElementListener(element); - if (element instanceof PsiJavaPackage javaPackage) { - final PsiDirectory[] directories = javaPackage.getDirectories(); - final PsiJavaPackage newElement = MoveClassesOrPackagesUtil.doMovePackage(javaPackage, myMoveDestination); - LOG.assertTrue(newElement != null, element); - oldToNewElementsMapping.put(element, newElement); - int i = 0; - final PsiDirectory[] newDirectories = newElement.getDirectories(); - if (newDirectories.length == 1) {//everything is moved in one directory - for (PsiDirectory directory : directories) { - oldToNewElementsMapping.put(directory, newDirectories[0]); - } - } else { - for (PsiDirectory directory : directories) { - oldToNewElementsMapping.put(directory, newDirectories[i++]); - } - } - element = newElement; - } else if (element instanceof PsiClass psiClass) { - MoveClassesOrPackagesUtil.prepareMoveClass(psiClass); - final PsiClass newElement = MoveClassesOrPackagesUtil.doMoveClass(psiClass, - myMoveDestination.getTargetDirectory(element.getContainingFile()), - allClasses.get(psiClass) - ); - oldToNewElementsMapping.put(element, newElement); - element = newElement; - } else { - LOG.error("Unexpected element to move: " + element); - } - elementListener.elementMoved(element); - myElementsToMove[idx] = element; - } - - for (PsiElement element : myElementsToMove) { + @Nullable + @RequiredReadAction + private String getNewQName(PsiElement element) { + final String qualifiedName = myTargetPackage.getQualifiedName(); + final String newQName; + final String oldQName; if (element instanceof PsiClass psiClass) { - MoveClassesOrPackagesUtil.finishMoveClass(psiClass); + newQName = StringUtil.getQualifiedName(qualifiedName, psiClass.getName()); + oldQName = psiClass.getQualifiedName(); } - } - - myNonCodeUsages = CommonMoveUtil.retargetUsages(usages, oldToNewElementsMapping); - } catch (IncorrectOperationException e) { - myNonCodeUsages = new NonCodeUsageInfo[0]; - RefactoringUIUtil.processIncorrectOperation(myProject, e); + else if (element instanceof PsiJavaPackage javaPackage) { + newQName = StringUtil.getQualifiedName(qualifiedName, javaPackage.getName()); + oldQName = javaPackage.getQualifiedName(); + } + else { + LOG.assertTrue(false); + newQName = null; + oldQName = null; + } + if (Comparing.strEqual(newQName, oldQName)) { + return null; + } + return newQName; } - } - - protected void performPsiSpoilingRefactoring() { - RenameUtil.renameNonCodeUsages(myProject, myNonCodeUsages); - if (myMoveCallback != null) { - if (myMoveCallback instanceof MoveClassesOrPackagesCallback moveClassesOrPackagesCallback) { - moveClassesOrPackagesCallback.classesOrPackagesMoved(myMoveDestination); - } - myMoveCallback.refactoringCompleted(); + + protected void refreshElements(PsiElement[] elements) { + LOG.assertTrue(elements.length == myElementsToMove.length); + System.arraycopy(elements, 0, myElementsToMove, 0, elements.length); } - } - - @Nonnull - protected String getCommandName() { - String elements = RefactoringUIUtil.calculatePsiElementDescriptionList(myElementsToMove); - String target = myTargetPackage.getQualifiedName(); - return RefactoringLocalize.moveClassesCommand(elements, target).get(); - } - - private class MyClassInstanceReferenceVisitor implements ClassInstanceScanner.ClassInstanceReferenceVisitor { - private final MultiMap myConflicts; - private final HashMap> myReportedElementToContainer = new HashMap<>(); - private final HashMap myIsDescendantOfCache = new HashMap<>(); - - public MyClassInstanceReferenceVisitor(MultiMap conflicts) { - myConflicts = conflicts; + + protected boolean isPreviewUsages(@Nonnull UsageInfo[] usages) { + if (UsageViewUtil.hasNonCodeUsages(usages)) { + WindowManager.getInstance().getStatusBar(myProject) + .setInfo(RefactoringLocalize.occurrencesFoundInCommentsStringsAndNonJavaFiles().get()); + return true; + } + else { + return super.isPreviewUsages(usages); + } } - @RequiredReadAction - public void visitQualifier( - PsiReferenceExpression qualified, - PsiExpression instanceRef, - PsiElement referencedInstance - ) { - PsiElement resolved = qualified.resolve(); + protected void performRefactoring(@Nonnull UsageInfo[] usages) { + // If files are being moved then I need to collect some information to delete these + // filese from CVS. I need to know all common parents of the moved files and releative + // paths. + + // Move files with correction of references. + + try { + final Map allClasses = new HashMap<>(); + for (PsiElement element : myElementsToMove) { + if (element instanceof PsiClass psiClass) { + if (allClasses.containsKey(psiClass)) { + continue; + } + for (MoveAllClassesInFileHandler fileHandler : MoveAllClassesInFileHandler.EP_NAME.getExtensionList()) { + fileHandler.processMoveAllClassesInFile(allClasses, psiClass, myElementsToMove); + } + } + } + final Map oldToNewElementsMapping = new HashMap<>(); + for (int idx = 0; idx < myElementsToMove.length; idx++) { + PsiElement element = myElementsToMove[idx]; + final RefactoringElementListener elementListener = getTransaction().getElementListener(element); + if (element instanceof PsiJavaPackage javaPackage) { + final PsiDirectory[] directories = javaPackage.getDirectories(); + final PsiJavaPackage newElement = MoveClassesOrPackagesUtil.doMovePackage(javaPackage, myMoveDestination); + LOG.assertTrue(newElement != null, element); + oldToNewElementsMapping.put(element, newElement); + int i = 0; + final PsiDirectory[] newDirectories = newElement.getDirectories(); + if (newDirectories.length == 1) {//everything is moved in one directory + for (PsiDirectory directory : directories) { + oldToNewElementsMapping.put(directory, newDirectories[0]); + } + } + else { + for (PsiDirectory directory : directories) { + oldToNewElementsMapping.put(directory, newDirectories[i++]); + } + } + element = newElement; + } + else if (element instanceof PsiClass psiClass) { + MoveClassesOrPackagesUtil.prepareMoveClass(psiClass); + final PsiClass newElement = MoveClassesOrPackagesUtil.doMoveClass( + psiClass, + myMoveDestination.getTargetDirectory(element.getContainingFile()), + allClasses.get(psiClass) + ); + oldToNewElementsMapping.put(element, newElement); + element = newElement; + } + else { + LOG.error("Unexpected element to move: " + element); + } + elementListener.elementMoved(element); + myElementsToMove[idx] = element; + } + + for (PsiElement element : myElementsToMove) { + if (element instanceof PsiClass psiClass) { + MoveClassesOrPackagesUtil.finishMoveClass(psiClass); + } + } - if (resolved instanceof PsiMember member) { - final PsiClass containingClass = member.getContainingClass(); - RefactoringUtil.IsDescendantOf isDescendantOf = myIsDescendantOfCache.get(containingClass); - if (isDescendantOf == null) { - isDescendantOf = new RefactoringUtil.IsDescendantOf(containingClass); - myIsDescendantOfCache.put(containingClass, isDescendantOf); + myNonCodeUsages = CommonMoveUtil.retargetUsages(usages, oldToNewElementsMapping); + } + catch (IncorrectOperationException e) { + myNonCodeUsages = new NonCodeUsageInfo[0]; + RefactoringUIUtil.processIncorrectOperation(myProject, e); } - visitMemberReference(member, qualified, isDescendantOf); - } } - private synchronized void visitMemberReference(final PsiModifierListOwner member, PsiReferenceExpression qualified, final RefactoringUtil.IsDescendantOf descendantOf) { - if (member.hasModifierProperty(PsiModifier.PACKAGE_LOCAL)) { - visitPackageLocalMemberReference(qualified, member); - } else if (member.hasModifierProperty(PsiModifier.PROTECTED)) { - final PsiExpression qualifier = qualified.getQualifierExpression(); - if (qualifier != null && !(qualifier instanceof PsiThisExpression) && !(qualifier instanceof PsiSuperExpression)) { - visitPackageLocalMemberReference(qualified, member); - } else { - if (!isInInheritor(qualified, descendantOf)) { - visitPackageLocalMemberReference(qualified, member); - } - } - } + protected void performPsiSpoilingRefactoring() { + RenameUtil.renameNonCodeUsages(myProject, myNonCodeUsages); + if (myMoveCallback != null) { + if (myMoveCallback instanceof MoveClassesOrPackagesCallback moveClassesOrPackagesCallback) { + moveClassesOrPackagesCallback.classesOrPackagesMoved(myMoveDestination); + } + myMoveCallback.refactoringCompleted(); + } } - private boolean isInInheritor(PsiReferenceExpression qualified, final RefactoringUtil.IsDescendantOf descendantOf) { - PsiClass aClass = PsiTreeUtil.getParentOfType(qualified, PsiClass.class); - while (aClass != null) { - if (descendantOf.value(aClass)) return true; - aClass = PsiTreeUtil.getParentOfType(aClass, PsiClass.class); - } - return false; + @Nonnull + protected String getCommandName() { + String elements = RefactoringUIUtil.calculatePsiElementDescriptionList(myElementsToMove); + String target = myTargetPackage.getQualifiedName(); + return RefactoringLocalize.moveClassesCommand(elements, target).get(); } - private void visitPackageLocalMemberReference(PsiJavaCodeReferenceElement qualified, PsiModifierListOwner member) { - PsiElement container = ConflictsUtil.getContainer(qualified); - HashSet reportedContainers = myReportedElementToContainer.get(member); - if (reportedContainers == null) { - reportedContainers = new HashSet<>(); - myReportedElementToContainer.put(member, reportedContainers); - } - - if (!reportedContainers.contains(container)) { - reportedContainers.add(container); - if (!isInsideMoved(container)) { - PsiFile containingFile = container.getContainingFile(); - if (containingFile != null) { - PsiDirectory directory = containingFile.getContainingDirectory(); - if (directory != null) { - PsiJavaPackage aPackage = JavaDirectoryService.getInstance().getPackage(directory); - if (!myTargetPackage.equalToPackage(aPackage)) { - LocalizeValue message = RefactoringLocalize.zeroWillBeInaccessibleFrom1( - RefactoringUIUtil.getDescription(member, true), - RefactoringUIUtil.getDescription(container, true) - ); - myConflicts.putValue(member, CommonRefactoringUtil.capitalize(message.get())); - } + private class MyClassInstanceReferenceVisitor implements ClassInstanceScanner.ClassInstanceReferenceVisitor { + private final MultiMap myConflicts; + private final HashMap> myReportedElementToContainer = new HashMap<>(); + private final HashMap myIsDescendantOfCache = new HashMap<>(); + + public MyClassInstanceReferenceVisitor(MultiMap conflicts) { + myConflicts = conflicts; + } + + @RequiredReadAction + public void visitQualifier( + PsiReferenceExpression qualified, + PsiExpression instanceRef, + PsiElement referencedInstance + ) { + PsiElement resolved = qualified.resolve(); + + if (resolved instanceof PsiMember member) { + final PsiClass containingClass = member.getContainingClass(); + RefactoringUtil.IsDescendantOf isDescendantOf = myIsDescendantOfCache.get(containingClass); + if (isDescendantOf == null) { + isDescendantOf = new RefactoringUtil.IsDescendantOf(containingClass); + myIsDescendantOfCache.put(containingClass, isDescendantOf); + } + visitMemberReference(member, qualified, isDescendantOf); } - } } - } - } - public void visitTypeCast(PsiTypeCastExpression typeCastExpression, - PsiExpression instanceRef, - PsiElement referencedInstance) { - } + private synchronized void visitMemberReference( + final PsiModifierListOwner member, + PsiReferenceExpression qualified, + final RefactoringUtil.IsDescendantOf descendantOf + ) { + if (member.hasModifierProperty(PsiModifier.PACKAGE_LOCAL)) { + visitPackageLocalMemberReference(qualified, member); + } + else if (member.hasModifierProperty(PsiModifier.PROTECTED)) { + final PsiExpression qualifier = qualified.getQualifierExpression(); + if (qualifier != null && !(qualifier instanceof PsiThisExpression) && !(qualifier instanceof PsiSuperExpression)) { + visitPackageLocalMemberReference(qualified, member); + } + else if (!isInInheritor(qualified, descendantOf)) { + visitPackageLocalMemberReference(qualified, member); + } + } + } - public void visitReadUsage(PsiExpression instanceRef, PsiType expectedType, PsiElement referencedInstance) { - } + private boolean isInInheritor(PsiReferenceExpression qualified, final RefactoringUtil.IsDescendantOf descendantOf) { + PsiClass aClass = PsiTreeUtil.getParentOfType(qualified, PsiClass.class); + while (aClass != null) { + if (descendantOf.value(aClass)) { + return true; + } + aClass = PsiTreeUtil.getParentOfType(aClass, PsiClass.class); + } + return false; + } - public void visitWriteUsage(PsiExpression instanceRef, PsiType assignedType, PsiElement referencedInstance) { - } - } - - private static class NonPublicClassMemberWrappersSet extends HashSet { - public void addElement(PsiMember member) { - final PsiNamedElement namedElement = (PsiNamedElement) member; - if (member.hasModifierProperty(PsiModifier.PUBLIC)) return; - if (member.hasModifierProperty(PsiModifier.PRIVATE)) return; - add(new ClassMemberWrapper(namedElement)); + private void visitPackageLocalMemberReference(PsiJavaCodeReferenceElement qualified, PsiModifierListOwner member) { + PsiElement container = ConflictsUtil.getContainer(qualified); + HashSet reportedContainers = myReportedElementToContainer.get(member); + if (reportedContainers == null) { + reportedContainers = new HashSet<>(); + myReportedElementToContainer.put(member, reportedContainers); + } + + if (!reportedContainers.contains(container)) { + reportedContainers.add(container); + if (!isInsideMoved(container)) { + PsiFile containingFile = container.getContainingFile(); + if (containingFile != null) { + PsiDirectory directory = containingFile.getContainingDirectory(); + if (directory != null) { + PsiJavaPackage aPackage = JavaDirectoryService.getInstance().getPackage(directory); + if (!myTargetPackage.equalToPackage(aPackage)) { + LocalizeValue message = RefactoringLocalize.zeroWillBeInaccessibleFrom1( + RefactoringUIUtil.getDescription(member, true), + RefactoringUIUtil.getDescription(container, true) + ); + myConflicts.putValue(member, CommonRefactoringUtil.capitalize(message.get())); + } + } + } + } + } + } + + public void visitTypeCast(PsiTypeCastExpression typeCastExpression, PsiExpression instanceRef, PsiElement referencedInstance) { + } + + public void visitReadUsage(PsiExpression instanceRef, PsiType expectedType, PsiElement referencedInstance) { + } + + public void visitWriteUsage(PsiExpression instanceRef, PsiType assignedType, PsiElement referencedInstance) { + } } - public void addElements(PsiMember[] members) { - for (PsiMember member : members) { - addElement(member); - } + private static class NonPublicClassMemberWrappersSet extends HashSet { + public void addElement(PsiMember member) { + final PsiNamedElement namedElement = (PsiNamedElement)member; + if (member.hasModifierProperty(PsiModifier.PUBLIC)) { + return; + } + if (member.hasModifierProperty(PsiModifier.PRIVATE)) { + return; + } + add(new ClassMemberWrapper(namedElement)); + } + + public void addElements(PsiMember[] members) { + for (PsiMember member : members) { + addElement(member); + } + } } - } } diff --git a/plugin/src/main/java/com/intellij/java/impl/refactoring/move/moveClassesOrPackages/MoveClassesOrPackagesUtil.java b/plugin/src/main/java/com/intellij/java/impl/refactoring/move/moveClassesOrPackages/MoveClassesOrPackagesUtil.java index c03db85bb0..304ba939a5 100644 --- a/plugin/src/main/java/com/intellij/java/impl/refactoring/move/moveClassesOrPackages/MoveClassesOrPackagesUtil.java +++ b/plugin/src/main/java/com/intellij/java/impl/refactoring/move/moveClassesOrPackages/MoveClassesOrPackagesUtil.java @@ -40,337 +40,371 @@ import consulo.virtualFileSystem.VirtualFile; import jakarta.annotation.Nullable; + import java.io.File; import java.util.*; public class MoveClassesOrPackagesUtil { - private static final Logger LOG = Logger.getInstance( - MoveClassesOrPackagesUtil.class); - - private MoveClassesOrPackagesUtil() { - } - - public static UsageInfo[] findUsages(final PsiElement element, - boolean searchInStringsAndComments, - boolean searchInNonJavaFiles, - final String newQName) { - PsiManager manager = element.getManager(); - - ArrayList results = new ArrayList(); - Set foundReferences = new HashSet(); - - GlobalSearchScope projectScope = GlobalSearchScope.projectScope(manager.getProject()); - for (PsiReference reference : ReferencesSearch.search(element, projectScope, false)) { - TextRange range = reference.getRangeInElement(); - if (foundReferences.contains(reference)) continue; - results.add(new MoveRenameUsageInfo(reference.getElement(), reference, range.getStartOffset(), range.getEndOffset(), element, false)); - foundReferences.add(reference); + private static final Logger LOG = Logger.getInstance(MoveClassesOrPackagesUtil.class); + + private MoveClassesOrPackagesUtil() { } - findNonCodeUsages(searchInStringsAndComments, searchInNonJavaFiles, element, newQName, results); - preprocessUsages(results); - return results.toArray(new UsageInfo[results.size()]); - } + public static UsageInfo[] findUsages( + final PsiElement element, + boolean searchInStringsAndComments, + boolean searchInNonJavaFiles, + final String newQName + ) { + PsiManager manager = element.getManager(); + + ArrayList results = new ArrayList(); + Set foundReferences = new HashSet(); + + GlobalSearchScope projectScope = GlobalSearchScope.projectScope(manager.getProject()); + for (PsiReference reference : ReferencesSearch.search(element, projectScope, false)) { + TextRange range = reference.getRangeInElement(); + if (foundReferences.contains(reference)) { + continue; + } + results.add(new MoveRenameUsageInfo( + reference.getElement(), + reference, + range.getStartOffset(), + range.getEndOffset(), + element, + false + )); + foundReferences.add(reference); + } - private static void preprocessUsages(ArrayList results) { - for (MoveClassHandler handler : MoveClassHandler.EP_NAME.getExtensionList()) { - handler.preprocessUsages(results); - } - } - - public static void findNonCodeUsages(boolean searchInStringsAndComments, - boolean searchInNonJavaFiles, - final PsiElement element, - final String newQName, - ArrayList results) { - final String stringToSearch = getStringToSearch(element); - if (stringToSearch == null) return; - TextOccurrencesUtil.findNonCodeUsages(element, stringToSearch, searchInStringsAndComments, searchInNonJavaFiles, newQName, results); - } - - private static String getStringToSearch(PsiElement element) { - if (element instanceof PsiJavaPackage) { - return ((PsiJavaPackage)element).getQualifiedName(); + findNonCodeUsages(searchInStringsAndComments, searchInNonJavaFiles, element, newQName, results); + preprocessUsages(results); + return results.toArray(new UsageInfo[results.size()]); } - else if (element instanceof PsiClass) { - return ((PsiClass)element).getQualifiedName(); + + private static void preprocessUsages(ArrayList results) { + for (MoveClassHandler handler : MoveClassHandler.EP_NAME.getExtensionList()) { + handler.preprocessUsages(results); + } } - else if (element instanceof PsiDirectory) { - return getStringToSearch(JavaDirectoryService.getInstance().getPackage((PsiDirectory)element)); + + public static void findNonCodeUsages( + boolean searchInStringsAndComments, + boolean searchInNonJavaFiles, + final PsiElement element, + final String newQName, + ArrayList results + ) { + final String stringToSearch = getStringToSearch(element); + if (stringToSearch == null) { + return; + } + TextOccurrencesUtil.findNonCodeUsages(element, stringToSearch, searchInStringsAndComments, searchInNonJavaFiles, newQName, results); } - else { - LOG.error("Unknown element type"); - return null; + + private static String getStringToSearch(PsiElement element) { + if (element instanceof PsiJavaPackage) { + return ((PsiJavaPackage)element).getQualifiedName(); + } + else if (element instanceof PsiClass) { + return ((PsiClass)element).getQualifiedName(); + } + else if (element instanceof PsiDirectory) { + return getStringToSearch(JavaDirectoryService.getInstance().getPackage((PsiDirectory)element)); + } + else { + LOG.error("Unknown element type"); + return null; + } } - } - // Does not process non-code usages! - public static PsiJavaPackage doMovePackage(PsiJavaPackage aPackage, MoveDestination moveDestination) - throws IncorrectOperationException { - final PackageWrapper targetPackage = moveDestination.getTargetPackage(); + // Does not process non-code usages! + public static PsiJavaPackage doMovePackage(PsiJavaPackage aPackage, MoveDestination moveDestination) + throws IncorrectOperationException { + final PackageWrapper targetPackage = moveDestination.getTargetPackage(); - final String newPrefix; - if ("".equals(targetPackage.getQualifiedName())) { - newPrefix = ""; - } - else { - newPrefix = targetPackage.getQualifiedName() + "."; - } + final String newPrefix; + if ("".equals(targetPackage.getQualifiedName())) { + newPrefix = ""; + } + else { + newPrefix = targetPackage.getQualifiedName() + "."; + } - final String newPackageQualifiedName = newPrefix + aPackage.getName(); + final String newPackageQualifiedName = newPrefix + aPackage.getName(); - // do actual move - final GlobalSearchScope projectScope = GlobalSearchScope.projectScope(aPackage.getProject()); - PsiDirectory[] dirs = aPackage.getDirectories(projectScope); - for (PsiDirectory dir : dirs) { - final PsiDirectory targetDirectory = moveDestination.getTargetDirectory(dir); - if (targetDirectory != null) { - moveDirectoryRecursively(dir, targetDirectory); - } - } + // do actual move + final GlobalSearchScope projectScope = GlobalSearchScope.projectScope(aPackage.getProject()); + PsiDirectory[] dirs = aPackage.getDirectories(projectScope); + for (PsiDirectory dir : dirs) { + final PsiDirectory targetDirectory = moveDestination.getTargetDirectory(dir); + if (targetDirectory != null) { + moveDirectoryRecursively(dir, targetDirectory); + } + } - aPackage.handleQualifiedNameChange(newPackageQualifiedName); - - return JavaPsiFacade.getInstance(targetPackage.getManager().getProject()).findPackage(newPackageQualifiedName); - } - - public static void moveDirectoryRecursively(PsiDirectory dir, PsiDirectory destination) - throws IncorrectOperationException { - if ( dir.getParentDirectory() == destination ) return; - moveDirectoryRecursively(dir, destination, new HashSet()); - } - - private static void moveDirectoryRecursively(PsiDirectory dir, PsiDirectory destination, HashSet movedPaths) - throws IncorrectOperationException { - final PsiManager manager = dir.getManager(); - final VirtualFile destVFile = destination.getVirtualFile(); - final VirtualFile sourceVFile = dir.getVirtualFile(); - if (movedPaths.contains(sourceVFile)) return; - String targetName = dir.getName(); - final PsiJavaPackage aPackage = JavaDirectoryService.getInstance().getPackage(dir); - if (aPackage != null) { - final String sourcePackageName = aPackage.getName(); - if (!sourcePackageName.equals(targetName)) { - targetName = sourcePackageName; - } - } - final PsiDirectory subdirectoryInDest; - final boolean isSourceRoot = RefactoringUtil.isSourceRoot(dir); - if (VfsUtil.isAncestor(sourceVFile, destVFile, false) || isSourceRoot) { - PsiDirectory exitsingSubdir = destination.findSubdirectory(targetName); - if (exitsingSubdir == null) { - subdirectoryInDest = destination.createSubdirectory(targetName); - movedPaths.add(subdirectoryInDest.getVirtualFile()); - } else { - subdirectoryInDest = exitsingSubdir; - } - } else { - subdirectoryInDest = destination.findSubdirectory(targetName); + aPackage.handleQualifiedNameChange(newPackageQualifiedName); + + return JavaPsiFacade.getInstance(targetPackage.getManager().getProject()).findPackage(newPackageQualifiedName); } - if (subdirectoryInDest == null) { - VirtualFile virtualFile = dir.getVirtualFile(); - MoveFilesOrDirectoriesUtil.doMoveDirectory(dir, destination); - movedPaths.add(virtualFile); + public static void moveDirectoryRecursively(PsiDirectory dir, PsiDirectory destination) + throws IncorrectOperationException { + if (dir.getParentDirectory() == destination) { + return; + } + moveDirectoryRecursively(dir, destination, new HashSet()); } - else { - final PsiFile[] files = dir.getFiles(); - for (PsiFile file : files) { - try { - subdirectoryInDest.checkAdd(file); + + private static void moveDirectoryRecursively(PsiDirectory dir, PsiDirectory destination, HashSet movedPaths) + throws IncorrectOperationException { + final PsiManager manager = dir.getManager(); + final VirtualFile destVFile = destination.getVirtualFile(); + final VirtualFile sourceVFile = dir.getVirtualFile(); + if (movedPaths.contains(sourceVFile)) { + return; + } + String targetName = dir.getName(); + final PsiJavaPackage aPackage = JavaDirectoryService.getInstance().getPackage(dir); + if (aPackage != null) { + final String sourcePackageName = aPackage.getName(); + if (!sourcePackageName.equals(targetName)) { + targetName = sourcePackageName; + } + } + final PsiDirectory subdirectoryInDest; + final boolean isSourceRoot = RefactoringUtil.isSourceRoot(dir); + if (VfsUtil.isAncestor(sourceVFile, destVFile, false) || isSourceRoot) { + PsiDirectory exitsingSubdir = destination.findSubdirectory(targetName); + if (exitsingSubdir == null) { + subdirectoryInDest = destination.createSubdirectory(targetName); + movedPaths.add(subdirectoryInDest.getVirtualFile()); + } + else { + subdirectoryInDest = exitsingSubdir; + } } - catch (IncorrectOperationException e) { - continue; + else { + subdirectoryInDest = destination.findSubdirectory(targetName); } - MoveFilesOrDirectoriesUtil.doMoveFile(file, subdirectoryInDest); - } - final PsiDirectory[] subdirectories = dir.getSubdirectories(); - for (PsiDirectory subdirectory : subdirectories) { - if (!subdirectory.equals(subdirectoryInDest)) { - moveDirectoryRecursively(subdirectory, subdirectoryInDest, movedPaths); + if (subdirectoryInDest == null) { + VirtualFile virtualFile = dir.getVirtualFile(); + MoveFilesOrDirectoriesUtil.doMoveDirectory(dir, destination); + movedPaths.add(virtualFile); + } + else { + final PsiFile[] files = dir.getFiles(); + for (PsiFile file : files) { + try { + subdirectoryInDest.checkAdd(file); + } + catch (IncorrectOperationException e) { + continue; + } + MoveFilesOrDirectoriesUtil.doMoveFile(file, subdirectoryInDest); + } + + final PsiDirectory[] subdirectories = dir.getSubdirectories(); + for (PsiDirectory subdirectory : subdirectories) { + if (!subdirectory.equals(subdirectoryInDest)) { + moveDirectoryRecursively(subdirectory, subdirectoryInDest, movedPaths); + } + } + if (!isSourceRoot && dir.getFiles().length == 0 && dir.getSubdirectories().length == 0) { + dir.delete(); + } } - } - if (!isSourceRoot && dir.getFiles().length == 0 && dir.getSubdirectories().length == 0) { - dir.delete(); - } } - } - public static void prepareMoveClass(PsiClass aClass) { - for (MoveClassHandler handler : MoveClassHandler.EP_NAME.getExtensionList()) { - handler.prepareMove(aClass); + public static void prepareMoveClass(PsiClass aClass) { + for (MoveClassHandler handler : MoveClassHandler.EP_NAME.getExtensionList()) { + handler.prepareMove(aClass); + } } - } - public static void finishMoveClass(PsiClass aClass) { - for (MoveClassHandler handler : MoveClassHandler.EP_NAME.getExtensionList()) { - handler.finishMoveClass(aClass); - } - } - - // Does not process non-code usages! - public static PsiClass doMoveClass(PsiClass aClass, PsiDirectory moveDestination) throws IncorrectOperationException { - return doMoveClass(aClass, moveDestination, true); - } - - // Does not process non-code usages! - public static PsiClass doMoveClass(PsiClass aClass, PsiDirectory moveDestination, boolean moveAllClassesInFile) throws IncorrectOperationException { - PsiClass newClass; - if (!moveAllClassesInFile) { - for (MoveClassHandler handler : MoveClassHandler.EP_NAME.getExtensionList()) { - newClass = handler.doMoveClass(aClass, moveDestination); - if (newClass != null) return newClass; - } + public static void finishMoveClass(PsiClass aClass) { + for (MoveClassHandler handler : MoveClassHandler.EP_NAME.getExtensionList()) { + handler.finishMoveClass(aClass); + } } - PsiFile file = aClass.getContainingFile(); - final PsiJavaPackage newPackage = JavaDirectoryService.getInstance().getPackage(moveDestination); - - newClass = aClass; - if (!moveDestination.equals(file.getContainingDirectory())) { - LOG.assertTrue(file.getVirtualFile() != null, aClass); - MoveFilesOrDirectoriesUtil.doMoveFile(file, moveDestination); - if (file instanceof PsiClassOwner && newPackage != null /*&& !JspPsiUtil.isInJspFile(file)*/) { - // Do not rely on class instance identity retention after setPackageName (Scala) - String aClassName = aClass.getName(); - ((PsiClassOwner)file).setPackageName(newPackage.getQualifiedName()); - newClass = findClassByName((PsiClassOwner)file, aClassName); - LOG.assertTrue(newClass != null); - } - } - return newClass; - } - - @Nullable - private static PsiClass findClassByName(PsiClassOwner file, String name) { - PsiClass[] classes = file.getClasses(); - for (PsiClass aClass : classes) { - if (name.equals(aClass.getName())) { - return aClass; - } + // Does not process non-code usages! + public static PsiClass doMoveClass(PsiClass aClass, PsiDirectory moveDestination) throws IncorrectOperationException { + return doMoveClass(aClass, moveDestination, true); } - return null; - } - public static String getPackageName(PackageWrapper aPackage) { - if (aPackage == null) { - return null; - } - String name = aPackage.getQualifiedName(); - if (name.length() > 0) { - return name; - } - else { - return UsageViewBundle.message("default.package.presentable.name"); - } - } - - @Nullable - public static PsiDirectory chooseDestinationPackage(Project project, String packageName, @Nullable PsiDirectory baseDir) { - final PsiManager psiManager = PsiManager.getInstance(project); - final PackageWrapper packageWrapper = new PackageWrapper(psiManager, packageName); - final PsiJavaPackage aPackage = JavaPsiFacade.getInstance(project).findPackage(packageName); - PsiDirectory directory; - - PsiDirectory[] directories = aPackage != null ? aPackage.getDirectories() : null; - final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex(); - final boolean filterOutSources = baseDir != null && fileIndex.isInTestSourceContent(baseDir.getVirtualFile()); - if (directories != null && directories.length == 1 && !(filterOutSources && - !fileIndex.isInTestSourceContent(directories[0].getVirtualFile()))) { - directory = directories[0]; + // Does not process non-code usages! + public static PsiClass doMoveClass(PsiClass aClass, PsiDirectory moveDestination, boolean moveAllClassesInFile) + throws IncorrectOperationException { + PsiClass newClass; + if (!moveAllClassesInFile) { + for (MoveClassHandler handler : MoveClassHandler.EP_NAME.getExtensionList()) { + newClass = handler.doMoveClass(aClass, moveDestination); + if (newClass != null) { + return newClass; + } + } + } + + PsiFile file = aClass.getContainingFile(); + final PsiJavaPackage newPackage = JavaDirectoryService.getInstance().getPackage(moveDestination); + + newClass = aClass; + if (!moveDestination.equals(file.getContainingDirectory())) { + LOG.assertTrue(file.getVirtualFile() != null, aClass); + MoveFilesOrDirectoriesUtil.doMoveFile(file, moveDestination); + if (file instanceof PsiClassOwner && newPackage != null /*&& !JspPsiUtil.isInJspFile(file)*/) { + // Do not rely on class instance identity retention after setPackageName (Scala) + String aClassName = aClass.getName(); + ((PsiClassOwner)file).setPackageName(newPackage.getQualifiedName()); + newClass = findClassByName((PsiClassOwner)file, aClassName); + LOG.assertTrue(newClass != null); + } + } + return newClass; } - else { - final VirtualFile[] contentSourceRoots = ProjectRootManager.getInstance(project).getContentSourceRoots(); - if (contentSourceRoots.length == 1 && !(filterOutSources && !fileIndex.isInTestSourceContent(contentSourceRoots[0]))) { - directory = ApplicationManager.getApplication().runWriteAction(new Computable() { - @Override - public PsiDirectory compute() { - return RefactoringUtil.createPackageDirectoryInSourceRoot(packageWrapper, contentSourceRoots[0]); - } - }); - } - else { - final VirtualFile sourceRootForFile = chooseSourceRoot(packageWrapper, contentSourceRoots, baseDir); - if (sourceRootForFile == null) return null; - directory = ApplicationManager.getApplication().runWriteAction(new Computable() { - @Override - public PsiDirectory compute() { - return new AutocreatingSingleSourceRootMoveDestination(packageWrapper, sourceRootForFile).getTargetDirectory((PsiDirectory)null); - } - }); - } + + @Nullable + private static PsiClass findClassByName(PsiClassOwner file, String name) { + PsiClass[] classes = file.getClasses(); + for (PsiClass aClass : classes) { + if (name.equals(aClass.getName())) { + return aClass; + } + } + return null; } - return directory; - } - - public static VirtualFile chooseSourceRoot(final PackageWrapper targetPackage, - final VirtualFile[] contentSourceRoots, - final PsiDirectory initialDirectory) { - Project project = targetPackage.getManager().getProject(); - //ensure that there would be no duplicates: e.g. when one content root is subfolder of another root (configured via excluded roots) - LinkedHashSet targetDirectories = new LinkedHashSet(); - Map relativePathsToCreate = new HashMap(); - buildDirectoryList(targetPackage, contentSourceRoots, targetDirectories, relativePathsToCreate); - - final PsiDirectory selectedDirectory = DirectoryChooserUtil.chooseDirectory( - targetDirectories.toArray(new PsiDirectory[targetDirectories.size()]), - initialDirectory, - project, - relativePathsToCreate - ); - - if (selectedDirectory == null) return null; - final VirtualFile virt = selectedDirectory.getVirtualFile(); - final VirtualFile sourceRootForFile = ProjectRootManager.getInstance(project).getFileIndex().getSourceRootForFile(virt); - LOG.assertTrue(sourceRootForFile != null); - return sourceRootForFile; - } - - public static void buildDirectoryList(PackageWrapper aPackage, - VirtualFile[] contentSourceRoots, - LinkedHashSet targetDirectories, - Map relativePathsToCreate) { - - sourceRoots: - for (VirtualFile root : contentSourceRoots) { - final PsiDirectory[] directories = aPackage.getDirectories(); - for (PsiDirectory directory : directories) { - if (VfsUtil.isAncestor(root, directory.getVirtualFile(), false)) { - targetDirectories.add(directory); - continue sourceRoots; + + public static String getPackageName(PackageWrapper aPackage) { + if (aPackage == null) { + return null; } - } - String qNameToCreate; - try { - qNameToCreate = RefactoringUtil.qNameToCreateInSourceRoot(aPackage, root); - } - catch (IncorrectOperationException e) { - continue sourceRoots; - } - PsiDirectory currentDirectory = aPackage.getManager().findDirectory(root); - if (currentDirectory == null) continue; - final String[] shortNames = qNameToCreate.split("\\."); - for (int j = 0; j < shortNames.length; j++) { - String shortName = shortNames[j]; - final PsiDirectory subdirectory = currentDirectory.findSubdirectory(shortName); - if (subdirectory == null) { - targetDirectories.add(currentDirectory); - final StringBuffer postfix = new StringBuffer(); - for (int k = j; k < shortNames.length; k++) { - String name = shortNames[k]; - postfix.append(File.separatorChar); - postfix.append(name); - } - relativePathsToCreate.put(currentDirectory, postfix.toString()); - continue sourceRoots; + String name = aPackage.getQualifiedName(); + if (name.length() > 0) { + return name; + } + else { + return UsageViewBundle.message("default.package.presentable.name"); + } + } + + @Nullable + public static PsiDirectory chooseDestinationPackage(Project project, String packageName, @Nullable PsiDirectory baseDir) { + final PsiManager psiManager = PsiManager.getInstance(project); + final PackageWrapper packageWrapper = new PackageWrapper(psiManager, packageName); + final PsiJavaPackage aPackage = JavaPsiFacade.getInstance(project).findPackage(packageName); + PsiDirectory directory; + + PsiDirectory[] directories = aPackage != null ? aPackage.getDirectories() : null; + final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex(); + final boolean filterOutSources = baseDir != null && fileIndex.isInTestSourceContent(baseDir.getVirtualFile()); + if (directories != null && directories.length == 1 && !(filterOutSources && + !fileIndex.isInTestSourceContent(directories[0].getVirtualFile()))) { + directory = directories[0]; } else { - currentDirectory = subdirectory; + final VirtualFile[] contentSourceRoots = ProjectRootManager.getInstance(project).getContentSourceRoots(); + if (contentSourceRoots.length == 1 && !(filterOutSources && !fileIndex.isInTestSourceContent(contentSourceRoots[0]))) { + directory = ApplicationManager.getApplication().runWriteAction(new Computable() { + @Override + public PsiDirectory compute() { + return RefactoringUtil.createPackageDirectoryInSourceRoot(packageWrapper, contentSourceRoots[0]); + } + }); + } + else { + final VirtualFile sourceRootForFile = chooseSourceRoot(packageWrapper, contentSourceRoots, baseDir); + if (sourceRootForFile == null) { + return null; + } + directory = ApplicationManager.getApplication().runWriteAction(new Computable() { + @Override + public PsiDirectory compute() { + return new AutocreatingSingleSourceRootMoveDestination(packageWrapper, sourceRootForFile) + .getTargetDirectory((PsiDirectory)null); + } + }); + } + } + return directory; + } + + public static VirtualFile chooseSourceRoot( + final PackageWrapper targetPackage, + final VirtualFile[] contentSourceRoots, + final PsiDirectory initialDirectory + ) { + Project project = targetPackage.getManager().getProject(); + //ensure that there would be no duplicates: e.g. when one content root is subfolder of another root (configured via excluded roots) + LinkedHashSet targetDirectories = new LinkedHashSet(); + Map relativePathsToCreate = new HashMap(); + buildDirectoryList(targetPackage, contentSourceRoots, targetDirectories, relativePathsToCreate); + + final PsiDirectory selectedDirectory = DirectoryChooserUtil.chooseDirectory( + targetDirectories.toArray(new PsiDirectory[targetDirectories.size()]), + initialDirectory, + project, + relativePathsToCreate + ); + + if (selectedDirectory == null) { + return null; + } + final VirtualFile virt = selectedDirectory.getVirtualFile(); + final VirtualFile sourceRootForFile = ProjectRootManager.getInstance(project).getFileIndex().getSourceRootForFile(virt); + LOG.assertTrue(sourceRootForFile != null); + return sourceRootForFile; + } + + public static void buildDirectoryList( + PackageWrapper aPackage, + VirtualFile[] contentSourceRoots, + LinkedHashSet targetDirectories, + Map relativePathsToCreate + ) { + sourceRoots: + for (VirtualFile root : contentSourceRoots) { + final PsiDirectory[] directories = aPackage.getDirectories(); + for (PsiDirectory directory : directories) { + if (VfsUtil.isAncestor(root, directory.getVirtualFile(), false)) { + targetDirectories.add(directory); + continue sourceRoots; + } + } + String qNameToCreate; + try { + qNameToCreate = RefactoringUtil.qNameToCreateInSourceRoot(aPackage, root); + } + catch (IncorrectOperationException e) { + continue sourceRoots; + } + PsiDirectory currentDirectory = aPackage.getManager().findDirectory(root); + if (currentDirectory == null) { + continue; + } + final String[] shortNames = qNameToCreate.split("\\."); + for (int j = 0; j < shortNames.length; j++) { + String shortName = shortNames[j]; + final PsiDirectory subdirectory = currentDirectory.findSubdirectory(shortName); + if (subdirectory == null) { + targetDirectories.add(currentDirectory); + final StringBuffer postfix = new StringBuffer(); + for (int k = j; k < shortNames.length; k++) { + String name = shortNames[k]; + postfix.append(File.separatorChar); + postfix.append(name); + } + relativePathsToCreate.put(currentDirectory, postfix.toString()); + continue sourceRoots; + } + else { + currentDirectory = subdirectory; + } + } } - } + LOG.assertTrue(targetDirectories.size() <= contentSourceRoots.length); + LOG.assertTrue(relativePathsToCreate.size() <= contentSourceRoots.length); } - LOG.assertTrue(targetDirectories.size() <= contentSourceRoots.length); - LOG.assertTrue(relativePathsToCreate.size() <= contentSourceRoots.length); - } } diff --git a/plugin/src/main/java/com/intellij/java/impl/refactoring/move/moveClassesOrPackages/MoveDirectoryWithClassesHelper.java b/plugin/src/main/java/com/intellij/java/impl/refactoring/move/moveClassesOrPackages/MoveDirectoryWithClassesHelper.java index 9bb7e16eb4..920a70db6b 100644 --- a/plugin/src/main/java/com/intellij/java/impl/refactoring/move/moveClassesOrPackages/MoveDirectoryWithClassesHelper.java +++ b/plugin/src/main/java/com/intellij/java/impl/refactoring/move/moveClassesOrPackages/MoveDirectoryWithClassesHelper.java @@ -22,32 +22,40 @@ */ @ExtensionAPI(ComponentScope.APPLICATION) public abstract class MoveDirectoryWithClassesHelper { - private static final ExtensionPointName EP_NAME = - ExtensionPointName.create(MoveDirectoryWithClassesHelper.class); - - public abstract void findUsages(Collection filesToMove, PsiDirectory[] directoriesToMove, Collection result, - boolean searchInComments, boolean searchInNonJavaFiles, Project project); - - public abstract boolean move(PsiFile file, - PsiDirectory moveDestination, - Map oldToNewElementsMapping, - List movedFiles, - RefactoringElementListener listener); - - public abstract void postProcessUsages(UsageInfo[] usages, Function newDirMapper); - - public abstract void beforeMove(PsiFile psiFile); - - public abstract void afterMove(PsiElement newElement); - - public void preprocessUsages(Project project, - Set files, - UsageInfo[] infos, - PsiDirectory directory, - MultiMap conflicts) { - } - - public static List findAll() { - return EP_NAME.getExtensionList(); - } + private static final ExtensionPointName EP_NAME = + ExtensionPointName.create(MoveDirectoryWithClassesHelper.class); + + public abstract void findUsages( + Collection filesToMove, + PsiDirectory[] directoriesToMove, + Collection result, + boolean searchInComments, boolean searchInNonJavaFiles, Project project + ); + + public abstract boolean move( + PsiFile file, + PsiDirectory moveDestination, + Map oldToNewElementsMapping, + List movedFiles, + RefactoringElementListener listener + ); + + public abstract void postProcessUsages(UsageInfo[] usages, Function newDirMapper); + + public abstract void beforeMove(PsiFile psiFile); + + public abstract void afterMove(PsiElement newElement); + + public void preprocessUsages( + Project project, + Set files, + UsageInfo[] infos, + PsiDirectory directory, + MultiMap conflicts + ) { + } + + public static List findAll() { + return EP_NAME.getExtensionList(); + } } \ No newline at end of file diff --git a/plugin/src/main/java/com/intellij/java/impl/refactoring/rename/RenameAliasingPomTargetProcessor.java b/plugin/src/main/java/com/intellij/java/impl/refactoring/rename/RenameAliasingPomTargetProcessor.java index 835ff12ed8..4136b61e4b 100644 --- a/plugin/src/main/java/com/intellij/java/impl/refactoring/rename/RenameAliasingPomTargetProcessor.java +++ b/plugin/src/main/java/com/intellij/java/impl/refactoring/rename/RenameAliasingPomTargetProcessor.java @@ -31,34 +31,36 @@ @ExtensionImpl public class RenameAliasingPomTargetProcessor extends RenamePsiElementProcessor { - @Override - public boolean canProcessElement(@Nonnull PsiElement element) { - return element instanceof PomTarget || element instanceof PomTargetPsiElement; - } - - @Override - public void prepareRenaming(PsiElement element, String newName, Map allRenames) { - PomTarget target = null; - if (element instanceof PomTargetPsiElement) { - target = ((PomTargetPsiElement) element).getTarget(); - } else if (element instanceof PomTarget) { - target = (PomTarget) element; + @Override + public boolean canProcessElement(@Nonnull PsiElement element) { + return element instanceof PomTarget || element instanceof PomTargetPsiElement; } - if (target != null) { - for (AliasingPsiTargetMapper mapper : Extensions.getExtensions(AliasingPsiTargetMapper.EP_NAME)) { - for (AliasingPsiTarget psiTarget : mapper.getTargets(target)) { - PsiElement psiElement = PomService.convertToPsi(psiTarget); - String name = psiTarget.getNameAlias(newName); + @Override + public void prepareRenaming(PsiElement element, String newName, Map allRenames) { + PomTarget target = null; + if (element instanceof PomTargetPsiElement) { + target = ((PomTargetPsiElement)element).getTarget(); + } + else if (element instanceof PomTarget) { + target = (PomTarget)element; + } + + if (target != null) { + for (AliasingPsiTargetMapper mapper : Extensions.getExtensions(AliasingPsiTargetMapper.EP_NAME)) { + for (AliasingPsiTarget psiTarget : mapper.getTargets(target)) { + PsiElement psiElement = PomService.convertToPsi(psiTarget); + String name = psiTarget.getNameAlias(newName); - String definedName = allRenames.put(psiElement, name); - if (definedName != null) { - assert definedName.equals(name); - } else { - prepareRenaming(psiElement, name, allRenames); - } + String definedName = allRenames.put(psiElement, name); + if (definedName != null) { + assert definedName.equals(name); + } + else { + prepareRenaming(psiElement, name, allRenames); + } + } + } } - } } - } } diff --git a/plugin/src/main/java/com/intellij/java/impl/refactoring/safeDelete/ImportSearcher.java b/plugin/src/main/java/com/intellij/java/impl/refactoring/safeDelete/ImportSearcher.java index 6b316dbba3..a8ac63527e 100644 --- a/plugin/src/main/java/com/intellij/java/impl/refactoring/safeDelete/ImportSearcher.java +++ b/plugin/src/main/java/com/intellij/java/impl/refactoring/safeDelete/ImportSearcher.java @@ -27,21 +27,23 @@ */ @ExtensionAPI(ComponentScope.APPLICATION) public abstract class ImportSearcher { - private static final ExtensionPointName EP_NAME = ExtensionPointName.create(ImportSearcher.class); + private static final ExtensionPointName EP_NAME = ExtensionPointName.create(ImportSearcher.class); - /** - * @return found import or null - */ - @Nullable - public abstract PsiElement findImport(PsiElement element, boolean onlyNonStatic); + /** + * @return found import or null + */ + @Nullable + public abstract PsiElement findImport(PsiElement element, boolean onlyNonStatic); - @Nullable - public static PsiElement getImport(PsiElement element, boolean onlyNonStatic) { - for (ImportSearcher searcher : EP_NAME.getExtensions()) { - PsiElement anImport = searcher.findImport(element, onlyNonStatic); - if (anImport != null) return anImport; - } + @Nullable + public static PsiElement getImport(PsiElement element, boolean onlyNonStatic) { + for (ImportSearcher searcher : EP_NAME.getExtensions()) { + PsiElement anImport = searcher.findImport(element, onlyNonStatic); + if (anImport != null) { + return anImport; + } + } - return null; - } + return null; + } } From fdac2237a9d21c1916159009848ffdce46bb8c52 Mon Sep 17 00:00:00 2001 From: UNV Date: Thu, 1 May 2025 23:16:01 +0300 Subject: [PATCH 2/2] General refactoring of users of EP_NAME (part 1). --- .../impl/settings/NodeRendererSettings.java | 2 +- .../codeInsight/AnnotationTargetUtil.java | 65 ++-- .../daemon/impl/quickfix/OrderEntryFix.java | 47 +-- .../generation/GenerateMembersUtil.java | 202 +++++++------ .../GetterSetterPrototypeProvider.java | 8 +- .../generation/OverrideImplementUtil.java | 154 +++++----- .../JavaExpressionSurroundDescriptor.java | 6 +- .../macro/VariableTypeCalculator.java | 5 +- .../UncheckedWarningLocalInspectionBase.java | 285 +++++++++--------- .../visibility/VisibilityInspection.java | 93 +++--- .../GenerateToStringActionHandlerImpl.java | 151 +++++----- 11 files changed, 520 insertions(+), 498 deletions(-) diff --git a/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/settings/NodeRendererSettings.java b/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/settings/NodeRendererSettings.java index 602259f319..5800f11fdb 100644 --- a/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/settings/NodeRendererSettings.java +++ b/java-debugger-impl/src/main/java/com/intellij/java/debugger/impl/settings/NodeRendererSettings.java @@ -94,7 +94,7 @@ public class NodeRendererSettings implements PersistentStateComponent { createLabelRenderer(" size = ", "size()", null), createExpressionChildrenRenderer( "entrySet().toArray()", - "!isEmpty" + "()" + "!isEmpty()" ) ), createCompoundReferenceRenderer( diff --git a/java-language-api/src/main/java/com/intellij/java/language/codeInsight/AnnotationTargetUtil.java b/java-language-api/src/main/java/com/intellij/java/language/codeInsight/AnnotationTargetUtil.java index 8f6e257cec..3de0ae4390 100644 --- a/java-language-api/src/main/java/com/intellij/java/language/codeInsight/AnnotationTargetUtil.java +++ b/java-language-api/src/main/java/com/intellij/java/language/codeInsight/AnnotationTargetUtil.java @@ -18,6 +18,7 @@ import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.PsiAnnotation.TargetType; import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; import consulo.application.dumb.IndexNotReadyException; import consulo.java.language.module.util.JavaClassNames; import consulo.language.psi.PsiCompiledElement; @@ -108,16 +109,16 @@ public static TargetType[] getTargetsForLocation(@Nullable PsiAnnotationOwner ow return TYPE_PARAMETER_TARGETS; } - if (owner instanceof PsiModifierList) { - PsiElement element = ((PsiModifierList)owner).getParent(); + if (owner instanceof PsiModifierList modifierList) { + PsiElement element = modifierList.getParent(); if (element instanceof PsiPackageStatement) { return PACKAGE_TARGETS; } - if (element instanceof PsiClass) { - if (((PsiClass)element).getModifierList() != owner) { + if (element instanceof PsiClass psiClass) { + if (psiClass.getModifierList() != owner) { return TargetType.EMPTY_ARRAY; } - if (((PsiClass)element).isAnnotationType()) { + if (psiClass.isAnnotationType()) { return ANNOTATION_TARGETS; } else { @@ -127,26 +128,21 @@ public static TargetType[] getTargetsForLocation(@Nullable PsiAnnotationOwner ow if (element instanceof PsiRecordComponent) { return RECORD_COMPONENT_TARGETS; } - if (element instanceof PsiMethod) { - if (((PsiMethod)element).isConstructor()) { - return CONSTRUCTOR_TARGETS; - } - else { - return METHOD_TARGETS; - } + if (element instanceof PsiMethod method) { + return method.isConstructor() ? CONSTRUCTOR_TARGETS : METHOD_TARGETS; } if (element instanceof PsiField) { return FIELD_TARGETS; } - if (element instanceof PsiParameter) { + if (element instanceof PsiParameter parameter) { // PARAMETER applies only to formal parameters (methods & lambdas) and catch parameters // see https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.6.4.1 - PsiElement scope = element.getParent(); - if (scope instanceof PsiForeachStatement || element instanceof PsiPatternVariable) { + PsiElement scope = parameter.getParent(); + if (scope instanceof PsiForeachStatement || parameter instanceof PsiPatternVariable) { return LOCAL_VARIABLE_TARGETS; } - if (scope instanceof PsiParameterList && scope.getParent() instanceof PsiLambdaExpression && - ((PsiParameter)element).getTypeElement() == null) { + if (scope instanceof PsiParameterList && scope.getParent() instanceof PsiLambdaExpression + && parameter.getTypeElement() == null) { return TargetType.EMPTY_ARRAY; } @@ -167,18 +163,19 @@ public static TargetType[] getTargetsForLocation(@Nullable PsiAnnotationOwner ow } @Nullable + @RequiredReadAction public static Set extractRequiredAnnotationTargets(@Nullable PsiAnnotationMemberValue value) { - if (value instanceof PsiReference) { - TargetType targetType = translateTargetRef((PsiReference)value); + if (value instanceof PsiReference ref) { + TargetType targetType = translateTargetRef(ref); if (targetType != null) { return Collections.singleton(targetType); } } - else if (value instanceof PsiArrayInitializerMemberValue) { + else if (value instanceof PsiArrayInitializerMemberValue arrayInitializerMemberValue) { Set targets = new HashSet<>(); - for (PsiAnnotationMemberValue initializer : ((PsiArrayInitializerMemberValue)value).getInitializers()) { - if (initializer instanceof PsiReference) { - TargetType targetType = translateTargetRef((PsiReference)initializer); + for (PsiAnnotationMemberValue initializer : arrayInitializerMemberValue.getInitializers()) { + if (initializer instanceof PsiReference ref) { + TargetType targetType = translateTargetRef(ref); if (targetType != null) { targets.add(targetType); } @@ -191,10 +188,11 @@ else if (value instanceof PsiArrayInitializerMemberValue) { } @Nullable + @RequiredReadAction private static TargetType translateTargetRef(@Nonnull PsiReference reference) { PsiElement field = reference.resolve(); - if (field instanceof PsiEnumConstant) { - String name = ((PsiEnumConstant)field).getName(); + if (field instanceof PsiEnumConstant enumConst) { + String name = enumConst.getName(); try { return TargetType.valueOf(name); } @@ -208,6 +206,7 @@ private static TargetType translateTargetRef(@Nonnull PsiReference reference) { /** * Returns {@code true} if the annotation resolves to a class having {@link TargetType#TYPE_USE} in it's targets. */ + @RequiredReadAction public static boolean isTypeAnnotation(@Nonnull PsiAnnotation element) { return findAnnotationTarget(element, TargetType.TYPE_USE) == TargetType.TYPE_USE; } @@ -217,14 +216,12 @@ public static boolean isTypeAnnotation(@Nonnull PsiAnnotation element) { * at any of the targets, or {@linkplain TargetType#UNKNOWN} if the annotation does not resolve to a valid annotation type. */ @Nullable + @RequiredReadAction public static TargetType findAnnotationTarget(@Nonnull PsiAnnotation annotation, @Nonnull TargetType... types) { if (types.length != 0) { PsiJavaCodeReferenceElement ref = annotation.getNameReferenceElement(); - if (ref != null) { - PsiElement annotationType = ref.resolve(); - if (annotationType instanceof PsiClass) { - return findAnnotationTarget((PsiClass)annotationType, types); - } + if (ref != null && ref.resolve() instanceof PsiClass annotationType) { + return findAnnotationTarget(annotationType, types); } } @@ -236,6 +233,7 @@ public static TargetType findAnnotationTarget(@Nonnull PsiAnnotation annotation, * at any of the targets, or {@linkplain TargetType#UNKNOWN} if the type is not a valid annotation (e.g. cannot be resolved). */ @Nullable + @RequiredReadAction public static TargetType findAnnotationTarget(@Nonnull PsiClass annotationType, @Nonnull TargetType... types) { if (types.length != 0) { Set targets = getAnnotationTargets(annotationType); @@ -256,6 +254,7 @@ public static TargetType findAnnotationTarget(@Nonnull PsiClass annotationType, * Returns a set of targets where the given annotation may be applied, or {@code null} when the type is not a valid annotation. */ @Nullable + @RequiredReadAction public static Set getAnnotationTargets(@Nonnull PsiClass annotationType) { if (!annotationType.isAnnotationType()) { return null; @@ -279,6 +278,7 @@ public static Set getAnnotationTargets(@Nonnull PsiClass annotationT * Returns null if {@code modifierListOwner.getModifierList()} is null. */ @Nullable + @RequiredReadAction public static PsiAnnotationOwner getTarget(@Nonnull PsiModifierListOwner modifierListOwner, @Nonnull String annotation) { PsiModifierList list = modifierListOwner.getModifierList(); if (list == null) { @@ -286,7 +286,8 @@ public static PsiAnnotationOwner getTarget(@Nonnull PsiModifierListOwner modifie } PsiClass annotationClass = JavaPsiFacade.getInstance(modifierListOwner.getProject()) .findClass(annotation, modifierListOwner.getResolveScope()); - return getTarget(modifierListOwner, annotationClass != null && findAnnotationTarget(annotationClass, TargetType.TYPE_USE) != null); + return getTarget(modifierListOwner, annotationClass != null + && findAnnotationTarget(annotationClass, TargetType.TYPE_USE) != null); } /** @@ -324,7 +325,7 @@ else if (parent instanceof PsiVariable) { * * @param modifierList the place where type appears */ - @RequiredReadAction + @RequiredWriteAction public static PsiType keepStrictlyTypeUseAnnotations(@Nullable PsiModifierList modifierList, @Nonnull PsiType type) { if (modifierList == null) { return type; diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/impl/quickfix/OrderEntryFix.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/impl/quickfix/OrderEntryFix.java index bbbf796b73..0984a7d397 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/impl/quickfix/OrderEntryFix.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/daemon/impl/quickfix/OrderEntryFix.java @@ -27,6 +27,7 @@ import com.intellij.java.language.psi.PsiImportStatement; import com.intellij.java.language.psi.search.PsiShortNamesCache; import com.intellij.java.language.psi.util.PsiUtil; +import consulo.annotation.access.RequiredReadAction; import consulo.codeEditor.Editor; import consulo.content.base.BinariesOrderRootType; import consulo.content.library.Library; @@ -82,11 +83,13 @@ public String getName() { } @Override - public void applyFix(@Nonnull final Project project, @Nonnull final ProblemDescriptor descriptor) { + @RequiredReadAction + public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descriptor) { invoke(project, null, descriptor.getPsiElement().getContainingFile()); } @Nullable + @RequiredReadAction public static List registerFixes(@Nonnull PsiReference reference) { PsiElement psiElement = reference.getElement(); String shortReferenceName = reference.getRangeInElement().substring(psiElement.getText()); @@ -107,9 +110,9 @@ public static List registerFixes(@Nonnull PsiReference reference) return null; } - if (reference instanceof PsiJavaModuleReferenceImpl) { + if (reference instanceof PsiJavaModuleReferenceImpl psiJavaModuleReference) { List result = new ArrayList<>(); - createModuleFixes((PsiJavaModuleReferenceImpl)reference, currentModule, refVFile, result); + createModuleFixes(psiJavaModuleReference, currentModule, refVFile, result); return result; } @@ -146,9 +149,8 @@ public static List registerFixes(@Nonnull PsiReference reference) continue; } for (OrderEntry orderEntry : fileIndex.getOrderEntriesForFile(virtualFile)) { - if (orderEntry instanceof LibraryOrderEntry) { - final LibraryOrderEntry libraryEntry = (LibraryOrderEntry)orderEntry; - final Library library = libraryEntry.getLibrary(); + if (orderEntry instanceof LibraryOrderEntry libraryEntry) { + Library library = libraryEntry.getLibrary(); if (library == null) { continue; } @@ -156,14 +158,15 @@ public static List registerFixes(@Nonnull PsiReference reference) if (files.length == 0) { continue; } - final VirtualFile jar = files[0]; + VirtualFile jar = files[0]; if (jar == null || libraryEntry.isModuleLevel() && !librariesToAdd.add(jar) || !librariesToAdd.add(library)) { continue; } OrderEntry entryForFile = moduleFileIndex.getOrderEntryForFile(virtualFile); - if (entryForFile != null && !(entryForFile instanceof ExportableOrderEntry && ((ExportableOrderEntry)entryForFile).getScope() == DependencyScope.TEST && !moduleFileIndex - .isInTestSourceContent(refVFile))) { + if (entryForFile != null && !(entryForFile instanceof ExportableOrderEntry exportableOrderEntry + && exportableOrderEntry.getScope() == DependencyScope.TEST + && !moduleFileIndex.isInTestSourceContent(refVFile))) { continue; } @@ -184,15 +187,16 @@ private static void createModuleFixes( List result ) { ProjectFileIndex index = ProjectRootManager.getInstance(currentModule.getProject()).getFileIndex(); - List targets = - Stream.of(reference.multiResolve(true)).map(ResolveResult::getElement).filter(Objects::nonNull).collect(Collectors.toList()); + List targets = Stream.of(reference.multiResolve(true)) + .map(ResolveResult::getElement) + .filter(Objects::nonNull) + .collect(Collectors.toList()); Set modules = targets.stream() .map(e -> !(e instanceof PsiCompiledElement) ? e.getContainingFile() : null) .map(f -> f != null ? f.getVirtualFile() : null) - .filter(vf -> vf != null && - index.isInSource(vf)) - .map(vf -> index.getModuleForFile(vf)) + .filter(vf -> vf != null && index.isInSource(vf)) + .map(index::getModuleForFile) .filter(m -> m != null && m != currentModule) .collect(Collectors.toSet()); if (!modules.isEmpty()) { @@ -202,14 +206,14 @@ private static void createModuleFixes( targets.stream() .map(e -> e instanceof PsiCompiledElement ? e.getContainingFile() : null) .map(f -> f != null ? f.getVirtualFile() : null) - .flatMap(vf -> vf != null ? index - .getOrderEntriesForFile(vf).stream() : Stream.empty()) - .map(e -> e instanceof LibraryOrderEntry ? ((LibraryOrderEntry)e).getLibrary() : null) + .flatMap(vf -> vf != null ? index.getOrderEntriesForFile(vf).stream() : Stream.empty()) + .map(e -> e instanceof LibraryOrderEntry libraryEntry ? libraryEntry.getLibrary() : null) .filter(Objects::nonNull) .distinct() .forEach(l -> result.add(new AddLibraryToDependenciesFix(currentModule, l, reference, null))); } + @RequiredReadAction private static void registerExternalFixes( @Nonnull PsiReference reference, PsiElement psiElement, @@ -259,7 +263,8 @@ private static List filterAllowedDependencies(PsiElement element, PsiC return result; } - private static ThreeState isReferenceToAnnotation(final PsiElement psiElement) { + @RequiredReadAction + private static ThreeState isReferenceToAnnotation(PsiElement psiElement) { if (psiElement.getLanguage() == JavaLanguage.INSTANCE && !PsiUtil.isLanguageLevel5OrHigher(psiElement)) { return ThreeState.NO; } @@ -290,7 +295,7 @@ public static void importClass( } } - public static void addJarToRoots(@Nonnull String jarPath, final @Nonnull Module module, @Nullable PsiElement location) { + public static void addJarToRoots(@Nonnull String jarPath, @Nonnull Module module, @Nullable PsiElement location) { addJarsToRoots(Collections.singletonList(jarPath), null, module, location); } @@ -313,7 +318,7 @@ public static List refreshAndConvertToUrls(@Nonnull List jarPath @Nonnull public static DependencyScope suggestScopeByLocation(@Nonnull Module module, @Nullable PsiElement location) { if (location != null) { - final VirtualFile vFile = location.getContainingFile().getVirtualFile(); + VirtualFile vFile = location.getContainingFile().getVirtualFile(); if (vFile != null && ModuleRootManager.getInstance(module).getFileIndex().isInTestSourceContent(vFile)) { return DependencyScope.TEST; } @@ -323,7 +328,7 @@ public static DependencyScope suggestScopeByLocation(@Nonnull Module module, @Nu @Nonnull private static String refreshAndConvertToUrl(String jarPath) { - final File libraryRoot = new File(jarPath); + File libraryRoot = new File(jarPath); LocalFileSystem.getInstance().refreshAndFindFileByIoFile(libraryRoot); return VirtualFileUtil.getUrlForLibraryRoot(libraryRoot); } diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/GenerateMembersUtil.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/GenerateMembersUtil.java index 781cd934db..45f2e7700f 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/GenerateMembersUtil.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/GenerateMembersUtil.java @@ -37,6 +37,7 @@ import com.intellij.java.language.psi.util.TypeConversionUtil; import com.intellij.java.language.util.VisibilityUtil; import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; import consulo.application.util.matcher.NameUtil; import consulo.codeEditor.Editor; import consulo.codeEditor.ScrollType; @@ -52,16 +53,14 @@ import consulo.language.psi.scope.GlobalSearchScope; import consulo.language.psi.util.PsiTreeUtil; import consulo.language.util.IncorrectOperationException; -import consulo.language.util.ModuleUtilCore; import consulo.logging.Logger; import consulo.module.Module; import consulo.project.Project; +import consulo.ui.annotation.RequiredUIAccess; import consulo.util.collection.ContainerUtil; import consulo.util.lang.Comparing; import consulo.util.lang.ObjectUtil; -import consulo.util.lang.Pair; import consulo.util.lang.StringUtil; -import consulo.util.lang.function.Condition; import org.jetbrains.annotations.Contract; import jakarta.annotation.Nonnull; @@ -76,12 +75,13 @@ private GenerateMembersUtil() { } @Nonnull + @RequiredWriteAction public static List insertMembersAtOffset(PsiFile file, int offset, @Nonnull List memberPrototypes) throws IncorrectOperationException { if (memberPrototypes.isEmpty()) { return memberPrototypes; } - final PsiElement leaf = file.findElementAt(offset); + PsiElement leaf = file.findElementAt(offset); if (leaf == null) { return Collections.emptyList(); } @@ -93,7 +93,7 @@ public static List insertMembersAtOffset(PsiFile f PsiElement anchor = memberPrototypes.get(0).findInsertionAnchor(aClass, leaf); if (anchor instanceof PsiWhiteSpace) { - final ASTNode spaceNode = anchor.getNode(); + ASTNode spaceNode = anchor.getNode(); anchor = anchor.getNextSibling(); assert spaceNode != null; @@ -114,11 +114,11 @@ public static List insertMembersAtOffset(PsiFile f // } whiteSpace += "\n"; } - final PsiParserFacade parserFacade = PsiParserFacade.SERVICE.getInstance(file.getProject()); - final ASTNode singleNewLineWhitespace = parserFacade.createWhiteSpaceFromText(whiteSpace).getNode(); + PsiParserFacade parserFacade = PsiParserFacade.SERVICE.getInstance(file.getProject()); + ASTNode singleNewLineWhitespace = parserFacade.createWhiteSpaceFromText(whiteSpace).getNode(); if (singleNewLineWhitespace != null) { - spaceNode.getTreeParent() - .replaceChild(spaceNode, singleNewLineWhitespace); // See http://jetbrains.net/jira/browse/IDEADEV-12837 + // See http://jetbrains.net/jira/browse/IDEADEV-12837 + spaceNode.getTreeParent().replaceChild(spaceNode, singleNewLineWhitespace); } } } @@ -134,8 +134,7 @@ public static List insertMembersAtOffset(PsiFile f } element = element.getNextSibling(); } - if (element instanceof PsiField) { - PsiField field = (PsiField)element; + if (element instanceof PsiField field) { PsiTypeElement typeElement = field.getTypeElement(); if (typeElement != null && !field.equals(typeElement.getParent())) { field.normalizeDeclaration(); @@ -164,6 +163,7 @@ public static List insertMembersBeforeAnchor( /** * @see GenerationInfo#positionCaret(Editor, boolean) */ + @RequiredUIAccess public static void positionCaret(@Nonnull Editor editor, @Nonnull PsiElement firstMember, boolean toEditMethodBody) { LOG.assertTrue(firstMember.isValid()); Project project = firstMember.getProject(); @@ -195,10 +195,8 @@ public static void positionCaret(@Nonnull Editor editor, @Nonnull PsiElement fir boolean adjustLineIndent = false; // body is whitespace - if (start > end && - firstBodyElement == lastBodyElement && - firstBodyElement instanceof PsiWhiteSpaceImpl) { - CharSequence chars = ((PsiWhiteSpaceImpl)firstBodyElement).getChars(); + if (start > end && firstBodyElement == lastBodyElement && firstBodyElement instanceof PsiWhiteSpaceImpl whiteSpace) { + CharSequence chars = whiteSpace.getChars(); if (chars.length() > 1 && chars.charAt(0) == '\n' && chars.charAt(1) == '\n') { start = end = firstBodyElement.getTextRange().getStartOffset() + 1; adjustLineIndent = true; @@ -224,15 +222,14 @@ else if (adjustLineIndent) { } int offset; - if (firstMember instanceof PsiMethod) { - PsiMethod method = (PsiMethod)firstMember; + if (firstMember instanceof PsiMethod method) { PsiCodeBlock body = method.getBody(); if (body == null) { offset = method.getTextRange().getStartOffset(); } else { PsiJavaToken lBrace = body.getLBrace(); - assert lBrace != null : firstMember.getText(); + assert lBrace != null : method.getText(); offset = lBrace.getTextRange().getEndOffset(); } } @@ -245,21 +242,15 @@ else if (adjustLineIndent) { editor.getSelectionModel().removeSelection(); } - public static PsiElement insert( - @Nonnull PsiClass aClass, - @Nonnull PsiMember member, - @Nullable PsiElement anchor, - boolean before - ) throws IncorrectOperationException { - if (member instanceof PsiMethod) { - if (!aClass.isInterface()) { - final PsiParameter[] parameters = ((PsiMethod)member).getParameterList().getParameters(); - final boolean generateFinals = CodeStyleSettingsManager.getSettings(aClass.getProject()).GENERATE_FINAL_PARAMETERS; - for (final PsiParameter parameter : parameters) { - final PsiModifierList modifierList = parameter.getModifierList(); - assert modifierList != null; - modifierList.setModifierProperty(PsiModifier.FINAL, generateFinals); - } + public static PsiElement insert(@Nonnull PsiClass aClass, @Nonnull PsiMember member, @Nullable PsiElement anchor, boolean before) + throws IncorrectOperationException { + if (member instanceof PsiMethod method && !aClass.isInterface()) { + PsiParameter[] parameters = method.getParameterList().getParameters(); + boolean generateFinals = CodeStyleSettingsManager.getSettings(aClass.getProject()).GENERATE_FINAL_PARAMETERS; + for (PsiParameter parameter : parameters) { + PsiModifierList modifierList = parameter.getModifierList(); + assert modifierList != null; + modifierList.setModifierProperty(PsiModifier.FINAL, generateFinals); } } @@ -272,11 +263,11 @@ public static PsiElement insert( } @Nullable + @RequiredReadAction private static PsiClass findClassAtOffset(PsiFile file, PsiElement leaf) { PsiElement element = leaf; while (element != null && !(element instanceof PsiFile)) { - if (element instanceof PsiClass && !(element instanceof PsiTypeParameter)) { - final PsiClass psiClass = (PsiClass)element; + if (element instanceof PsiClass psiClass && !(element instanceof PsiTypeParameter)) { if (psiClass.isEnum()) { PsiElement lastChild = null; for (PsiElement child : psiClass.getChildren()) { @@ -302,23 +293,25 @@ else if (child instanceof PsiJavaToken && ",".equals(child.getText()) || child i return null; } - public static PsiMethod substituteGenericMethod(PsiMethod method, final PsiSubstitutor substitutor) { + @RequiredWriteAction + public static PsiMethod substituteGenericMethod(PsiMethod method, PsiSubstitutor substitutor) { return substituteGenericMethod(method, substitutor, null); } + @RequiredWriteAction public static PsiMethod substituteGenericMethod( @Nonnull PsiMethod sourceMethod, @Nonnull PsiSubstitutor substitutor, @Nullable PsiElement target ) { - final Project project = sourceMethod.getProject(); - final JVMElementFactory factory = getFactory(sourceMethod.getProject(), target); - final JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(project); + Project project = sourceMethod.getProject(); + JVMElementFactory factory = getFactory(sourceMethod.getProject(), target); + JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(project); try { - final PsiMethod resultMethod = createMethod(factory, sourceMethod, target); + PsiMethod resultMethod = createMethod(factory, sourceMethod, target); copyModifiers(sourceMethod.getModifierList(), resultMethod.getModifierList()); - final PsiSubstitutor collisionResolvedSubstitutor = substituteTypeParameters( + PsiSubstitutor collisionResolvedSubstitutor = substituteTypeParameters( factory, target, sourceMethod.getTypeParameterList(), @@ -337,16 +330,16 @@ public static PsiMethod substituteGenericMethod( ); copyDocComment(sourceMethod, resultMethod, factory); GlobalSearchScope scope = sourceMethod.getResolveScope(); - final List thrownTypes = + List thrownTypes = ExceptionUtil.collectSubstituted(collisionResolvedSubstitutor, sourceMethod.getThrowsList().getReferencedTypes(), scope); - if (target instanceof PsiClass) { - final PsiMethod[] methods = ((PsiClass)target).findMethodsBySignature(sourceMethod, true); + if (target instanceof PsiClass psiClass) { + PsiMethod[] methods = psiClass.findMethodsBySignature(sourceMethod, true); for (PsiMethod psiMethod : methods) { if (psiMethod != null && psiMethod != sourceMethod) { PsiClass aSuper = psiMethod.getContainingClass(); - if (aSuper != null && aSuper != target) { + if (aSuper != null && aSuper != psiClass) { PsiSubstitutor superClassSubstitutor = - TypeConversionUtil.getSuperClassSubstitutor(aSuper, (PsiClass)target, PsiSubstitutor.EMPTY); + TypeConversionUtil.getSuperClassSubstitutor(aSuper, psiClass, PsiSubstitutor.EMPTY); ExceptionUtil.retainExceptions( thrownTypes, ExceptionUtil.collectSubstituted( @@ -373,6 +366,7 @@ private static void copyModifiers(@Nonnull PsiModifierList sourceModifierList, @ } @Nonnull + @RequiredWriteAction private static PsiSubstitutor substituteTypeParameters( @Nonnull JVMElementFactory factory, @Nullable PsiElement target, @@ -385,11 +379,11 @@ private static PsiSubstitutor substituteTypeParameters( return substitutor; } - final Map substitutionMap = new HashMap<>(substitutor.getSubstitutionMap()); + Map substitutionMap = new HashMap<>(substitutor.getSubstitutionMap()); for (PsiTypeParameter typeParam : sourceTypeParameterList.getTypeParameters()) { - final PsiTypeParameter substitutedTypeParam = substituteTypeParameter(factory, typeParam, substitutor, sourceMethod); + PsiTypeParameter substitutedTypeParam = substituteTypeParameter(factory, typeParam, substitutor, sourceMethod); - final PsiTypeParameter resolvedTypeParam = + PsiTypeParameter resolvedTypeParam = resolveTypeParametersCollision(factory, sourceTypeParameterList, target, substitutedTypeParam, substitutor); targetTypeParameterList.add(resolvedTypeParam); if (substitutedTypeParam != resolvedTypeParam) { @@ -411,12 +405,12 @@ private static PsiTypeParameter resolveTypeParametersCollision( String typeParamName = typeParam.getName(); for (PsiType type : substitutor.getSubstitutionMap().values()) { if (type != null && Objects.equals(type.getCanonicalText(), typeParamName)) { - final String newName = suggestUniqueTypeParameterName( + String newName = suggestUniqueTypeParameterName( typeParamName, sourceTypeParameterList, PsiTreeUtil.getParentOfType(target, PsiClass.class, false) ); - final PsiTypeParameter newTypeParameter = factory.createTypeParameter(newName, typeParam.getSuperTypes()); + PsiTypeParameter newTypeParameter = factory.createTypeParameter(newName, typeParam.getSuperTypes()); substitutor.put(typeParam, factory.createType(newTypeParameter)); return newTypeParameter; } @@ -425,6 +419,7 @@ private static PsiTypeParameter resolveTypeParametersCollision( } @Nonnull + @RequiredReadAction private static String suggestUniqueTypeParameterName( String baseName, @Nonnull PsiTypeParameterList typeParameterList, @@ -432,7 +427,7 @@ private static String suggestUniqueTypeParameterName( ) { int i = 0; while (true) { - final String newName = baseName + ++i; + String newName = baseName + ++i; if (checkUniqueTypeParameterName(newName, typeParameterList) && (targetClass == null || checkUniqueTypeParameterName(newName, targetClass.getTypeParameterList()))) { return newName; @@ -440,7 +435,7 @@ private static String suggestUniqueTypeParameterName( } } - + @RequiredReadAction private static boolean checkUniqueTypeParameterName(@Nonnull String baseName, @Nullable PsiTypeParameterList typeParameterList) { if (typeParameterList == null) { return true; @@ -457,10 +452,10 @@ private static boolean checkUniqueTypeParameterName(@Nonnull String baseName, @N @Nonnull private static PsiTypeParameter substituteTypeParameter( - final @Nonnull JVMElementFactory factory, + @Nonnull JVMElementFactory factory, @Nonnull PsiTypeParameter typeParameter, - final @Nonnull PsiSubstitutor substitutor, - @Nonnull final PsiMethod sourceMethod + @Nonnull PsiSubstitutor substitutor, + @Nonnull PsiMethod sourceMethod ) { if (typeParameter instanceof LightElement) { List substitutedSupers = @@ -470,21 +465,19 @@ private static PsiTypeParameter substituteTypeParameter( substitutedSupers.toArray(PsiClassType.EMPTY_ARRAY) ); } - final PsiElement copy = ObjectUtil.notNull( - typeParameter instanceof PsiCompiledElement - ? ((PsiCompiledElement)typeParameter).getMirror() - : typeParameter, + PsiElement copy = ObjectUtil.notNull( + typeParameter instanceof PsiCompiledElement compiledElem ? compiledElem.getMirror() : typeParameter, typeParameter ).copy(); LOG.assertTrue(copy != null, typeParameter); - final Map replacementMap = new HashMap<>(); + Map replacementMap = new HashMap<>(); copy.accept(new JavaRecursiveElementVisitor() { @Override + @RequiredWriteAction public void visitReferenceElement(@Nonnull PsiJavaCodeReferenceElement reference) { super.visitReferenceElement(reference); - final PsiElement resolve = reference.resolve(); - if (resolve instanceof PsiTypeParameter) { - final PsiType type = factory.createType((PsiTypeParameter)resolve); + if (reference.resolve() instanceof PsiTypeParameter typeParam) { + PsiType type = factory.createType(typeParam); replacementMap.put( reference, factory.createReferenceElementByType((PsiClassType)substituteType(substitutor, type, sourceMethod, null)) @@ -508,6 +501,7 @@ private static PsiClassType toClassType(PsiType type) { return null; } + @RequiredWriteAction private static void substituteParameters( @Nonnull JVMElementFactory factory, @Nonnull JavaCodeStyleManager codeStyleManager, @@ -516,15 +510,16 @@ private static void substituteParameters( @Nonnull PsiSubstitutor substitutor, PsiElement target ) { - final PsiParameter[] parameters = sourceParameterList.getParameters(); - final PsiParameter[] newParameters = overriddenParameters(parameters, factory, codeStyleManager, substitutor, target); + PsiParameter[] parameters = sourceParameterList.getParameters(); + PsiParameter[] newParameters = overriddenParameters(parameters, factory, codeStyleManager, substitutor, target); for (int i = 0; i < newParameters.length; i++) { - final PsiParameter newParameter = newParameters[i]; + PsiParameter newParameter = newParameters[i]; copyOrReplaceModifierList(parameters[i], newParameter); targetParameterList.add(newParameter); } } + @RequiredWriteAction public static PsiParameter[] overriddenParameters( PsiParameter[] parameters, @Nonnull JVMElementFactory factory, @@ -537,21 +532,21 @@ public static PsiParameter[] overriddenParameters( for (int i = 0; i < parameters.length; i++) { PsiParameter parameter = parameters[i]; - final PsiType parameterType = parameter.getType(); + PsiType parameterType = parameter.getType(); PsiElement declarationScope = parameter.getDeclarationScope(); PsiType substituted = declarationScope instanceof PsiTypeParameterListOwner ? substituteType(substitutor, parameterType, (PsiTypeParameterListOwner)declarationScope, parameter.getModifierList()) : parameterType; String paramName = parameter.getName(); boolean isBaseNameGenerated = true; - final boolean isSubstituted = substituted.equals(parameterType); + boolean isSubstituted = substituted.equals(parameterType); if (!isSubstituted && isBaseNameGenerated(codeStyleManager, TypeConversionUtil.erasure(parameterType), paramName)) { isBaseNameGenerated = false; } - if (paramName == null || - isBaseNameGenerated && !isSubstituted && isBaseNameGenerated(codeStyleManager, parameterType, paramName) || - !factory.isValidParameterName(paramName)) { + if (paramName == null + || isBaseNameGenerated && !isSubstituted && isBaseNameGenerated(codeStyleManager, parameterType, paramName) + || !factory.isValidParameterName(paramName)) { String[] names = codeStyleManager.suggestVariableName(VariableKind.PARAMETER, null, null, substituted).names; if (names.length > 0) { paramName = generator.generateUniqueName(names[0]); @@ -569,6 +564,7 @@ else if (!generator.test(paramName)) { return result; } + @RequiredWriteAction private static void substituteThrows( @Nonnull JVMElementFactory factory, @Nonnull PsiReferenceList targetThrowsList, @@ -588,25 +584,26 @@ private static void substituteThrows( @RequiredReadAction private static void copyDocComment(PsiMethod source, PsiMethod target, JVMElementFactory factory) { - final PsiElement navigationElement = source.getNavigationElement(); + PsiElement navigationElement = source.getNavigationElement(); if (navigationElement instanceof PsiDocCommentOwner) { - final PsiDocComment docComment = ((PsiDocCommentOwner)navigationElement).getDocComment(); + PsiDocComment docComment = ((PsiDocCommentOwner)navigationElement).getDocComment(); if (docComment != null) { target.addAfter(factory.createDocCommentFromText(docComment.getText()), null); } } - final PsiParameter[] sourceParameters = source.getParameterList().getParameters(); - final PsiParameterList targetParameterList = target.getParameterList(); - RefactoringUtil.fixJavadocsForParams(target, new HashSet<>(Arrays.asList(targetParameterList.getParameters())), new Condition<>() { - @Override - public boolean value(Pair pair) { - final int parameterIndex = targetParameterList.getParameterIndex(pair.first); + PsiParameter[] sourceParameters = source.getParameterList().getParameters(); + PsiParameterList targetParameterList = target.getParameterList(); + RefactoringUtil.fixJavadocsForParams( + target, + new HashSet<>(Arrays.asList(targetParameterList.getParameters())), + pair -> { + int parameterIndex = targetParameterList.getParameterIndex(pair.first); if (parameterIndex >= 0 && parameterIndex < sourceParameters.length) { return Comparing.strEqual(pair.second, sourceParameters[parameterIndex].getName()); } return false; } - }); + ); } @Nonnull @@ -617,17 +614,18 @@ private static PsiMethod createMethod(@Nonnull JVMElementFactory factory, @Nonnu return factory.createMethod(method.getName(), PsiType.VOID, target); } + @RequiredWriteAction private static void substituteReturnType( @Nonnull PsiManager manager, @Nonnull PsiMethod method, @Nullable PsiType returnType, @Nonnull PsiSubstitutor substitutor ) { - final PsiTypeElement returnTypeElement = method.getReturnTypeElement(); + PsiTypeElement returnTypeElement = method.getReturnTypeElement(); if (returnTypeElement == null || returnType == null) { return; } - final PsiType substitutedReturnType = substituteType(substitutor, returnType, method, method.getModifierList()); + PsiType substitutedReturnType = substituteType(substitutor, returnType, method, method.getModifierList()); returnTypeElement.replace(new LightTypeElement( manager, @@ -638,6 +636,7 @@ private static void substituteReturnType( } @Nonnull + @RequiredReadAction private static JVMElementFactory getFactory(@Nonnull Project p, @Nullable PsiElement target) { return target == null ? JavaPsiFacade.getInstance(p).getElementFactory() @@ -648,14 +647,15 @@ private static boolean isBaseNameGenerated(JavaCodeStyleManager csManager, PsiTy if (Arrays.asList(csManager.suggestVariableName(VariableKind.PARAMETER, null, null, parameterType).names).contains(paramName)) { return true; } - final String typeName = JavaCodeStyleManagerImpl.getTypeName(parameterType); + String typeName = JavaCodeStyleManagerImpl.getTypeName(parameterType); return typeName != null && NameUtil.getSuggestionsByName(typeName, "", "", false, false, parameterType instanceof PsiArrayType).contains(paramName); } + @RequiredWriteAction private static PsiType substituteType( - final PsiSubstitutor substitutor, - final PsiType type, + PsiSubstitutor substitutor, + PsiType type, @Nonnull PsiTypeParameterListOwner owner, PsiModifierList modifierList ) { @@ -665,6 +665,7 @@ private static PsiType substituteType( return substitutedType != null ? AnnotationTargetUtil.keepStrictlyTypeUseAnnotations(modifierList, substitutedType) : null; } + @RequiredReadAction public static boolean isChildInRange(PsiElement child, PsiElement first, PsiElement last) { if (child.equals(first)) { return true; @@ -683,6 +684,7 @@ public static boolean isChildInRange(PsiElement child, PsiElement first, PsiElem } } + @RequiredWriteAction public static void setupGeneratedMethod(PsiMethod method) { PsiClass containingClass = method.getContainingClass(); PsiClass base = containingClass == null ? null : containingClass.getSuperClass(); @@ -711,18 +713,19 @@ public static void setupGeneratedMethod(PsiMethod method) { OverrideImplementUtil.annotateOnOverrideImplement(method, base, overridden); } + @RequiredWriteAction public static void copyOrReplaceModifierList(@Nonnull PsiModifierListOwner sourceParam, @Nonnull PsiModifierListOwner targetParam) { PsiModifierList sourceModifierList = sourceParam.getModifierList(); PsiModifierList targetModifierList = targetParam.getModifierList(); if (sourceModifierList != null && targetModifierList != null) { - final Module module = ModuleUtilCore.findModuleForPsiElement(targetModifierList); - final GlobalSearchScope moduleScope = module != null ? GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module) : null; - final Project project = targetModifierList.getProject(); - final JavaPsiFacade facade = JavaPsiFacade.getInstance(project); + Module module = targetModifierList.getModule(); + GlobalSearchScope moduleScope = module != null ? GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module) : null; + Project project = targetModifierList.getProject(); + JavaPsiFacade facade = JavaPsiFacade.getInstance(project); JVMElementFactory factory = JVMElementFactories.requireFactory(targetParam.getLanguage(), targetParam.getProject()); for (PsiAnnotation annotation : AnnotationUtil.getAllAnnotations(sourceParam, false, null, false)) { - final String qualifiedName = annotation.getQualifiedName(); + String qualifiedName = annotation.getQualifiedName(); if (qualifiedName != null && (moduleScope == null || facade.findClass(qualifiedName, moduleScope) != null) && !AnnotationTargetUtil.isTypeAnnotation(annotation)) { targetModifierList.add(factory.createAnnotationFromText(annotation.getText(), sourceParam)); @@ -760,21 +763,24 @@ private static void filterAnnotations(Project project, PsiModifierList modifierL } //java bean getters/setters + @RequiredWriteAction public static PsiMethod generateSimpleGetterPrototype(@Nonnull PsiField field) { return generatePrototype(field, PropertyUtil.generateGetterPrototype(field)); } + @RequiredWriteAction public static PsiMethod generateSimpleSetterPrototype(@Nonnull PsiField field) { return generatePrototype(field, PropertyUtil.generateSetterPrototype(field)); } + @RequiredWriteAction public static PsiMethod generateSimpleSetterPrototype(PsiField field, PsiClass targetClass) { return generatePrototype(field, PropertyUtil.generateSetterPrototype(field, targetClass)); } //custom getters/setters public static String suggestGetterName(PsiField field) { - final PsiMethod prototype = generateGetterPrototype(field); + PsiMethod prototype = generateGetterPrototype(field); return prototype != null ? prototype.getName() : PropertyUtil.suggestGetterName(field); } @@ -786,7 +792,7 @@ public static String suggestGetterName(String name, PsiType type, Project projec } public static String suggestSetterName(PsiField field) { - final PsiMethod prototype = generateSetterPrototype(field); + PsiMethod prototype = generateSetterPrototype(field); return prototype != null ? prototype.getName() : PropertyUtil.suggestSetterName(field); } @@ -797,18 +803,22 @@ public static String suggestSetterName(String name, PsiType type, Project projec )); } + @RequiredWriteAction public static PsiMethod generateGetterPrototype(@Nonnull PsiField field) { return generateGetterPrototype(field, true); } + @RequiredWriteAction public static PsiMethod generateSetterPrototype(@Nonnull PsiField field) { return generateSetterPrototype(field, true); } + @RequiredWriteAction public static PsiMethod generateSetterPrototype(@Nonnull PsiField field, PsiClass aClass) { return generatePrototype(field, aClass, true, SetterTemplatesManager.getInstance()); } + @RequiredWriteAction static PsiMethod generateGetterPrototype(@Nonnull PsiField field, boolean ignoreInvalidTemplate) { return generatePrototype(field, field.getContainingClass(), ignoreInvalidTemplate, GetterTemplatesManager.getInstance()); } @@ -817,6 +827,7 @@ static PsiMethod generateSetterPrototype(@Nonnull PsiField field, boolean ignore return generatePrototype(field, field.getContainingClass(), ignoreInvalidTemplate, SetterTemplatesManager.getInstance()); } + @RequiredWriteAction private static PsiMethod generatePrototype( @Nonnull PsiField field, PsiClass psiClass, @@ -851,7 +862,7 @@ private static PsiMethod generatePrototype( annotationTarget = result; } else { - final PsiParameter[] parameters = result.getParameterList().getParameters(); + PsiParameter[] parameters = result.getParameterList().getParameters(); annotationTarget = parameters.length == 1 ? parameters[0] : null; } if (annotationTarget != null) { @@ -862,11 +873,13 @@ private static PsiMethod generatePrototype( } @Nullable + @RequiredWriteAction private static PsiMethod generatePrototype(@Nonnull PsiField field, PsiMethod result) { return setVisibility(field, annotateOnOverrideImplement(field.getContainingClass(), result)); } @Contract("_, null -> null") + @RequiredWriteAction public static PsiMethod setVisibility(PsiMember member, PsiMethod prototype) { if (prototype == null) { return null; @@ -889,6 +902,7 @@ public static PsiMethod setVisibility(PsiMember member, PsiMethod prototype) { } @Nullable + @RequiredWriteAction public static PsiMethod annotateOnOverrideImplement(@Nullable PsiClass targetClass, @Nullable PsiMethod generated) { if (generated == null || targetClass == null) { return generated; diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/GetterSetterPrototypeProvider.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/GetterSetterPrototypeProvider.java index a35ca61da9..d228d45049 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/GetterSetterPrototypeProvider.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/GetterSetterPrototypeProvider.java @@ -26,8 +26,8 @@ import consulo.component.extension.Extensions; /** - * User: anna - * Date: 3/4/13 + * @author anna + * @since 2013-03-04 */ @ExtensionAPI(ComponentScope.APPLICATION) public abstract class GetterSetterPrototypeProvider { @@ -83,13 +83,13 @@ public static boolean isReadOnlyProperty(PsiField field) { public static PsiMethod[] findGetters(PsiClass aClass, String propertyName, boolean isStatic) { if (!isStatic) { for (GetterSetterPrototypeProvider provider : Extensions.getExtensions(EP_NAME)) { - final PsiMethod[] getterSetter = provider.findGetters(aClass, propertyName); + PsiMethod[] getterSetter = provider.findGetters(aClass, propertyName); if (getterSetter != null) { return getterSetter; } } } - final PsiMethod propertyGetterSetter = PropertyUtil.findPropertyGetter(aClass, propertyName, isStatic, false); + PsiMethod propertyGetterSetter = PropertyUtil.findPropertyGetter(aClass, propertyName, isStatic, false); if (propertyGetterSetter != null) { return new PsiMethod[]{propertyGetterSetter}; } diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/OverrideImplementUtil.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/OverrideImplementUtil.java index be31bf9fcc..6a69cc5960 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/OverrideImplementUtil.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/OverrideImplementUtil.java @@ -29,11 +29,11 @@ import com.intellij.java.language.psi.javadoc.PsiDocComment; import com.intellij.java.language.psi.util.*; import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; import consulo.application.Application; import consulo.application.Result; import consulo.codeEditor.Editor; import consulo.codeEditor.ScrollType; -import consulo.component.extension.Extensions; import consulo.externalService.statistic.FeatureUsageTracker; import consulo.fileEditor.FileEditorManager; import consulo.fileTemplate.FileTemplate; @@ -54,11 +54,11 @@ import consulo.language.psi.scope.GlobalSearchScope; import consulo.language.psi.util.PsiTreeUtil; import consulo.language.util.IncorrectOperationException; -import consulo.language.util.ModuleUtilCore; import consulo.logging.Logger; import consulo.module.Module; import consulo.navigation.OpenFileDescriptorFactory; import consulo.project.Project; +import consulo.ui.annotation.RequiredUIAccess; import consulo.ui.ex.action.KeyboardShortcut; import consulo.ui.ex.action.Shortcut; import consulo.ui.ex.awt.DialogWrapper; @@ -71,7 +71,6 @@ import consulo.virtualFileSystem.fileType.FileType; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; -import org.jetbrains.annotations.NonNls; import javax.swing.*; import java.awt.event.ActionEvent; @@ -99,12 +98,14 @@ protected static List getImplementors() { * @return list of method prototypes */ @Nonnull + @RequiredWriteAction public static List overrideOrImplementMethod(PsiClass aClass, PsiMethod method, boolean toCopyJavaDoc) throws IncorrectOperationException { - final PsiClass containingClass = method.getContainingClass(); + PsiClass containingClass = method.getContainingClass(); LOG.assertTrue(containingClass != null); PsiSubstitutor substitutor = aClass.isInheritor(containingClass, true) - ? TypeConversionUtil.getSuperClassSubstitutor(containingClass, aClass, PsiSubstitutor.EMPTY) : PsiSubstitutor.EMPTY; + ? TypeConversionUtil.getSuperClassSubstitutor(containingClass, aClass, PsiSubstitutor.EMPTY) + : PsiSubstitutor.EMPTY; return overrideOrImplementMethod( aClass, method, @@ -114,13 +115,15 @@ public static List overrideOrImplementMethod(PsiClass aClass, PsiMeth ); } + @RequiredReadAction public static boolean isInsertOverride(PsiMethod superMethod, PsiClass targetClass) { return CodeStyleSettingsManager.getSettings(targetClass.getProject()).INSERT_OVERRIDE_ANNOTATION && canInsertOverride(superMethod, targetClass); } + @RequiredReadAction public static boolean canInsertOverride(PsiMethod superMethod, PsiClass targetClass) { - if (superMethod.isConstructor() || superMethod.hasModifierProperty(PsiModifier.STATIC)) { + if (superMethod.isConstructor() || superMethod.isStatic()) { return false; } if (!PsiUtil.isLanguageLevel5OrHigher(targetClass)) { @@ -133,6 +136,7 @@ public static boolean canInsertOverride(PsiMethod superMethod, PsiClass targetCl return superClass != null && !superClass.isInterface(); } + @RequiredWriteAction public static List overrideOrImplementMethod( PsiClass aClass, PsiMethod method, @@ -145,8 +149,8 @@ public static List overrideOrImplementMethod( } List results = new ArrayList<>(); - for (final MethodImplementor implementor : getImplementors()) { - final PsiMethod[] prototypes = implementor.createImplementationPrototypes(aClass, method); + for (MethodImplementor implementor : getImplementors()) { + PsiMethod[] prototypes = implementor.createImplementationPrototypes(aClass, method); for (PsiMethod prototype : prototypes) { implementor.createDecorator(aClass, method, toCopyJavaDoc, insertOverrideIfPossible).accept(prototype); results.add(prototype); @@ -186,14 +190,15 @@ public static List overrideOrImplementMethod( } public static Consumer createDefaultDecorator( - final PsiClass aClass, - final PsiMethod method, - final boolean toCopyJavaDoc, - final boolean insertOverrideIfPossible + PsiClass aClass, + PsiMethod method, + boolean toCopyJavaDoc, + boolean insertOverrideIfPossible ) { return result -> decorateMethod(aClass, method, toCopyJavaDoc, insertOverrideIfPossible, result); } + @RequiredWriteAction private static PsiMethod decorateMethod( PsiClass aClass, PsiMethod method, @@ -201,7 +206,7 @@ private static PsiMethod decorateMethod( boolean insertOverrideIfPossible, PsiMethod result ) { - PsiUtil.setModifierProperty(result, PsiModifier.ABSTRACT, aClass.isInterface() && method.hasModifierProperty(PsiModifier.ABSTRACT)); + PsiUtil.setModifierProperty(result, PsiModifier.ABSTRACT, aClass.isInterface() && method.isAbstract()); PsiUtil.setModifierProperty(result, PsiModifier.NATIVE, false); if (!toCopyJavaDoc) { @@ -209,9 +214,9 @@ private static PsiMethod decorateMethod( } //method type params are not allowed when overriding from raw type - final PsiTypeParameterList list = result.getTypeParameterList(); + PsiTypeParameterList list = result.getTypeParameterList(); if (list != null) { - final PsiClass containingClass = method.getContainingClass(); + PsiClass containingClass = method.getContainingClass(); if (containingClass != null) { for (PsiClassType classType : aClass.getSuperTypes()) { if (InheritanceUtil.isInheritorOrSelf(PsiUtil.resolveClassInType(classType), containingClass, true) @@ -230,7 +235,7 @@ private static PsiMethod decorateMethod( result.getModifierList().setModifierProperty(PsiModifier.SYNCHRONIZED, true); } - final PsiCodeBlock body = JavaPsiFacade.getInstance(method.getProject()).getElementFactory().createCodeBlockFromText("{}", null); + PsiCodeBlock body = JavaPsiFacade.getInstance(method.getProject()).getElementFactory().createCodeBlockFromText("{}", null); PsiCodeBlock oldBody = result.getBody(); if (oldBody != null) { oldBody.replace(body); @@ -242,7 +247,7 @@ private static PsiMethod decorateMethod( setupMethodBody(result, method, aClass); // probably, it's better to reformat the whole method - it can go from other style sources - final Project project = method.getProject(); + Project project = method.getProject(); CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project); CommonCodeStyleSettings javaSettings = CodeStyleSettingsManager.getSettings(project).getCommonSettings(JavaLanguage.INSTANCE); boolean keepBreaks = javaSettings.KEEP_LINE_BREAKS; @@ -260,6 +265,7 @@ public static void deleteDocComment(PsiMethod result) { } } + @RequiredWriteAction public static void annotateOnOverrideImplement(PsiMethod method, PsiClass targetClass, PsiMethod overridden) { annotateOnOverrideImplement( method, @@ -269,18 +275,19 @@ public static void annotateOnOverrideImplement(PsiMethod method, PsiClass target ); } + @RequiredWriteAction public static void annotateOnOverrideImplement(PsiMethod method, PsiClass targetClass, PsiMethod overridden, boolean insertOverride) { if (insertOverride && canInsertOverride(overridden, targetClass)) { - final String overrideAnnotationName = Override.class.getName(); + String overrideAnnotationName = Override.class.getName(); if (!AnnotationUtil.isAnnotated(method, overrideAnnotationName, false, true)) { - AddAnnotationPsiFix.addPhysicalAnnotation(overrideAnnotationName, PsiNameValuePair.EMPTY_ARRAY, method.getModifierList()); + AddAnnotationPsiFix.addPhysicalAnnotationTo(overrideAnnotationName, PsiNameValuePair.EMPTY_ARRAY, method.getModifierList()); } } - final Module module = ModuleUtilCore.findModuleForPsiElement(targetClass); - final GlobalSearchScope moduleScope = module != null ? GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module) : null; - final Project project = targetClass.getProject(); - final JavaPsiFacade facade = JavaPsiFacade.getInstance(project); - for (OverrideImplementsAnnotationsHandler each : Extensions.getExtensions(OverrideImplementsAnnotationsHandler.EP_NAME)) { + Module module = targetClass.getModule(); + GlobalSearchScope moduleScope = module != null ? GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module) : null; + Project project = targetClass.getProject(); + JavaPsiFacade facade = JavaPsiFacade.getInstance(project); + for (OverrideImplementsAnnotationsHandler each : OverrideImplementsAnnotationsHandler.EP_NAME.getExtensions()) { for (String annotation : each.getAnnotations(project)) { if (moduleScope != null && facade.findClass(annotation, moduleScope) == null) { continue; @@ -293,7 +300,7 @@ public static void annotateOnOverrideImplement(PsiMethod method, PsiClass target } AddAnnotationPsiFix.removePhysicalAnnotations(method, each.annotationsToRemove(project, annotation)); - AddAnnotationPsiFix.addPhysicalAnnotation(annotation, PsiNameValuePair.EMPTY_ARRAY, method.getModifierList()); + AddAnnotationPsiFix.addPhysicalAnnotationTo(annotation, PsiNameValuePair.EMPTY_ARRAY, method.getModifierList()); } } } @@ -308,6 +315,7 @@ public static void annotate(@Nonnull PsiMethod result, String fqn, String... ann } @Nonnull + @RequiredWriteAction public static List> overrideOrImplementMethods( PsiClass aClass, Collection candidates, @@ -315,11 +323,12 @@ public static List> overrideOrImplementMethods( boolean toInsertAtOverride ) throws IncorrectOperationException { List candidateInfos = ContainerUtil.map2List(candidates, s -> new CandidateInfo(s.getElement(), s.getSubstitutor())); - final List methods = overrideOrImplementMethodCandidates(aClass, candidateInfos, toCopyJavaDoc, toInsertAtOverride); + List methods = overrideOrImplementMethodCandidates(aClass, candidateInfos, toCopyJavaDoc, toInsertAtOverride); return convert2GenerationInfos(methods); } @Nonnull + @RequiredWriteAction public static List overrideOrImplementMethodCandidates( PsiClass aClass, Collection candidates, @@ -339,7 +348,7 @@ public static List overrideOrImplementMethodCandidates( return result; } - public static List> convert2GenerationInfos(final Collection methods) { + public static List> convert2GenerationInfos(Collection methods) { return ContainerUtil.map2List(methods, OverrideImplementUtil::createGenerationInfo); } @@ -349,7 +358,7 @@ public static PsiGenerationInfo createGenerationInfo(PsiMethod s) { public static PsiGenerationInfo createGenerationInfo(PsiMethod s, boolean mergeIfExists) { for (MethodImplementor implementor : getImplementors()) { - final GenerationInfo info = implementor.createGenerationInfo(s, mergeIfExists); + GenerationInfo info = implementor.createGenerationInfo(s, mergeIfExists); if (info instanceof PsiGenerationInfo generationInfo) { //noinspection unchecked return generationInfo; @@ -360,7 +369,7 @@ public static PsiGenerationInfo createGenerationInfo(PsiMethod s, boo @Nonnull public static String callSuper(PsiMethod superMethod, PsiMethod overriding) { - @NonNls StringBuilder buffer = new StringBuilder(); + StringBuilder buffer = new StringBuilder(); if (!superMethod.isConstructor() && !PsiType.VOID.equals(superMethod.getReturnType())) { buffer.append("return "); } @@ -382,28 +391,25 @@ public static String callSuper(PsiMethod superMethod, PsiMethod overriding) { return buffer.toString(); } + @RequiredWriteAction public static void setupMethodBody(PsiMethod result, PsiMethod originalMethod, PsiClass targetClass) throws IncorrectOperationException { - boolean isAbstract = - originalMethod.hasModifierProperty(PsiModifier.ABSTRACT) || originalMethod.hasModifierProperty(PsiModifier.DEFAULT); + boolean isAbstract = originalMethod.isAbstract() || originalMethod.hasModifierProperty(PsiModifier.DEFAULT); String templateName = isAbstract ? JavaTemplateUtil.TEMPLATE_IMPLEMENTED_METHOD_BODY : JavaTemplateUtil.TEMPLATE_OVERRIDDEN_METHOD_BODY; FileTemplate template = FileTemplateManager.getInstance(result.getProject()).getCodeTemplate(templateName); setupMethodBody(result, originalMethod, targetClass, template); } - public static void setupMethodBody( - final PsiMethod result, - final PsiMethod originalMethod, - final PsiClass targetClass, - final FileTemplate template - ) throws IncorrectOperationException { + @RequiredWriteAction + public static void setupMethodBody(PsiMethod result, PsiMethod originalMethod, PsiClass targetClass, FileTemplate template) + throws IncorrectOperationException { if (targetClass.isInterface()) { if (isImplementInterfaceInJava8Interface(targetClass) || originalMethod.hasModifierProperty(PsiModifier.DEFAULT)) { PsiUtil.setModifierProperty(result, PsiModifier.DEFAULT, true); } else { - final PsiCodeBlock body = result.getBody(); + PsiCodeBlock body = result.getBody(); if (body != null) { body.delete(); } @@ -424,7 +430,7 @@ public static void setupMethodBody( if (factory == null) { factory = JavaPsiFacade.getInstance(originalMethod.getProject()).getElementFactory(); } - @NonNls String methodText; + String methodText; try { methodText = "void foo () {\n" + template.getText(properties) + "\n}"; @@ -439,7 +445,7 @@ public static void setupMethodBody( m = factory.createMethodFromText(methodText, originalMethod); } catch (IncorrectOperationException e) { - Application.get().invokeLater(() -> Messages.showErrorDialog( + targetClass.getApplication().invokeLater(() -> Messages.showErrorDialog( CodeInsightLocalize.overrideImplementBrokenFileTemplateMessage().get(), CodeInsightLocalize.overrideImplementBrokenFileTemplateTitle().get() )); @@ -452,6 +458,7 @@ public static void setupMethodBody( } } + @RequiredReadAction private static boolean isImplementInterfaceInJava8Interface(PsiClass targetClass) { if (!PsiUtil.isLanguageLevel8OrHigher(targetClass)) { return false; @@ -460,38 +467,34 @@ private static boolean isImplementInterfaceInJava8Interface(PsiClass targetClass return commandName != null && StringUtil.containsIgnoreCase(commandName, "implement"); } - @RequiredReadAction + @RequiredUIAccess public static void chooseAndOverrideMethods(Project project, Editor editor, PsiClass aClass) { FeatureUsageTracker.getInstance().triggerFeatureUsed(ProductivityFeatureNames.CODEASSISTS_OVERRIDE_IMPLEMENT); chooseAndOverrideOrImplementMethods(project, editor, aClass, false); } - @RequiredReadAction + @RequiredUIAccess public static void chooseAndImplementMethods(Project project, Editor editor, PsiClass aClass) { FeatureUsageTracker.getInstance().triggerFeatureUsed(ProductivityFeatureNames.CODEASSISTS_OVERRIDE_IMPLEMENT); chooseAndOverrideOrImplementMethods(project, editor, aClass, true); } - @RequiredReadAction - public static void chooseAndOverrideOrImplementMethods( - final Project project, - final Editor editor, - final PsiClass aClass, - final boolean toImplement - ) { + @RequiredUIAccess + public static void chooseAndOverrideOrImplementMethods(Project project, Editor editor, PsiClass aClass, boolean toImplement) { LOG.assertTrue(aClass.isValid()); project.getApplication().assertReadAccessAllowed(); Collection candidates = getMethodsToOverrideImplement(aClass, toImplement); - Collection secondary = toImplement || aClass.isInterface() ? new ArrayList<>() + Collection secondary = toImplement || aClass.isInterface() + ? new ArrayList<>() : getMethodsToOverrideImplement(aClass, true); - final MemberChooser chooser = showOverrideImplementChooser(editor, aClass, toImplement, candidates, secondary); + MemberChooser chooser = showOverrideImplementChooser(editor, aClass, toImplement, candidates, secondary); if (chooser == null) { return; } - final List selectedElements = chooser.getSelectedElements(); + List selectedElements = chooser.getSelectedElements(); if (selectedElements == null || selectedElements.isEmpty()) { return; } @@ -499,7 +502,8 @@ public static void chooseAndOverrideOrImplementMethods( LOG.assertTrue(aClass.isValid()); new WriteCommandAction(project, aClass.getContainingFile()) { @Override - protected void run(@Nonnull final Result result) throws Throwable { + @RequiredUIAccess + protected void run(@Nonnull Result result) throws Throwable { overrideOrImplementMethodsInRightPlace( editor, aClass, @@ -515,25 +519,26 @@ protected void run(@Nonnull final Result result) throws Throwable { * @param candidates, secondary should allow modifications */ @Nullable + @RequiredUIAccess public static MemberChooser showOverrideImplementChooser( Editor editor, - final PsiElement aClass, - final boolean toImplement, - final Collection candidates, + PsiElement aClass, + boolean toImplement, + Collection candidates, Collection secondary ) { if (toImplement) { for (Iterator iterator = candidates.iterator(); iterator.hasNext(); ) { CandidateInfo candidate = iterator.next(); PsiElement element = candidate.getElement(); - if (element instanceof PsiMethod && ((PsiMethod)element).hasModifierProperty(PsiModifier.DEFAULT)) { + if (element instanceof PsiMethod method && method.hasModifierProperty(PsiModifier.DEFAULT)) { iterator.remove(); secondary.add(candidate); } } } - final JavaOverrideImplementMemberChooser chooser = + JavaOverrideImplementMemberChooser chooser = JavaOverrideImplementMemberChooser.create(aClass, toImplement, candidates, secondary); if (chooser == null) { return null; @@ -552,30 +557,31 @@ public static MemberChooser showOverrideImplementChooser( return chooser; } + @RequiredUIAccess private static void registerHandlerForComplementaryAction( - final Project project, - final Editor editor, - final PsiElement aClass, - final boolean toImplement, - final MemberChooser chooser + Project project, + Editor editor, + PsiElement aClass, + boolean toImplement, + MemberChooser chooser ) { - final JComponent preferredFocusedComponent = chooser.getPreferredFocusedComponent(); - final Keymap keymap = KeymapManager.getInstance().getActiveKeymap(); + JComponent preferredFocusedComponent = chooser.getPreferredFocusedComponent(); + Keymap keymap = KeymapManager.getInstance().getActiveKeymap(); - @NonNls final String s = toImplement ? "OverrideMethods" : "ImplementMethods"; - final Shortcut[] shortcuts = keymap.getShortcuts(s); + String s = toImplement ? "OverrideMethods" : "ImplementMethods"; + Shortcut[] shortcuts = keymap.getShortcuts(s); if (shortcuts.length > 0 && shortcuts[0] instanceof KeyboardShortcut keyboardShortcut) { preferredFocusedComponent.getInputMap().put(keyboardShortcut.getFirstKeyStroke(), s); preferredFocusedComponent.getActionMap().put(s, new AbstractAction() { @Override - public void actionPerformed(final ActionEvent e) { + public void actionPerformed(ActionEvent e) { chooser.close(DialogWrapper.CANCEL_EXIT_CODE); // invoke later in order to close previous modal dialog project.getApplication().invokeLater(() -> { - final CodeInsightActionHandler handler = + CodeInsightActionHandler handler = toImplement ? new JavaOverrideMethodsHandler() : new JavaImplementMethodsHandler(); handler.invoke(project, editor, aClass.getContainingFile()); }); @@ -584,7 +590,7 @@ public void actionPerformed(final ActionEvent e) { } } - @RequiredReadAction + @RequiredUIAccess public static void overrideOrImplementMethodsInRightPlace( Editor editor, PsiClass aClass, @@ -682,13 +688,12 @@ public static PsiElement getDefaultAnchorToOverrideOrImplement(PsiClass aClass, return null; } - @RequiredReadAction + @RequiredWriteAction public static List> overrideOrImplement(PsiClass psiClass, @Nonnull PsiMethod baseMethod) throws IncorrectOperationException { FileEditorManager fileEditorManager = FileEditorManager.getInstance(baseMethod.getProject()); List> results = new ArrayList<>(); try { - List> prototypes = convert2GenerationInfos(overrideOrImplementMethod(psiClass, baseMethod, false)); if (prototypes.isEmpty()) { return null; @@ -726,14 +731,14 @@ public static PsiClass getContextClass(Project project, Editor editor, PsiFile f } while (element instanceof PsiTypeParameter); - final PsiClass aClass = (PsiClass)element; + PsiClass aClass = (PsiClass)element; if (aClass instanceof PsiSyntheticClass) { return null; } return aClass == null || !allowInterface && aClass.isInterface() ? null : aClass; } - @RequiredReadAction + @RequiredUIAccess public static void overrideOrImplementMethodsInRightPlace( Editor editor1, PsiClass aClass, @@ -744,6 +749,7 @@ public static void overrideOrImplementMethodsInRightPlace( overrideOrImplementMethodsInRightPlace(editor1, aClass, members, copyJavadoc, insert); } + @RequiredUIAccess public static List overrideOrImplementMethodCandidates( PsiClass aClass, Collection candidatesToImplement, diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/surroundWith/JavaExpressionSurroundDescriptor.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/surroundWith/JavaExpressionSurroundDescriptor.java index ba7f2b7ef7..e0d2e113da 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/surroundWith/JavaExpressionSurroundDescriptor.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/generation/surroundWith/JavaExpressionSurroundDescriptor.java @@ -19,6 +19,7 @@ import com.intellij.java.impl.refactoring.introduceVariable.IntroduceVariableBase; import com.intellij.java.language.JavaLanguage; import com.intellij.java.language.psi.PsiExpression; +import consulo.annotation.access.RequiredReadAction; import consulo.annotation.component.ExtensionImpl; import consulo.externalService.statistic.FeatureUsageTracker; import consulo.language.Language; @@ -49,8 +50,9 @@ public class JavaExpressionSurroundDescriptor implements SurroundDescriptor { new JavaWithNullCheckSurrounder() }; - @Override @Nonnull + @Override + @RequiredReadAction public PsiElement[] getElementsToSurround(PsiFile file, int startOffset, int endOffset) { PsiExpression expr = CodeInsightUtil.findExpressionInRange(file, startOffset, endOffset); if (expr == null) { @@ -67,7 +69,7 @@ public PsiElement[] getElementsToSurround(PsiFile file, int startOffset, int end @Nonnull public Surrounder[] getSurrounders() { if (mySurrounders == null) { - final ArrayList list = new ArrayList(); + ArrayList list = new ArrayList<>(); Collections.addAll(list, SURROUNDERS); Collections.addAll(list, JavaExpressionSurrounder.EP_NAME.getExtensions()); mySurrounders = list.toArray(new Surrounder[list.size()]); diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInsight/template/macro/VariableTypeCalculator.java b/plugin/src/main/java/com/intellij/java/impl/codeInsight/template/macro/VariableTypeCalculator.java index ade58207fa..a91415bdd1 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInsight/template/macro/VariableTypeCalculator.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInsight/template/macro/VariableTypeCalculator.java @@ -29,8 +29,7 @@ */ @ExtensionAPI(ComponentScope.APPLICATION) public abstract class VariableTypeCalculator { - public static final ExtensionPointName EP_NAME = - ExtensionPointName.create(VariableTypeCalculator.class); + public static final ExtensionPointName EP_NAME = ExtensionPointName.create(VariableTypeCalculator.class); @Nullable public abstract PsiType inferVarTypeAt(@Nonnull PsiVariable var, @Nonnull PsiElement place); @@ -41,7 +40,7 @@ public abstract class VariableTypeCalculator { @Nonnull public static PsiType getVarTypeAt(@Nonnull PsiVariable var, @Nonnull PsiElement place) { for (VariableTypeCalculator calculator : EP_NAME.getExtensionList()) { - final PsiType type = calculator.inferVarTypeAt(var, place); + PsiType type = calculator.inferVarTypeAt(var, place); if (type != null) { return type; } diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInspection/uncheckedWarnings/UncheckedWarningLocalInspectionBase.java b/plugin/src/main/java/com/intellij/java/impl/codeInspection/uncheckedWarnings/UncheckedWarningLocalInspectionBase.java index ac9dc8a847..154345adc6 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInspection/uncheckedWarnings/UncheckedWarningLocalInspectionBase.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInspection/uncheckedWarnings/UncheckedWarningLocalInspectionBase.java @@ -22,11 +22,12 @@ import com.intellij.java.analysis.impl.codeInsight.quickfix.ChangeVariableTypeQuickFixProvider; import com.intellij.java.language.LanguageLevel; import com.intellij.java.language.codeInsight.daemon.impl.analysis.JavaGenericsUtil; -import com.intellij.java.language.impl.codeInsight.daemon.JavaErrorBundle; import com.intellij.java.language.projectRoots.JavaSdkVersion; import com.intellij.java.language.projectRoots.JavaVersionService; import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.util.*; +import consulo.annotation.access.RequiredReadAction; +import consulo.java.language.impl.localize.JavaErrorLocalize; import consulo.language.editor.inspection.LocalInspectionToolSession; import consulo.language.editor.inspection.LocalQuickFix; import consulo.language.editor.inspection.ProblemsHolder; @@ -35,14 +36,13 @@ import consulo.language.psi.PsiElement; import consulo.language.psi.PsiElementVisitor; import consulo.language.psi.util.PsiTreeUtil; +import consulo.localize.LocalizeValue; import consulo.logging.Logger; import consulo.util.xml.serializer.WriteExternalException; +import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; import org.intellij.lang.annotations.Pattern; import org.jdom.Element; -import org.jetbrains.annotations.NonNls; - -import jakarta.annotation.Nonnull; import javax.swing.*; import java.util.ArrayList; @@ -51,9 +51,7 @@ import java.util.function.Consumer; public abstract class UncheckedWarningLocalInspectionBase extends BaseJavaBatchLocalInspectionTool { - @NonNls public static final String SHORT_NAME = "UNCHECKED_WARNING"; - @NonNls private static final String ID = "unchecked"; private static final Logger LOG = Logger.getInstance(UncheckedWarningLocalInspectionBase.class); public boolean IGNORE_UNCHECKED_ASSIGNMENT; @@ -63,8 +61,8 @@ public abstract class UncheckedWarningLocalInspectionBase extends BaseJavaBatchL public boolean IGNORE_UNCHECKED_OVERRIDING; @Nonnull - static JCheckBox createSetting(@Nonnull String cbText, final boolean option, @Nonnull Consumer pass) { - final JCheckBox uncheckedCb = new JCheckBox(cbText, option); + static JCheckBox createSetting(@Nonnull String cbText, boolean option, @Nonnull Consumer pass) { + JCheckBox uncheckedCb = new JCheckBox(cbText, option); uncheckedCb.addActionListener(e -> pass.accept(uncheckedCb)); return uncheckedCb; } @@ -79,12 +77,12 @@ private static LocalQuickFix[] getChangeVariableTypeFixes( return generifyFixes; } LOG.assertTrue(parameter.isValid()); - final List result = new ArrayList<>(); + List result = new ArrayList<>(); if (itemType != null) { for (ChangeVariableTypeQuickFixProvider fixProvider : ChangeVariableTypeQuickFixProvider.EP_NAME.getExtensionList()) { for (IntentionAction action : fixProvider.getFixes(parameter, itemType)) { - if (action instanceof LocalQuickFix) { - result.add((LocalQuickFix)action); + if (action instanceof LocalQuickFix fix) { + result.add(fix); } } } @@ -108,17 +106,15 @@ public String getDisplayName() { return InspectionLocalize.uncheckedWarning().get(); } - @Override @Nonnull - @NonNls + @Override public String getShortName() { return SHORT_NAME; } + @Nonnull @Override @Pattern(VALID_ID_PATTERN) - @Nonnull - @NonNls public String getID() { return ID; } @@ -130,15 +126,17 @@ public boolean isEnabledByDefault() { @Override public void writeSettings(@Nonnull Element node) throws WriteExternalException { - if (IGNORE_UNCHECKED_ASSIGNMENT || IGNORE_UNCHECKED_CALL || IGNORE_UNCHECKED_CAST || IGNORE_UNCHECKED_OVERRIDING || IGNORE_UNCHECKED_GENERICS_ARRAY_CREATION) { + if (IGNORE_UNCHECKED_ASSIGNMENT || IGNORE_UNCHECKED_CALL || IGNORE_UNCHECKED_CAST + || IGNORE_UNCHECKED_OVERRIDING || IGNORE_UNCHECKED_GENERICS_ARRAY_CREATION) { super.writeSettings(node); } } @Nonnull @Override + @RequiredReadAction public PsiElementVisitor buildVisitorImpl( - @Nonnull final ProblemsHolder holder, + @Nonnull ProblemsHolder holder, boolean isOnTheFly, @Nonnull LocalInspectionToolSession session, Object state @@ -150,18 +148,24 @@ public PsiElementVisitor buildVisitorImpl( return new UncheckedWarningsVisitor(isOnTheFly, languageLevel) { @Override + @RequiredReadAction protected void registerProblem( - @Nonnull String message, + @Nonnull LocalizeValue message, @Nullable PsiElement callExpression, @Nonnull PsiElement psiElement, @Nonnull LocalQuickFix[] quickFixes ) { - final String rawExpression = isMethodCalledOnRawType(callExpression); + String rawExpression = isMethodCalledOnRawType(callExpression); if (rawExpression != null) { - final String referenceName = ((PsiMethodCallExpression)callExpression).getMethodExpression().getReferenceName(); - message += ". Reason: '" + rawExpression + "' has raw type, so result of " + referenceName + " is erased"; + String referenceName = ((PsiMethodCallExpression)callExpression).getMethodExpression().getReferenceName(); + message = LocalizeValue.localizeTODO( + message + ". Reason: '" + rawExpression + "' has raw type, so result of " + referenceName + " is erased" + ); } - holder.registerProblem(psiElement, message, quickFixes); + holder.newProblem(message) + .range(psiElement) + .withFixes(quickFixes) + .create(); } }; } @@ -171,19 +175,13 @@ protected LocalQuickFix[] createFixes() { return LocalQuickFix.EMPTY_ARRAY; } + @RequiredReadAction private static String isMethodCalledOnRawType(PsiElement expression) { - if (expression instanceof PsiMethodCallExpression) { - final PsiExpression qualifierExpression = ((PsiMethodCallExpression)expression).getMethodExpression().getQualifierExpression(); - if (qualifierExpression != null) { - final PsiClass qualifierClass = PsiUtil.resolveClassInClassTypeOnly(qualifierExpression.getType()); - if (qualifierClass != null) { - if (PsiUtil.isRawSubstitutor( - qualifierClass, - ((PsiMethodCallExpression)expression).resolveMethodGenerics().getSubstitutor() - )) { - return qualifierExpression.getText(); - } - } + if (expression instanceof PsiMethodCallExpression methodCall + && methodCall.getMethodExpression().getQualifierExpression() instanceof PsiExpression qualifierExpression) { + PsiClass qualifierClass = PsiUtil.resolveClassInClassTypeOnly(qualifierExpression.getType()); + if (qualifierClass != null && PsiUtil.isRawSubstitutor(qualifierClass, methodCall.resolveMethodGenerics().getSubstitutor())) { + return qualifierExpression.getText(); } } return null; @@ -202,38 +200,42 @@ private abstract class UncheckedWarningsVisitor extends JavaElementVisitor { } protected abstract void registerProblem( - @Nonnull String message, + @Nonnull LocalizeValue message, PsiElement callExpression, @Nonnull PsiElement psiElement, @Nonnull LocalQuickFix[] quickFixes ); - @Override - public void visitReferenceExpression(PsiReferenceExpression expression) { + public void visitReferenceExpression(@Nonnull PsiReferenceExpression expression) { if (IGNORE_UNCHECKED_GENERICS_ARRAY_CREATION) { return; } - final JavaResolveResult result = expression.advancedResolve(false); + JavaResolveResult result = expression.advancedResolve(false); if (JavaGenericsUtil.isUncheckedWarning(expression, result, myLanguageLevel)) { - registerProblem("Unchecked generics array creation for varargs parameter", null, expression, LocalQuickFix.EMPTY_ARRAY); + registerProblem( + LocalizeValue.localizeTODO("Unchecked generics array creation for varargs parameter"), + null, + expression, + LocalQuickFix.EMPTY_ARRAY + ); } } @Override - public void visitNewExpression(PsiNewExpression expression) { + public void visitNewExpression(@Nonnull PsiNewExpression expression) { super.visitNewExpression(expression); if (IGNORE_UNCHECKED_GENERICS_ARRAY_CREATION) { return; } - final PsiJavaCodeReferenceElement classReference = expression.getClassOrAnonymousClassReference(); + PsiJavaCodeReferenceElement classReference = expression.getClassOrAnonymousClassReference(); if (classReference != null && JavaGenericsUtil.isUncheckedWarning( classReference, expression.resolveMethodGenerics(), myLanguageLevel )) { registerProblem( - "Unchecked generics array creation for varargs parameter", + LocalizeValue.localizeTODO("Unchecked generics array creation for varargs parameter"), expression, classReference, LocalQuickFix.EMPTY_ARRAY @@ -242,21 +244,21 @@ public void visitNewExpression(PsiNewExpression expression) { } @Override - public void visitTypeCastExpression(PsiTypeCastExpression expression) { + public void visitTypeCastExpression(@Nonnull PsiTypeCastExpression expression) { super.visitTypeCastExpression(expression); if (IGNORE_UNCHECKED_CAST) { return; } - final PsiTypeElement typeElement = expression.getCastType(); + PsiTypeElement typeElement = expression.getCastType(); if (typeElement == null) { return; } - final PsiType castType = typeElement.getType(); - final PsiExpression operand = expression.getOperand(); + PsiType castType = typeElement.getType(); + PsiExpression operand = expression.getOperand(); if (operand == null) { return; } - final PsiType exprType = operand.getType(); + PsiType exprType = operand.getType(); if (exprType == null) { return; } @@ -264,59 +266,58 @@ public void visitTypeCastExpression(PsiTypeCastExpression expression) { return; } if (JavaGenericsUtil.isUncheckedCast(castType, exprType)) { - final String description = JavaErrorBundle.message( - "generics.unchecked.cast", - JavaHighlightUtil.formatType(exprType), - JavaHighlightUtil.formatType(castType) - ); + LocalizeValue description = + JavaErrorLocalize.genericsUncheckedCast(JavaHighlightUtil.formatType(exprType), JavaHighlightUtil.formatType(castType)); registerProblem(description, operand, expression, myGenerifyFixes); } } @Override - public void visitMethodReferenceExpression(PsiMethodReferenceExpression expression) { + @RequiredReadAction + public void visitMethodReferenceExpression(@Nonnull PsiMethodReferenceExpression expression) { super.visitMethodReferenceExpression(expression); if (IGNORE_UNCHECKED_CALL) { return; } - final JavaResolveResult result = expression.advancedResolve(false); - final String description = getUncheckedCallDescription(expression, result); - if (description != null) { - final PsiElement referenceNameElement = expression.getReferenceNameElement(); + JavaResolveResult result = expression.advancedResolve(false); + LocalizeValue description = getUncheckedCallDescription(expression, result); + if (description != LocalizeValue.empty()) { + PsiElement referenceNameElement = expression.getReferenceNameElement(); registerProblem(description, expression, referenceNameElement != null ? referenceNameElement : expression, myGenerifyFixes); } } @Override - public void visitCallExpression(PsiCallExpression callExpression) { + @RequiredReadAction + public void visitCallExpression(@Nonnull PsiCallExpression callExpression) { super.visitCallExpression(callExpression); - final JavaResolveResult result = callExpression.resolveMethodGenerics(); - final String description = getUncheckedCallDescription(callExpression, result); - if (description != null) { + JavaResolveResult result = callExpression.resolveMethodGenerics(); + LocalizeValue description = getUncheckedCallDescription(callExpression, result); + if (description != LocalizeValue.empty()) { if (IGNORE_UNCHECKED_CALL) { return; } - final PsiExpression element = - callExpression instanceof PsiMethodCallExpression ? ((PsiMethodCallExpression)callExpression).getMethodExpression() : callExpression; + PsiExpression element = + callExpression instanceof PsiMethodCallExpression methodCall ? methodCall.getMethodExpression() : callExpression; registerProblem(description, null, element, myGenerifyFixes); } else { if (IGNORE_UNCHECKED_ASSIGNMENT) { return; } - final PsiSubstitutor substitutor = result.getSubstitutor(); - final PsiExpressionList argumentList = callExpression.getArgumentList(); + PsiSubstitutor substitutor = result.getSubstitutor(); + PsiExpressionList argumentList = callExpression.getArgumentList(); if (argumentList != null) { - final PsiMethod method = (PsiMethod)result.getElement(); + PsiMethod method = (PsiMethod)result.getElement(); if (method != null) { - final PsiExpression[] expressions = argumentList.getExpressions(); - final PsiParameter[] parameters = method.getParameterList().getParameters(); + PsiExpression[] expressions = argumentList.getExpressions(); + PsiParameter[] parameters = method.getParameterList().getParameters(); if (parameters.length != 0) { for (int i = 0; i < expressions.length; i++) { PsiParameter parameter = parameters[Math.min(i, parameters.length - 1)]; - final PsiExpression expression = expressions[i]; - final PsiType parameterType = substitutor.substitute(parameter.getType()); - final PsiType expressionType = expression.getType(); + PsiExpression expression = expressions[i]; + PsiType parameterType = substitutor.substitute(parameter.getType()); + PsiType expressionType = expression.getType(); if (expressionType != null) { checkRawToGenericsAssignment( expression, @@ -335,7 +336,7 @@ public void visitCallExpression(PsiCallExpression callExpression) { } @Override - public void visitVariable(PsiVariable variable) { + public void visitVariable(@Nonnull PsiVariable variable) { super.visitVariable(variable); if (IGNORE_UNCHECKED_ASSIGNMENT) { return; @@ -344,7 +345,7 @@ public void visitVariable(PsiVariable variable) { if (initializer == null || initializer instanceof PsiArrayInitializerExpression) { return; } - final PsiType initializerType = initializer.getType(); + PsiType initializerType = initializer.getType(); checkRawToGenericsAssignment( initializer, initializer, @@ -358,25 +359,27 @@ public void visitVariable(PsiVariable variable) { } @Override - public void visitForeachStatement(PsiForeachStatement statement) { + public void visitForeachStatement(@Nonnull PsiForeachStatement statement) { super.visitForeachStatement(statement); if (IGNORE_UNCHECKED_ASSIGNMENT) { return; } - final PsiParameter parameter = statement.getIterationParameter(); - final PsiType parameterType = parameter.getType(); - final PsiExpression iteratedValue = statement.getIteratedValue(); + PsiParameter parameter = statement.getIterationParameter(); + PsiType parameterType = parameter.getType(); + PsiExpression iteratedValue = statement.getIteratedValue(); if (iteratedValue == null) { return; } - final PsiType itemType = JavaGenericsUtil.getCollectionItemType(iteratedValue); - LocalQuickFix[] fixes = - myOnTheFly ? getChangeVariableTypeFixes(parameter, itemType, myGenerifyFixes) : LocalQuickFix.EMPTY_ARRAY; + PsiType itemType = JavaGenericsUtil.getCollectionItemType(iteratedValue); + LocalQuickFix[] fixes = myOnTheFly + ? getChangeVariableTypeFixes(parameter, itemType, myGenerifyFixes) + : LocalQuickFix.EMPTY_ARRAY; checkRawToGenericsAssignment(parameter, iteratedValue, parameterType, itemType, true, fixes); } @Override - public void visitAssignmentExpression(PsiAssignmentExpression expression) { + @RequiredReadAction + public void visitAssignmentExpression(@Nonnull PsiAssignmentExpression expression) { super.visitAssignmentExpression(expression); if (IGNORE_UNCHECKED_ASSIGNMENT) { return; @@ -395,11 +398,8 @@ public void visitAssignmentExpression(PsiAssignmentExpression expression) { return; } PsiVariable leftVar = null; - if (lExpr instanceof PsiReferenceExpression) { - PsiElement element = ((PsiReferenceExpression)lExpr).resolve(); - if (element instanceof PsiVariable) { - leftVar = (PsiVariable)element; - } + if (lExpr instanceof PsiReferenceExpression lRefExpr && lRefExpr.resolve() instanceof PsiVariable var) { + leftVar = var; } checkRawToGenericsAssignment( rExpr, @@ -412,24 +412,23 @@ public void visitAssignmentExpression(PsiAssignmentExpression expression) { } @Override - public void visitArrayInitializerExpression(PsiArrayInitializerExpression arrayInitializer) { + public void visitArrayInitializerExpression(@Nonnull PsiArrayInitializerExpression arrayInitializer) { super.visitArrayInitializerExpression(arrayInitializer); if (IGNORE_UNCHECKED_ASSIGNMENT) { return; } - final PsiType type = arrayInitializer.getType(); - if (!(type instanceof PsiArrayType)) { + PsiType type = arrayInitializer.getType(); + if (!(type instanceof PsiArrayType arrayType)) { return; } - final PsiType componentType = ((PsiArrayType)type).getComponentType(); - + PsiType componentType = arrayType.getComponentType(); boolean arrayTypeFixChecked = false; VariableArrayTypeFix fix = null; - final PsiExpression[] initializers = arrayInitializer.getInitializers(); + PsiExpression[] initializers = arrayInitializer.getInitializers(); for (PsiExpression expression : initializers) { - final PsiType itemType = expression.getType(); + PsiType itemType = expression.getType(); if (itemType == null) { continue; @@ -438,13 +437,12 @@ public void visitArrayInitializerExpression(PsiArrayInitializerExpression arrayI continue; } if (JavaGenericsUtil.isRawToGeneric(componentType, itemType)) { - String description = JavaErrorBundle.message( - "generics.unchecked.assignment", + LocalizeValue description = JavaErrorLocalize.genericsUncheckedAssignment( JavaHighlightUtil.formatType(itemType), JavaHighlightUtil.formatType(componentType) ); if (!arrayTypeFixChecked) { - final PsiType checkResult = JavaHighlightUtil.sameType(initializers); + PsiType checkResult = JavaHighlightUtil.sameType(initializers); fix = checkResult != null ? VariableArrayTypeFix.createFix(arrayInitializer, checkResult) : null; arrayTypeFixChecked = true; } @@ -471,8 +469,7 @@ private void checkRawToGenericsAssignment( return; } if (JavaGenericsUtil.isRawToGeneric(parameterType, itemType)) { - String description = JavaErrorBundle.message( - "generics.unchecked.assignment", + LocalizeValue description = JavaErrorLocalize.genericsUncheckedAssignment( JavaHighlightUtil.formatType(itemType), JavaHighlightUtil.formatType(parameterType) ); @@ -481,15 +478,15 @@ private void checkRawToGenericsAssignment( } @Override - public void visitMethod(PsiMethod method) { + public void visitMethod(@Nonnull PsiMethod method) { super.visitMethod(method); if (IGNORE_UNCHECKED_OVERRIDING) { return; } if (!method.isConstructor()) { List superMethodSignatures = method.getHierarchicalMethodSignature().getSuperSignatures(); - if (!superMethodSignatures.isEmpty() && !method.hasModifierProperty(PsiModifier.STATIC)) { - final MethodSignature signature = method.getSignature(PsiSubstitutor.EMPTY); + if (!superMethodSignatures.isEmpty() && !method.isStatic()) { + MethodSignature signature = method.getSignature(PsiSubstitutor.EMPTY); for (MethodSignatureBackedByPsiMethod superSignature : superMethodSignatures) { PsiMethod baseMethod = superSignature.getMethod(); PsiSubstitutor substitutor = MethodSignatureUtil.getSuperMethodSignatureSubstitutor(signature, superSignature); @@ -499,19 +496,18 @@ public void visitMethod(PsiMethod method) { if (PsiUtil.isRawSubstitutor(baseMethod, superSignature.getSubstitutor())) { continue; } - final PsiType baseReturnType = substitutor.substitute(baseMethod.getReturnType()); - final PsiType overriderReturnType = method.getReturnType(); + PsiType baseReturnType = substitutor.substitute(baseMethod.getReturnType()); + PsiType overriderReturnType = method.getReturnType(); if (baseReturnType == null || overriderReturnType == null) { return; } if (JavaGenericsUtil.isRawToGeneric(baseReturnType, overriderReturnType)) { - final String message = JavaErrorBundle.message( - "unchecked.overriding.incompatible.return.type", + LocalizeValue message = JavaErrorLocalize.uncheckedOverridingIncompatibleReturnType( JavaHighlightUtil.formatType(overriderReturnType), JavaHighlightUtil.formatType(baseReturnType) ); - final PsiTypeElement returnTypeElement = method.getReturnTypeElement(); + PsiTypeElement returnTypeElement = method.getReturnTypeElement(); LOG.assertTrue(returnTypeElement != null); registerProblem(message, null, returnTypeElement, LocalQuickFix.EMPTY_ARRAY); } @@ -521,21 +517,21 @@ public void visitMethod(PsiMethod method) { } @Override - public void visitReturnStatement(PsiReturnStatement statement) { + public void visitReturnStatement(@Nonnull PsiReturnStatement statement) { super.visitReturnStatement(statement); if (IGNORE_UNCHECKED_ASSIGNMENT) { return; } - final PsiType returnType = PsiTypesUtil.getMethodReturnType(statement); + PsiType returnType = PsiTypesUtil.getMethodReturnType(statement); if (returnType != null && !PsiType.VOID.equals(returnType)) { - final PsiExpression returnValue = statement.getReturnValue(); + PsiExpression returnValue = statement.getReturnValue(); if (returnValue != null) { - final PsiType valueType = returnValue.getType(); + PsiType valueType = returnValue.getType(); if (valueType != null) { - final PsiElement psiElement = PsiTreeUtil.getParentOfType(statement, PsiMethod.class, PsiLambdaExpression.class); - LocalQuickFix[] fixes = psiElement instanceof PsiMethod - ? new LocalQuickFix[]{QuickFixFactory.getInstance().createMethodReturnFix((PsiMethod)psiElement, valueType, true)} : - LocalQuickFix.EMPTY_ARRAY; + PsiElement psiElement = PsiTreeUtil.getParentOfType(statement, PsiMethod.class, PsiLambdaExpression.class); + LocalQuickFix[] fixes = psiElement instanceof PsiMethod method + ? new LocalQuickFix[]{QuickFixFactory.getInstance().createMethodReturnFix(method, valueType, true)} + : LocalQuickFix.EMPTY_ARRAY; checkRawToGenericsAssignment(returnValue, returnValue, returnType, valueType, false, fixes); } } @@ -543,17 +539,17 @@ public void visitReturnStatement(PsiReturnStatement statement) { } @Override - public void visitLambdaExpression(PsiLambdaExpression expression) { + public void visitLambdaExpression(@Nonnull PsiLambdaExpression expression) { super.visitLambdaExpression(expression); if (IGNORE_UNCHECKED_ASSIGNMENT) { return; } PsiElement body = expression.getBody(); - if (body instanceof PsiExpression) { + if (body instanceof PsiExpression bodyExpr) { PsiType interfaceReturnType = LambdaUtil.getFunctionalInterfaceReturnType(expression); if (interfaceReturnType != null && !PsiType.VOID.equals(interfaceReturnType)) { - PsiType type = ((PsiExpression)body).getType(); + PsiType type = bodyExpr.getType(); if (type != null) { checkRawToGenericsAssignment( body, @@ -568,33 +564,33 @@ public void visitLambdaExpression(PsiLambdaExpression expression) { } } - @Nullable - private String getUncheckedCallDescription(PsiElement place, JavaResolveResult resolveResult) { - final PsiElement element = resolveResult.getElement(); - if (!(element instanceof PsiMethod)) { - return null; + @Nonnull + @RequiredReadAction + private LocalizeValue getUncheckedCallDescription(PsiElement place, JavaResolveResult resolveResult) { + PsiElement element = resolveResult.getElement(); + if (!(element instanceof PsiMethod method)) { + return LocalizeValue.empty(); } - final PsiMethod method = (PsiMethod)element; - final PsiSubstitutor substitutor = resolveResult.getSubstitutor(); + PsiSubstitutor substitutor = resolveResult.getSubstitutor(); if (!PsiUtil.isRawSubstitutor(method, substitutor)) { if (JavaVersionService.getInstance().isAtLeast(place, JavaSdkVersion.JDK_1_8)) { for (PsiTypeParameter parameter : PsiUtil.typeParametersIterable(method)) { - final PsiClassType[] extendsListTypes = parameter.getExtendsListTypes(); + PsiClassType[] extendsListTypes = parameter.getExtendsListTypes(); if (extendsListTypes.length > 0) { - final PsiType subst = substitutor.substitute(parameter); + PsiType subst = substitutor.substitute(parameter); for (PsiClassType classType : extendsListTypes) { if (JavaGenericsUtil.isRawToGeneric(substitutor.substitute(classType), subst)) { - return JavaErrorBundle.message("generics.unchecked.call", JavaHighlightUtil.formatMethod(method)); + return JavaErrorLocalize.genericsUncheckedCall(JavaHighlightUtil.formatMethod(method)); } } } } } - return null; + return LocalizeValue.empty(); } - final PsiParameter[] parameters = method.getParameterList().getParameters(); - for (final PsiParameter parameter : parameters) { - final PsiType parameterType = parameter.getType(); + PsiParameter[] parameters = method.getParameterList().getParameters(); + for (PsiParameter parameter : parameters) { + PsiType parameterType = parameter.getType(); if (parameterType.accept(new PsiTypeVisitor() { @Override public Boolean visitPrimitiveType(PsiPrimitiveType primitiveType) { @@ -610,48 +606,41 @@ public Boolean visitArrayType(PsiArrayType arrayType) { public Boolean visitClassType(PsiClassType classType) { PsiClassType.ClassResolveResult result = classType.resolveGenerics(); PsiClass psiClass = result.getElement(); - if (psiClass instanceof PsiTypeParameter) { - if (((PsiTypeParameter)psiClass).getOwner() == method) { - return Boolean.FALSE; - } - return substitutor.substitute((PsiTypeParameter)psiClass) == null ? Boolean.TRUE : Boolean.FALSE; + if (psiClass instanceof PsiTypeParameter typeParam) { + return typeParam.getOwner() != method && substitutor.substitute(typeParam) == null; } if (psiClass != null) { PsiSubstitutor typeSubstitutor = result.getSubstitutor(); for (PsiTypeParameter parameter : PsiUtil.typeParametersIterable(psiClass)) { PsiType psiType = typeSubstitutor.substitute(parameter); - if (psiType != null && psiType.accept(this).booleanValue()) { - return Boolean.TRUE; + if (psiType != null && psiType.accept(this)) { + return true; } } } - return Boolean.FALSE; + return false; } @Override public Boolean visitWildcardType(PsiWildcardType wildcardType) { PsiType bound = wildcardType.getBound(); - if (bound != null) { - return bound.accept(this); - } - return Boolean.TRUE; + return bound == null || bound.accept(this); } @Override public Boolean visitEllipsisType(PsiEllipsisType ellipsisType) { return ellipsisType.getComponentType().accept(this); } - }).booleanValue()) { - final PsiElementFactory elementFactory = JavaPsiFacade.getInstance(method.getProject()).getElementFactory(); + })) { + PsiElementFactory elementFactory = JavaPsiFacade.getInstance(method.getProject()).getElementFactory(); PsiType type = elementFactory.createType(method.getContainingClass(), substitutor); - return JavaErrorBundle.message( - "generics.unchecked.call.to.member.of.raw.type", + return JavaErrorLocalize.genericsUncheckedCallToMemberOfRawType( JavaHighlightUtil.formatMethod(method), JavaHighlightUtil.formatType(type) ); } } - return null; + return LocalizeValue.empty(); } } } diff --git a/plugin/src/main/java/com/intellij/java/impl/codeInspection/visibility/VisibilityInspection.java b/plugin/src/main/java/com/intellij/java/impl/codeInspection/visibility/VisibilityInspection.java index 7c06f07e84..00bc8ac924 100644 --- a/plugin/src/main/java/com/intellij/java/impl/codeInspection/visibility/VisibilityInspection.java +++ b/plugin/src/main/java/com/intellij/java/impl/codeInspection/visibility/VisibilityInspection.java @@ -54,7 +54,6 @@ import consulo.project.Project; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; -import org.jetbrains.annotations.NonNls; import javax.swing.*; import java.awt.*; @@ -66,7 +65,6 @@ public class VisibilityInspection extends GlobalJavaInspectionTool implements Ol public boolean SUGGEST_PACKAGE_LOCAL_FOR_MEMBERS = true; public boolean SUGGEST_PACKAGE_LOCAL_FOR_TOP_CLASSES = true; public boolean SUGGEST_PRIVATE_FOR_INNERS = false; - @NonNls private static final String SHORT_NAME = "WeakerAccess"; private class OptionsPanel extends JPanel { @@ -138,12 +136,12 @@ public String getShortName() { @Nullable @RequiredReadAction public CommonProblemDescriptor[] checkElement( - final RefEntity refEntity, - final AnalysisScope scope, - final InspectionManager manager, - final GlobalInspectionContext globalContext, - final ProblemDescriptionsProcessor processor, - Object state + @Nonnull RefEntity refEntity, + @Nonnull AnalysisScope scope, + @Nonnull InspectionManager manager, + @Nonnull GlobalInspectionContext globalContext, + @Nonnull ProblemDescriptionsProcessor processor, + @Nonnull Object state ) { if (refEntity instanceof RefJavaElement refElement) { if (refElement instanceof RefParameter || refElement.isSyntheticJSP()) { @@ -190,8 +188,8 @@ public CommonProblemDescriptor[] checkElement( String access = getPossibleAccess(refElement); if (access != refElement.getAccessModifier() && access != null) { - final PsiElement element = refElement.getElement(); - final PsiElement nameIdentifier = element != null ? IdentifierUtil.getNameIdentifier(element) : null; + PsiElement element = refElement.getElement(); + PsiElement nameIdentifier = element != null ? IdentifierUtil.getNameIdentifier(element) : null; if (nameIdentifier != null) { return new ProblemDescriptor[]{ manager.createProblemDescriptor( @@ -318,7 +316,7 @@ private boolean isAccessible(RefJavaElement to, @PsiModifier.ModifierConstant St } } - for (final RefElement refElement : refClass.getInTypeReferences()) { + for (RefElement refElement : refClass.getInTypeReferences()) { if (!isAccessibleFrom(refElement, refClass, accessModifier)) { return false; } @@ -355,7 +353,7 @@ private boolean isAccessibleFrom(RefElement from, RefJavaElement to, String acce return true; } - final RefJavaUtil refUtil = RefJavaUtil.getInstance(); + RefJavaUtil refUtil = RefJavaUtil.getInstance(); if (accessModifier == PsiModifier.PACKAGE_LOCAL) { return RefJavaUtil.getPackage(from) == RefJavaUtil.getPackage(to); } @@ -377,6 +375,7 @@ private boolean isAccessibleFrom(RefElement from, RefJavaElement to, String acce if (accessModifier == PsiModifier.PRIVATE) { if (SUGGEST_PRIVATE_FOR_INNERS) { + //noinspection SimplifiableIfStatement if (isInExtendsList(to, fromTopLevel.getElement().getExtendsList()) || isInExtendsList(to, fromTopLevel.getElement().getImplementsList()) || isInAnnotations(to, fromTopLevel)) { @@ -391,7 +390,7 @@ private boolean isAccessibleFrom(RefElement from, RefJavaElement to, String acce if (fromTopLevel == toOwner) { if (from instanceof RefClass fromRefClass && to instanceof RefClass) { - final PsiClass fromClass = fromRefClass.getElement(); + PsiClass fromClass = fromRefClass.getElement(); LOG.assertTrue(fromClass != null); if (isInExtendsList(to, fromClass.getExtendsList()) || isInExtendsList(to, fromClass.getImplementsList())) { @@ -406,14 +405,14 @@ private boolean isAccessibleFrom(RefElement from, RefJavaElement to, String acce return false; } - private static boolean isInAnnotations(final RefJavaElement to, final RefClass fromTopLevel) { - final PsiModifierList modifierList = fromTopLevel.getElement().getModifierList(); + private static boolean isInAnnotations(RefJavaElement to, RefClass fromTopLevel) { + PsiModifierList modifierList = fromTopLevel.getElement().getModifierList(); if (modifierList == null) { return false; } - final PsiElement toElement = to.getElement(); + PsiElement toElement = to.getElement(); - final boolean[] resolved = new boolean[]{false}; + boolean[] resolved = new boolean[]{false}; modifierList.accept(new JavaRecursiveElementWalkingVisitor() { @RequiredReadAction @Override @@ -430,11 +429,11 @@ public void visitReferenceExpression(PsiReferenceExpression expression) { return resolved[0]; } - private static boolean isInExtendsList(final RefJavaElement to, final PsiReferenceList extendsList) { + private static boolean isInExtendsList(RefJavaElement to, PsiReferenceList extendsList) { if (extendsList != null) { - final PsiJavaCodeReferenceElement[] referenceElements = extendsList.getReferenceElements(); + PsiJavaCodeReferenceElement[] referenceElements = extendsList.getReferenceElements(); for (PsiJavaCodeReferenceElement referenceElement : referenceElements) { - final PsiReferenceParameterList parameterList = referenceElement.getParameterList(); + PsiReferenceParameterList parameterList = referenceElement.getParameterList(); if (parameterList != null) { for (PsiType type : parameterList.getTypeArguments()) { if (extendsList.getManager().areElementsEquivalent(PsiUtil.resolveClassInType(type), to.getElement())) { @@ -450,12 +449,12 @@ private static boolean isInExtendsList(final RefJavaElement to, final PsiReferen @Override protected boolean queryExternalUsagesRequests( - final RefManager manager, - final GlobalJavaInspectionContext globalContext, - final ProblemDescriptionsProcessor processor, + RefManager manager, + GlobalJavaInspectionContext globalContext, + ProblemDescriptionsProcessor processor, Object state ) { - final EntryPointsManager entryPointsManager = globalContext.getEntryPointsManager(manager); + EntryPointsManager entryPointsManager = globalContext.getEntryPointsManager(manager); for (RefElement entryPoint : entryPointsManager.getEntryPoints()) { ignoreElement(processor, entryPoint); } @@ -465,7 +464,7 @@ protected boolean queryExternalUsagesRequests( } manager.iterate(new RefJavaVisitor() { @Override - public void visitElement(@Nonnull final RefEntity refEntity) { + public void visitElement(@Nonnull RefEntity refEntity) { if (!(refEntity instanceof RefElement)) { return; } @@ -474,7 +473,7 @@ public void visitElement(@Nonnull final RefEntity refEntity) { } refEntity.accept(new RefJavaVisitor() { @Override - public void visitField(@Nonnull final RefField refField) { + public void visitField(@Nonnull RefField refField) { if (refField.getAccessModifier() != PsiModifier.PRIVATE) { globalContext.enqueueFieldUsagesProcessor(refField, psiReference -> { ignoreElement(processor, refField); @@ -484,25 +483,31 @@ public void visitField(@Nonnull final RefField refField) { } @Override - public void visitMethod(@Nonnull final RefMethod refMethod) { + public void visitMethod(@Nonnull RefMethod refMethod) { if (!refMethod.isExternalOverride() && refMethod.getAccessModifier() != PsiModifier.PRIVATE && !(refMethod instanceof RefImplicitConstructor)) { - globalContext.enqueueDerivedMethodsProcessor(refMethod, derivedMethod -> { - ignoreElement(processor, refMethod); - return false; - }); + globalContext.enqueueDerivedMethodsProcessor( + refMethod, + derivedMethod -> { + ignoreElement(processor, refMethod); + return false; + } + ); - globalContext.enqueueMethodUsagesProcessor(refMethod, psiReference -> { - ignoreElement(processor, refMethod); - return false; - }); + globalContext.enqueueMethodUsagesProcessor( + refMethod, + psiReference -> { + ignoreElement(processor, refMethod); + return false; + } + ); if (entryPointsManager.isAddNonJavaEntries()) { - final RefClass ownerClass = refMethod.getOwnerClass(); + RefClass ownerClass = refMethod.getOwnerClass(); if (refMethod.isConstructor() && ownerClass.getDefaultConstructor() != null) { String qualifiedName = ownerClass.getElement().getQualifiedName(); if (qualifiedName != null) { - final Project project = manager.getProject(); + Project project = manager.getProject(); PsiSearchHelper.SERVICE.getInstance(project).processUsagesInNonJavaFiles( qualifiedName, (file, startOffset, endOffset) -> { @@ -519,7 +524,7 @@ public void visitMethod(@Nonnull final RefMethod refMethod) { } @Override - public void visitClass(@Nonnull final RefClass refClass) { + public void visitClass(@Nonnull RefClass refClass) { if (!refClass.isAnonymous()) { globalContext.enqueueDerivedClassesProcessor(refClass, inheritor -> { ignoreElement(processor, refClass); @@ -557,19 +562,19 @@ private static void ignoreElement(@Nonnull ProblemDescriptionsProcessor processo } @Override - public void compose(@Nonnull final StringBuffer buf, @Nonnull final RefEntity refEntity, final HTMLComposer composer) { + public void compose(@Nonnull StringBuffer buf, @Nonnull RefEntity refEntity, HTMLComposer composer) { composer.appendElementInReferences(buf, (RefElement)refEntity); } @Override @Nullable - public QuickFix getQuickFix(final String hint) { + public QuickFix getQuickFix(String hint) { return new AcceptSuggestedAccess(null, hint); } @Override @Nullable - public String getHint(@Nonnull final QuickFix fix) { + public String getHint(@Nonnull QuickFix fix) { return ((AcceptSuggestedAccess)fix).getHint(); } @@ -578,7 +583,7 @@ private static class AcceptSuggestedAccess implements LocalQuickFix { @PsiModifier.ModifierConstant private final String myHint; - private AcceptSuggestedAccess(final RefManager manager, @PsiModifier.ModifierConstant String hint) { + private AcceptSuggestedAccess(RefManager manager, @PsiModifier.ModifierConstant String hint) { myManager = manager; myHint = hint; } @@ -601,7 +606,7 @@ public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descri if (!FileModificationService.getInstance().preparePsiElementForWrite(descriptor.getPsiElement())) { return; } - final PsiModifierListOwner element = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), PsiModifierListOwner.class); + PsiModifierListOwner element = PsiTreeUtil.getParentOfType(descriptor.getPsiElement(), PsiModifierListOwner.class); if (element != null) { RefElement refElement = null; if (myManager != null) { @@ -619,7 +624,7 @@ public void applyFix(@Nonnull Project project, @Nonnull ProblemDescriptor descri if (element instanceof PsiMethod psiMethod) { PsiClass containingClass = psiMethod.getContainingClass(); if (containingClass != null && containingClass.getParent() instanceof PsiFile - && myHint == PsiModifier.PRIVATE && list.hasModifierProperty(PsiModifier.FINAL)) { + && myHint == PsiModifier.PRIVATE && psiMethod.isFinal()) { list.setModifierProperty(PsiModifier.FINAL, false); } } diff --git a/plugin/src/main/java/com/intellij/java/impl/generate/GenerateToStringActionHandlerImpl.java b/plugin/src/main/java/com/intellij/java/impl/generate/GenerateToStringActionHandlerImpl.java index 7acce98049..13d3c1b141 100644 --- a/plugin/src/main/java/com/intellij/java/impl/generate/GenerateToStringActionHandlerImpl.java +++ b/plugin/src/main/java/com/intellij/java/impl/generate/GenerateToStringActionHandlerImpl.java @@ -15,14 +15,20 @@ */ package com.intellij.java.impl.generate; +import com.intellij.java.analysis.impl.generate.config.Config; +import com.intellij.java.impl.generate.template.TemplateResource; +import com.intellij.java.impl.generate.template.toString.ToStringTemplatesManager; +import com.intellij.java.impl.generate.tostring.GenerateToStringClassFilter; +import com.intellij.java.impl.generate.view.TemplatesPanel; import com.intellij.java.language.impl.codeInsight.generation.PsiElementClassMember; import com.intellij.java.language.psi.PsiClass; import com.intellij.java.language.psi.PsiField; import com.intellij.java.language.psi.PsiMember; import com.intellij.java.language.psi.PsiMethod; import com.intellij.java.language.psi.util.PsiUtil; +import consulo.annotation.access.RequiredReadAction; import consulo.annotation.component.ServiceImpl; -import consulo.application.ApplicationManager; +import consulo.application.Application; import consulo.codeEditor.Editor; import consulo.configurable.Configurable; import consulo.configurable.ConfigurationException; @@ -36,23 +42,19 @@ import consulo.language.psi.PsiElement; import consulo.language.psi.PsiFile; import consulo.language.psi.util.PsiTreeUtil; +import consulo.localize.LocalizeValue; import consulo.logging.Logger; import consulo.project.Project; +import consulo.ui.annotation.RequiredUIAccess; import consulo.ui.ex.awt.ColoredListCellRenderer; import consulo.ui.ex.awt.ComboBox; import consulo.ui.ex.awt.DialogWrapper; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; import jakarta.inject.Singleton; -import com.intellij.java.impl.generate.tostring.GenerateToStringClassFilter; import org.jetbrains.java.generate.GenerateToStringActionHandler; import org.jetbrains.java.generate.GenerateToStringContext; import org.jetbrains.java.generate.GenerateToStringUtils; -import com.intellij.java.analysis.impl.generate.config.Config; -import com.intellij.java.impl.generate.template.TemplateResource; -import com.intellij.java.impl.generate.template.toString.ToStringTemplatesManager; -import com.intellij.java.impl.generate.view.TemplatesPanel; - -import jakarta.annotation.Nonnull; -import jakarta.annotation.Nullable; import javax.swing.*; import java.awt.*; @@ -77,6 +79,7 @@ public boolean startInWriteAction() { } @Override + @RequiredUIAccess public void invoke(@Nonnull Project project, @Nonnull Editor editor, @Nonnull PsiFile file) { PsiClass clazz = getSubjectClass(editor, file); assert clazz != null; @@ -86,66 +89,63 @@ public void invoke(@Nonnull Project project, @Nonnull Editor editor, @Nonnull Ps @Override - public void executeActionQuickFix(final Project project, final PsiClass clazz) { + public void executeActionQuickFix(Project project, PsiClass clazz) { doExecuteAction(project, clazz, null); } - private static void doExecuteAction(@Nonnull final Project project, @Nonnull final PsiClass clazz, final Editor editor) { + private static void doExecuteAction(@Nonnull Project project, @Nonnull PsiClass clazz, Editor editor) { logger.debug("+++ doExecuteAction - START +++"); if (logger.isDebugEnabled()) { logger.debug("Current project " + project.getName()); } - final PsiElementClassMember[] dialogMembers = buildMembersToShow(clazz); + PsiElementClassMember[] dialogMembers = buildMembersToShow(clazz); - final MemberChooserHeaderPanel header = new MemberChooserHeaderPanel(clazz); + MemberChooserHeaderPanel header = new MemberChooserHeaderPanel(clazz); logger.debug("Displaying member chooser dialog"); - ApplicationManager.getApplication().invokeLater(new Runnable() { - @Override - public void run() { - if (project.isDisposed()) { - return; + Application.get().invokeLater(() -> { + if (project.isDisposed()) { + return; + } + MemberChooser chooser = new MemberChooser<>( + dialogMembers, + true, + true, + project, + PsiUtil.isLanguageLevel5OrHigher(clazz), + header + ) { + @Nullable + @Override + protected String getHelpId() { + return "editing.altInsert.tostring"; } - final MemberChooser chooser = new MemberChooser( - dialogMembers, - true, - true, - project, - PsiUtil.isLanguageLevel5OrHigher(clazz), - header - ) { - @Nullable - @Override - protected String getHelpId() { - return "editing.altInsert.tostring"; - } - }; - chooser.setTitle("Generate toString()"); - - chooser.setCopyJavadocVisible(false); - chooser.selectElements(dialogMembers); - header.setChooser(chooser); - chooser.show(); - - if (DialogWrapper.OK_EXIT_CODE == chooser.getExitCode()) { - Collection selectedMembers = GenerationUtil.convertClassMembersToPsiMembers(chooser.getSelectedElements()); - - final TemplateResource template = header.getSelectedTemplate(); - ToStringTemplatesManager.getInstance().setDefaultTemplate(template); - - if (template.isValidTemplate()) { - GenerateToStringWorker.executeGenerateActionLater( - clazz, - editor, - selectedMembers, - template, - chooser.isInsertOverrideAnnotation() - ); - } - else { - HintManager.getInstance().showErrorHint(editor, "toString() template '" + template.getFileName() + "' is invalid"); - } + }; + chooser.setTitle(LocalizeValue.localizeTODO("Generate toString()")); + + chooser.setCopyJavadocVisible(false); + chooser.selectElements(dialogMembers); + header.setChooser(chooser); + chooser.show(); + + if (DialogWrapper.OK_EXIT_CODE == chooser.getExitCode()) { + Collection selectedMembers = GenerationUtil.convertClassMembersToPsiMembers(chooser.getSelectedElements()); + + TemplateResource template = header.getSelectedTemplate(); + ToStringTemplatesManager.getInstance().setDefaultTemplate(template); + + if (template.isValidTemplate()) { + GenerateToStringWorker.executeGenerateActionLater( + clazz, + editor, + selectedMembers, + template, + chooser.isInsertOverrideAnnotation() + ); + } + else { + HintManager.getInstance().showErrorHint(editor, "toString() template '" + template.getFileName() + "' is invalid"); } } }); @@ -154,7 +154,7 @@ protected String getHelpId() { } public static void updateDialog(PsiClass clazz, MemberChooser dialog) { - final PsiElementClassMember[] members = buildMembersToShow(clazz); + PsiElementClassMember[] members = buildMembersToShow(clazz); dialog.resetElements(members); dialog.selectElements(members); } @@ -181,7 +181,8 @@ private static PsiElementClassMember[] buildMembersToShow(PsiClass clazz) { } @Nullable - private static PsiClass getSubjectClass(Editor editor, final PsiFile file) { + @RequiredReadAction + private static PsiClass getSubjectClass(Editor editor, PsiFile file) { if (file == null) { return null; } @@ -215,18 +216,17 @@ public void setChooser(MemberChooser chooser) { this.chooser = chooser; } - public MemberChooserHeaderPanel(final PsiClass clazz) { + public MemberChooserHeaderPanel(PsiClass clazz) { super(new GridBagLayout()); - final Collection templates = ToStringTemplatesManager.getInstance().getAllTemplates(); - final TemplateResource[] all = templates.toArray(new TemplateResource[templates.size()]); + Collection templates = ToStringTemplatesManager.getInstance().getAllTemplates(); + TemplateResource[] all = templates.toArray(new TemplateResource[templates.size()]); - final JButton settingsButton = new JButton("Settings"); + JButton settingsButton = new JButton("Settings"); settingsButton.setMnemonic(KeyEvent.VK_S); comboBox = new ComboBox<>(all); - comboBox.setRenderer(new ColoredListCellRenderer() { - + comboBox.setRenderer(new ColoredListCellRenderer<>() { @Override protected void customizeCellRenderer( @Nonnull JList jList, @@ -240,13 +240,14 @@ protected void customizeCellRenderer( }); settingsButton.addActionListener(new ActionListener() { @Override + @RequiredUIAccess public void actionPerformed(ActionEvent e) { - final TemplatesPanel ui = new TemplatesPanel(clazz.getProject()); + TemplatesPanel ui = new TemplatesPanel(clazz.getProject()); Disposable disposable = Disposable.newDisposable(); Configurable composite = new TabbedConfigurable(disposable) { @Override protected List createConfigurables() { - List res = new ArrayList(); + List res = new ArrayList<>(); res.add(new GenerateToStringConfigurable(clazz.getProject())); res.add(ui); return res; @@ -263,6 +264,7 @@ public String getHelpTopic() { } @Override + @RequiredUIAccess public void apply() throws ConfigurationException { super.apply(); updateDialog(clazz, chooser); @@ -275,23 +277,22 @@ public void apply() throws ConfigurationException { } }; - ShowSettingsUtil.getInstance().editConfigurable(MemberChooserHeaderPanel.this, composite, new Runnable() { - @Override - public void run() { - ui.selectItem(ToStringTemplatesManager.getInstance().getDefaultTemplate()); - } - }); + ShowSettingsUtil.getInstance().editConfigurable( + MemberChooserHeaderPanel.this, + composite, + () -> ui.selectItem(ToStringTemplatesManager.getInstance().getDefaultTemplate()) + ); Disposer.dispose(disposable); } }); comboBox.setSelectedItem(ToStringTemplatesManager.getInstance().getDefaultTemplate()); - final JLabel templatesLabel = new JLabel("Template: "); + JLabel templatesLabel = new JLabel(LocalizeValue.localizeTODO("Template: ").get()); templatesLabel.setDisplayedMnemonic('T'); templatesLabel.setLabelFor(comboBox); - final GridBagConstraints constraints = new GridBagConstraints(); + GridBagConstraints constraints = new GridBagConstraints(); constraints.anchor = GridBagConstraints.BASELINE; constraints.gridx = 0; add(templatesLabel, constraints);