mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-18 09:34:34 +07:00
291 lines
12 KiB
Java
291 lines
12 KiB
Java
/*
|
|
* Copyright 2000-2017 JetBrains s.r.o.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
package com.intellij.codeInspection.java19modules;
|
|
|
|
import com.intellij.analysis.AnalysisScope;
|
|
import com.intellij.codeInsight.FileModificationService;
|
|
import com.intellij.codeInsight.daemon.impl.analysis.JavaModuleGraphUtil;
|
|
import com.intellij.codeInspection.*;
|
|
import com.intellij.codeInspection.reference.*;
|
|
import com.intellij.openapi.diagnostic.Logger;
|
|
import com.intellij.openapi.project.Project;
|
|
import com.intellij.openapi.util.Key;
|
|
import com.intellij.openapi.util.text.StringUtil;
|
|
import com.intellij.pom.java.LanguageLevel;
|
|
import com.intellij.psi.*;
|
|
import com.intellij.psi.impl.source.PsiJavaModuleReference;
|
|
import com.intellij.psi.util.PsiTreeUtil;
|
|
import com.intellij.util.containers.ContainerUtil;
|
|
import gnu.trove.THashSet;
|
|
import one.util.streamex.StreamEx;
|
|
import org.jetbrains.annotations.Nls;
|
|
import org.jetbrains.annotations.NotNull;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
import java.util.*;
|
|
|
|
/**
|
|
* @author Pavel.Dolgov
|
|
*/
|
|
public class Java9RedundantRequiresStatementInspection extends GlobalJavaBatchInspectionTool {
|
|
private static final Logger LOG = Logger.getInstance(Java9RedundantRequiresStatementInspection.class);
|
|
|
|
private static final Key<Set<String>> IMPORTED_JAVA_PACKAGES = Key.create("imported_java_packages");
|
|
|
|
@Nls
|
|
@NotNull
|
|
@Override
|
|
public String getDisplayName() {
|
|
return InspectionsBundle.message("inspection.redundant.requires.statement.name");
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public CommonProblemDescriptor[] checkElement(@NotNull RefEntity refEntity,
|
|
@NotNull AnalysisScope scope,
|
|
@NotNull InspectionManager manager,
|
|
@NotNull GlobalInspectionContext globalContext) {
|
|
if (refEntity instanceof RefJavaModule) {
|
|
RefJavaModule refJavaModule = (RefJavaModule)refEntity;
|
|
|
|
RefModule refModule = refJavaModule.getModule();
|
|
PsiJavaModule psiJavaModule = refJavaModule.getElement();
|
|
if (refModule != null && psiJavaModule != null) {
|
|
Set<String> moduleImportedPackages = refModule.getUserData(IMPORTED_JAVA_PACKAGES);
|
|
if (moduleImportedPackages != null) {
|
|
List<RefJavaModule.RequiredModule> requiredModules = refJavaModule.getRequiredModules();
|
|
if (!requiredModules.isEmpty()) {
|
|
List<CommonProblemDescriptor> descriptors = new ArrayList<>();
|
|
for (RefJavaModule.RequiredModule requiredModule : requiredModules) {
|
|
String requiredModuleName = requiredModule.moduleName;
|
|
|
|
if (PsiJavaModule.JAVA_BASE.equals(requiredModuleName) ||
|
|
isDependencyUnused(requiredModule.packagesExportedByModule, moduleImportedPackages, refJavaModule.getName())) {
|
|
PsiRequiresStatement requiresStatement = ContainerUtil.find(
|
|
psiJavaModule.getRequires(), statement -> requiredModuleName.equals(statement.getModuleName()));
|
|
if (requiresStatement != null) {
|
|
CommonProblemDescriptor descriptor = manager.createProblemDescriptor(
|
|
requiresStatement,
|
|
InspectionsBundle.message("inspection.redundant.requires.statement.description", requiredModuleName),
|
|
new DeleteRedundantRequiresStatementFix(requiredModuleName, moduleImportedPackages),
|
|
ProblemHighlightType.LIKE_UNUSED_SYMBOL, false);
|
|
descriptors.add(descriptor);
|
|
}
|
|
}
|
|
}
|
|
if (!ContainerUtil.isEmpty(descriptors)) {
|
|
return descriptors.toArray(CommonProblemDescriptor.EMPTY_ARRAY);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static boolean isDependencyUnused(@NotNull Map<String, List<String>> dependencyExportedPackages,
|
|
@NotNull Set<String> importedPackageNames,
|
|
@NotNull String contextModuleName) {
|
|
for (Map.Entry<String, List<String>> entry : dependencyExportedPackages.entrySet()) {
|
|
String exportedPackageName = entry.getKey();
|
|
List<String> exportedToModules = entry.getValue();
|
|
if (!exportedToModules.isEmpty() && !exportedToModules.contains(contextModuleName)) {
|
|
continue; // exported to some other modules but not to this one
|
|
}
|
|
if (importedPackageNames.contains(exportedPackageName)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public RefGraphAnnotator getAnnotator(@NotNull RefManager refManager) {
|
|
return new RedundantRequiresStatementAnnotator();
|
|
}
|
|
|
|
private static PsiJavaModule resolveRequiredModule(PsiRequiresStatement requiresStatement) {
|
|
return PsiJavaModuleReference.resolve(requiresStatement, requiresStatement.getModuleName(), false);
|
|
}
|
|
|
|
private static class DeleteRedundantRequiresStatementFix implements LocalQuickFix {
|
|
private String myRequiredModuleName;
|
|
private Set<String> myImportedPackages;
|
|
|
|
public DeleteRedundantRequiresStatementFix(String requiredModuleName, Set<String> importedPackages) {
|
|
myRequiredModuleName = requiredModuleName;
|
|
myImportedPackages = importedPackages;
|
|
}
|
|
|
|
@Nls
|
|
@NotNull
|
|
@Override
|
|
public String getFamilyName() {
|
|
return InspectionsBundle.message("inspection.redundant.requires.statement.fix.family");
|
|
}
|
|
|
|
@Nls
|
|
@NotNull
|
|
@Override
|
|
public String getName() {
|
|
return InspectionsBundle.message("inspection.redundant.requires.statement.fix.name", myRequiredModuleName);
|
|
}
|
|
|
|
@Override
|
|
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
|
PsiElement element = descriptor.getPsiElement();
|
|
if (!FileModificationService.getInstance().preparePsiElementForWrite(element)) return;
|
|
|
|
LOG.assertTrue(element instanceof PsiRequiresStatement, "Should be 'requires' statement");
|
|
PsiRequiresStatement statementToDelete = (PsiRequiresStatement)element;
|
|
addTransitiveDependencies(statementToDelete);
|
|
statementToDelete.delete();
|
|
}
|
|
|
|
private Set<String> getReexportedDependencies(@NotNull PsiJavaModule currentModule, @NotNull PsiJavaModule dependencyModule) {
|
|
Set<String> directDependencies = StreamEx
|
|
.of(currentModule.getRequires().iterator())
|
|
.map(PsiRequiresStatement::getModuleName)
|
|
.nonNull()
|
|
.toSet();
|
|
|
|
List<PsiJavaModule> transitiveModules = StreamEx
|
|
.of(dependencyModule.getRequires().iterator())
|
|
.filter(PsiRequiresStatement::isPublic)
|
|
.filter(requiresStatement -> !directDependencies.contains(requiresStatement.getModuleName()))
|
|
.map(Java9RedundantRequiresStatementInspection::resolveRequiredModule)
|
|
.nonNull()
|
|
.toList();
|
|
|
|
return StreamEx.of(transitiveModules)
|
|
.filter(transitiveModule -> isReexported(currentModule, transitiveModule))
|
|
.map(transitiveModule -> transitiveModule.getName())
|
|
.toSet();
|
|
}
|
|
|
|
private boolean isReexported(@NotNull PsiJavaModule currentModule, @NotNull PsiJavaModule transitiveModule) {
|
|
return StreamEx
|
|
.of(transitiveModule.getExports().iterator())
|
|
.map(PsiPackageAccessibilityStatement::getPackageName)
|
|
.nonNull()
|
|
.filter(myImportedPackages::contains)
|
|
.anyMatch(packageName -> JavaModuleGraphUtil.exports(transitiveModule, packageName, currentModule));
|
|
}
|
|
|
|
private void addTransitiveDependencies(@NotNull PsiRequiresStatement statementToDelete) {
|
|
PsiElement parent = statementToDelete.getParent();
|
|
if (parent instanceof PsiJavaModule) {
|
|
PsiJavaModule currentModule = (PsiJavaModule)parent;
|
|
Optional.of(statementToDelete)
|
|
.map(Java9RedundantRequiresStatementInspection::resolveRequiredModule)
|
|
.map(dependencyModule -> getReexportedDependencies(currentModule, dependencyModule))
|
|
.ifPresent(reexportedDependencies -> addReexportedDependencies(reexportedDependencies, currentModule, statementToDelete));
|
|
}
|
|
}
|
|
|
|
private static void addReexportedDependencies(@NotNull Set<String> reexportedDependencies,
|
|
@NotNull PsiJavaModule currentModule,
|
|
@NotNull PsiElement addingPlace) {
|
|
if (!reexportedDependencies.isEmpty()) {
|
|
PsiJavaParserFacade parserFacade = JavaPsiFacade.getInstance(currentModule.getProject()).getParserFacade();
|
|
for (String dependencyName : reexportedDependencies) {
|
|
PsiJavaModule tempModule =
|
|
parserFacade.createModuleFromText("module " + currentModule.getName() + " { requires " + dependencyName + "; }");
|
|
Iterable<PsiRequiresStatement> tempModuleRequires = tempModule.getRequires();
|
|
PsiRequiresStatement requiresStatement = tempModuleRequires.iterator().next();
|
|
currentModule.addAfter(requiresStatement, addingPlace);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static class RedundantRequiresStatementAnnotator extends RefGraphAnnotator {
|
|
private static final Set<String> DONT_COLLECT_PACKAGES = Collections.emptySet();
|
|
|
|
@Override
|
|
public void onReferencesBuild(RefElement refElement) {
|
|
if (refElement instanceof RefFile) {
|
|
RefFile refFile = (RefFile)refElement;
|
|
PsiFile file = refFile.getElement();
|
|
if (file instanceof PsiJavaFile) {
|
|
onJavaFileReferencesBuilt(refFile, (PsiJavaFile)file);
|
|
}
|
|
}
|
|
else if (refElement instanceof RefJavaModule) {
|
|
RefModule refModule = refElement.getModule();
|
|
if (refModule != null) {
|
|
setImportedPackages(refModule, refElement.getElement() != null);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void onJavaFileReferencesBuilt(@NotNull RefFile refFile, @NotNull PsiJavaFile file) {
|
|
if (file.getLanguageLevel().isAtLeast(LanguageLevel.JDK_1_9)) {
|
|
PsiImportList importList = file.getImportList();
|
|
if (importList != null) {
|
|
RefModule refModule = refFile.getModule();
|
|
if (refModule != null) {
|
|
Set<String> packageNames = getImportedPackages(refModule);
|
|
if (packageNames != DONT_COLLECT_PACKAGES) {
|
|
PsiImportStatementBase[] statements = importList.getAllImportStatements();
|
|
for (PsiImportStatementBase statement : statements) {
|
|
String packageName = getPackageName(statement);
|
|
if (!StringUtil.isEmpty(packageName)) {
|
|
packageNames.add(packageName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
private static String getPackageName(@NotNull PsiImportStatementBase statement) {
|
|
PsiElement resolved = statement.resolve();
|
|
if (resolved instanceof PsiPackage) {
|
|
return ((PsiPackage)resolved).getQualifiedName();
|
|
}
|
|
else if (resolved instanceof PsiMember) {
|
|
PsiJavaFile parentFile = PsiTreeUtil.getParentOfType(resolved, PsiJavaFile.class);
|
|
if (parentFile != null) {
|
|
return parentFile.getPackageName();
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@NotNull
|
|
private static Set<String> getImportedPackages(@NotNull RefModule refModule) {
|
|
Set<String> importedPackages = refModule.getUserData(IMPORTED_JAVA_PACKAGES);
|
|
if (importedPackages == null) {
|
|
PsiJavaModule javaModule = JavaModuleGraphUtil.findDescriptorByModule(refModule.getModule());
|
|
importedPackages = javaModule != null ? new THashSet<>() : DONT_COLLECT_PACKAGES;
|
|
refModule.putUserData(IMPORTED_JAVA_PACKAGES, importedPackages);
|
|
}
|
|
return importedPackages;
|
|
}
|
|
|
|
private static void setImportedPackages(RefModule refModule, boolean collectPackages) {
|
|
Set<String> importedPackages = refModule.getUserData(IMPORTED_JAVA_PACKAGES);
|
|
if (importedPackages == null) {
|
|
refModule.putUserData(IMPORTED_JAVA_PACKAGES, collectPackages ? new THashSet<>() : DONT_COLLECT_PACKAGES);
|
|
}
|
|
}
|
|
}
|
|
}
|