mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-05 01:50:56 +07:00
[java, action] refactoring "Generate module-info descriptors" IDEA-187523
GitOrigin-RevId: f4a1450f82cc684c67838172ec4d1a28bb7b7e6d
This commit is contained in:
committed by
intellij-monorepo-bot
parent
7d7ba2c4b3
commit
7ac5997e0c
@@ -0,0 +1,250 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInspection.java19api;
|
||||
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.JavaModuleGraphUtil;
|
||||
import com.intellij.ide.fileTemplates.FileTemplate;
|
||||
import com.intellij.ide.fileTemplates.FileTemplateManager;
|
||||
import com.intellij.ide.fileTemplates.FileTemplateUtil;
|
||||
import com.intellij.java.refactoring.JavaRefactoringBundle;
|
||||
import com.intellij.lang.java.JavaLanguage;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.ReadAction;
|
||||
import com.intellij.openapi.application.ex.ApplicationManagerEx;
|
||||
import com.intellij.openapi.command.CommandProcessor;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.progress.impl.CoreProgressManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.NlsContexts;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.intellij.ide.fileTemplates.JavaTemplateUtil.INTERNAL_MODULE_INFO_TEMPLATE_NAME;
|
||||
import static com.intellij.psi.PsiJavaModule.MODULE_INFO_CLASS;
|
||||
import static com.intellij.psi.PsiJavaModule.MODULE_INFO_FILE;
|
||||
|
||||
class DescriptorsGenerator {
|
||||
private final Logger myLogger;
|
||||
private final Project myProject;
|
||||
private final UniqueModuleNames myUniqueModuleNames;
|
||||
|
||||
private final ProgressTracker myProgressTracker = new ProgressTracker(0.5, 0.3, 0.2);
|
||||
|
||||
DescriptorsGenerator(@NotNull Project project, @NotNull UniqueModuleNames uniqueModuleNames, @NotNull Logger logger) {
|
||||
myProject = project;
|
||||
myUniqueModuleNames = uniqueModuleNames;
|
||||
myLogger = logger;
|
||||
}
|
||||
|
||||
void generate(@NotNull List<ModuleFiles> moduleFiles, @NotNull ProgressIndicator indicator) {
|
||||
myProgressTracker.init(indicator);
|
||||
List<ModuleInfo> modulesInfos;
|
||||
try {
|
||||
myProgressTracker.startPhase(JavaRefactoringBundle.message("generate.module.descriptors.collecting.message"),
|
||||
moduleFiles.stream().mapToInt(m -> m.files().size()).sum());
|
||||
Map<String, Set<ModuleNode>> packagesDeclaredInModules = collectDependencies(moduleFiles);
|
||||
myProgressTracker.nextPhase();
|
||||
|
||||
final int modulesCount = packagesDeclaredInModules.values().stream().mapToInt(Set::size).sum();
|
||||
|
||||
myProgressTracker.startPhase(JavaRefactoringBundle.message("generate.module.descriptors.analysing.message"), modulesCount);
|
||||
final List<ModuleNode> modules = prepareModulesWithDependencies(packagesDeclaredInModules);
|
||||
myProgressTracker.nextPhase();
|
||||
|
||||
myProgressTracker.startPhase(JavaRefactoringBundle.message("generate.module.descriptors.preparing.message"), modulesCount);
|
||||
modulesInfos = prepareModuleInfos(modules);
|
||||
myProgressTracker.nextPhase();
|
||||
}
|
||||
finally {
|
||||
myProgressTracker.dispose();
|
||||
}
|
||||
createFilesLater(modulesInfos);
|
||||
}
|
||||
|
||||
private void createFilesLater(@NotNull List<ModuleInfo> moduleInfos) {
|
||||
final Runnable createFiles = () -> {
|
||||
if (myProject.isDisposed()) return;
|
||||
CommandProcessor.getInstance().executeCommand(myProject, () ->
|
||||
ApplicationManagerEx.getApplicationEx().runWriteActionWithCancellableProgressInDispatchThread(
|
||||
getCommandTitle(), myProject, null,
|
||||
indicator -> createFiles(myProject, moduleInfos, indicator)), getCommandTitle(), null);
|
||||
};
|
||||
|
||||
if (CoreProgressManager.shouldKeepTasksAsynchronous()) {
|
||||
ApplicationManager.getApplication().invokeLater(createFiles);
|
||||
}
|
||||
else {
|
||||
ApplicationManager.getApplication().invokeAndWait(createFiles);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Map<String, Set<ModuleNode>> collectDependencies(@NotNull List<ModuleFiles> modulesFiles) {
|
||||
PackageNamesCache packageNamesCache = new PackageNamesCache(myProject);
|
||||
Map<String, Set<ModuleNode>> packagesDeclaredInModules = new HashMap<>();
|
||||
|
||||
for (ModuleFiles moduleFiles : modulesFiles) {
|
||||
ModuleVisitor visitor = new ModuleVisitor(packageNamesCache::getPackageName);
|
||||
if (moduleFiles.files().isEmpty()) {
|
||||
myLogger.info("Output directory for module " + moduleFiles.module().getName() + " doesn't contain .class files");
|
||||
continue;
|
||||
}
|
||||
for (Path file : moduleFiles.files) {
|
||||
visitor.processFile(file.toFile());
|
||||
myProgressTracker.increment();
|
||||
}
|
||||
Set<String> declaredPackages = visitor.getDeclaredPackages();
|
||||
Set<String> requiredPackages = visitor.getRequiredPackages();
|
||||
|
||||
ModuleNode moduleNode = new ModuleNode(moduleFiles.module(), declaredPackages, requiredPackages, myUniqueModuleNames);
|
||||
for (String declaredPackage : declaredPackages) {
|
||||
packagesDeclaredInModules.computeIfAbsent(declaredPackage, key -> new HashSet<>()).add(moduleNode);
|
||||
}
|
||||
}
|
||||
return packagesDeclaredInModules;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<ModuleNode> prepareModulesWithDependencies(@NotNull Map<String, Set<ModuleNode>> packagesDeclaredInModules) {
|
||||
List<ModuleNode> modules = packagesDeclaredInModules.values().stream()
|
||||
.flatMap(Collection::stream).toList();
|
||||
|
||||
Map<PsiJavaModule, ModuleNode> nodesByDescriptor = new HashMap<>();
|
||||
for (ModuleNode module : modules) {
|
||||
if (module.getDescriptor() != null) {
|
||||
nodesByDescriptor.put(module.getDescriptor(), module);
|
||||
}
|
||||
|
||||
for (String packageName : module.getRequiredPackages()) {
|
||||
Set<ModuleNode> set = packagesDeclaredInModules.get(packageName);
|
||||
if (set == null) {
|
||||
PsiPackage psiPackage = JavaPsiFacade.getInstance(myProject).findPackage(packageName);
|
||||
if (psiPackage == null) continue;
|
||||
PsiJavaModule descriptor = ReadAction.compute(() -> findDescriptor(psiPackage));
|
||||
if (descriptor == null) continue;
|
||||
set = Set.of(nodesByDescriptor.computeIfAbsent(descriptor, ModuleNode::new));
|
||||
}
|
||||
else if (set.size() != 1) {
|
||||
myLogger.info("Split package " + packageName + " found in " + set);
|
||||
}
|
||||
module.getDependencies().addAll(set);
|
||||
}
|
||||
myProgressTracker.increment();
|
||||
}
|
||||
return modules;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<ModuleInfo> prepareModuleInfos(@NotNull List<ModuleNode> modules) {
|
||||
Set<String> requiredPackages = modules.stream()
|
||||
.map(ModuleNode::getRequiredPackages)
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
List<ModuleInfo> moduleInfo = new ArrayList<>();
|
||||
for (ModuleNode moduleNode : modules) {
|
||||
if (moduleNode.getDescriptor() != null) {
|
||||
myLogger.info("Module descriptor already exists in " + moduleNode);
|
||||
continue;
|
||||
}
|
||||
for (String packageName : moduleNode.getDeclaredPackages()) {
|
||||
if (requiredPackages.contains(packageName)) {
|
||||
moduleNode.addExport(packageName);
|
||||
}
|
||||
}
|
||||
|
||||
PsiDirectory rootDir = moduleNode.getRootDir();
|
||||
if (rootDir != null) {
|
||||
moduleInfo.add(new ModuleInfo(rootDir, moduleNode.getName(), moduleNode.getDependencies(), moduleNode.getExports()));
|
||||
}
|
||||
else {
|
||||
myLogger.info("Skipped module " + moduleNode + " because it doesn't have production source root");
|
||||
}
|
||||
myProgressTracker.increment();
|
||||
}
|
||||
return moduleInfo;
|
||||
}
|
||||
|
||||
private void createFiles(@NotNull Project project, @NotNull List<ModuleInfo> moduleInfos, @NotNull ProgressIndicator indicator) {
|
||||
indicator.setIndeterminate(false);
|
||||
int count = 0;
|
||||
double total = moduleInfos.size();
|
||||
FileTemplate template = FileTemplateManager.getInstance(project).getInternalTemplate(INTERNAL_MODULE_INFO_TEMPLATE_NAME);
|
||||
for (ModuleInfo moduleInfo : moduleInfos) {
|
||||
ProgressManager.getInstance().executeNonCancelableSection(() -> createFile(template, moduleInfo));
|
||||
indicator.setFraction(++count / total);
|
||||
}
|
||||
}
|
||||
|
||||
private void createFile(@NotNull FileTemplate template, @NotNull ModuleInfo moduleInfo) {
|
||||
if (moduleInfo.fileAlreadyExists()) return;
|
||||
Project project = moduleInfo.rootDir().getProject();
|
||||
Properties properties = FileTemplateManager.getInstance(project).getDefaultProperties();
|
||||
FileTemplateUtil.fillDefaultProperties(properties, moduleInfo.rootDir());
|
||||
properties.setProperty(FileTemplate.ATTRIBUTE_NAME, MODULE_INFO_CLASS);
|
||||
try {
|
||||
PsiJavaFile moduleInfoFile = // this is done to copy the file header to the output
|
||||
(PsiJavaFile)FileTemplateUtil.createFromTemplate(template, MODULE_INFO_FILE, properties, moduleInfo.rootDir());
|
||||
PsiJavaModule javaModule = moduleInfoFile.getModuleDeclaration();
|
||||
myLogger.assertTrue(javaModule != null, "module-info file should contain module declaration");
|
||||
|
||||
CharSequence moduleText = moduleInfo.createModuleText();
|
||||
PsiJavaFile dummyFile = (PsiJavaFile)PsiFileFactory.getInstance(project)
|
||||
.createFileFromText(MODULE_INFO_FILE, JavaLanguage.INSTANCE, moduleText);
|
||||
PsiJavaModule actualModule = dummyFile.getModuleDeclaration();
|
||||
myLogger.assertTrue(actualModule != null, "module declaration wasn't created");
|
||||
javaModule.replace(actualModule);
|
||||
CodeStyleManager.getInstance(project).reformat(moduleInfoFile);
|
||||
}
|
||||
catch (Exception e) {
|
||||
myLogger.error("Failed to create module-info.java in " + moduleInfo.rootDir().getVirtualFile().getPath() + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PsiJavaModule findDescriptor(@NotNull PsiPackage psiPackage) {
|
||||
PsiManager psiManager = psiPackage.getManager();
|
||||
for (PsiDirectory directory : psiPackage.getDirectories()) {
|
||||
PsiDirectory managerDirectory = psiManager.findDirectory(directory.getVirtualFile());
|
||||
if (managerDirectory != null) {
|
||||
return JavaModuleGraphUtil.findDescriptorByElement(managerDirectory);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static @NlsContexts.Command String getCommandTitle() {
|
||||
return JavaRefactoringBundle.message("generate.module.descriptors.command.title");
|
||||
}
|
||||
|
||||
private static class PackageNamesCache {
|
||||
private final Map<String, Boolean> myPackages = new HashMap<>();
|
||||
private final JavaPsiFacade myPsiFacade;
|
||||
|
||||
PackageNamesCache(@NotNull Project project) {
|
||||
myPsiFacade = JavaPsiFacade.getInstance(project);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getPackageName(@NotNull String className) {
|
||||
int dotPos;
|
||||
while ((dotPos = className.lastIndexOf('.')) > 0) {
|
||||
className = className.substring(0, dotPos);
|
||||
Boolean isPackage = myPackages.computeIfAbsent(className, packageName -> ReadAction.compute(() -> myPsiFacade.findPackage(packageName) != null));
|
||||
if (isPackage) return className;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
record ModuleFiles(@NotNull Module module, @NotNull List<Path> files) {
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,12 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInspection.java19api;
|
||||
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.JavaModuleGraphUtil;
|
||||
import com.intellij.codeInspection.AbstractDependencyVisitor;
|
||||
import com.intellij.ide.fileTemplates.FileTemplate;
|
||||
import com.intellij.ide.fileTemplates.FileTemplateManager;
|
||||
import com.intellij.ide.fileTemplates.FileTemplateUtil;
|
||||
import com.intellij.codeInspection.java19api.DescriptorsGenerator.ModuleFiles;
|
||||
import com.intellij.java.refactoring.JavaRefactoringBundle;
|
||||
import com.intellij.lang.java.JavaLanguage;
|
||||
import com.intellij.lang.java.lexer.JavaLexer;
|
||||
import com.intellij.openapi.actionSystem.ActionUpdateThread;
|
||||
import com.intellij.openapi.actionSystem.AnAction;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.ReadAction;
|
||||
import com.intellij.openapi.application.ex.ApplicationManagerEx;
|
||||
import com.intellij.openapi.command.CommandProcessor;
|
||||
import com.intellij.openapi.compiler.CompileScope;
|
||||
import com.intellij.openapi.compiler.CompilerManager;
|
||||
import com.intellij.openapi.compiler.CompilerPaths;
|
||||
@@ -23,43 +14,33 @@ import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.module.LanguageLevelUtil;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.module.ModuleManager;
|
||||
import com.intellij.openapi.module.ModuleUtilCore;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.progress.Task;
|
||||
import com.intellij.openapi.progress.impl.CoreProgressManager;
|
||||
import com.intellij.openapi.project.DumbService;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.projectRoots.JavaSdkVersion;
|
||||
import com.intellij.openapi.projectRoots.ProjectJdkTable;
|
||||
import com.intellij.openapi.projectRoots.ex.JavaSdkUtil;
|
||||
import com.intellij.openapi.roots.ModuleRootManager;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.openapi.util.NlsContexts;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager;
|
||||
import com.intellij.psi.impl.java.stubs.index.JavaModuleNameIndex;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.search.ProjectScope;
|
||||
import com.intellij.psi.CommonClassNames;
|
||||
import com.intellij.refactoring.util.CommonRefactoringUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.text.UniqueNameGenerator;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.intellij.ide.fileTemplates.JavaTemplateUtil.INTERNAL_MODULE_INFO_TEMPLATE_NAME;
|
||||
import static com.intellij.psi.PsiJavaModule.*;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public final class Java9GenerateModuleDescriptorsAction extends AnAction {
|
||||
private static final Logger LOG = Logger.getInstance(Java9GenerateModuleDescriptorsAction.class);
|
||||
private static final String CLASS_FILE_PATTERN = "glob:*" + CommonClassNames.CLASS_FILE_EXTENSION;
|
||||
|
||||
@Override
|
||||
public void update(@NotNull AnActionEvent e) {
|
||||
@@ -78,634 +59,94 @@ public final class Java9GenerateModuleDescriptorsAction extends AnAction {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||
Project project = e.getProject();
|
||||
final Project project = e.getProject();
|
||||
if (project == null) return;
|
||||
CompilerManager compilerManager = CompilerManager.getInstance(project);
|
||||
CompileScope scope = compilerManager.createProjectCompileScope(project);
|
||||
if (!compilerManager.isUpToDate(scope)) {
|
||||
int result = Messages.showYesNoCancelDialog(
|
||||
project, JavaRefactoringBundle.message("generate.module.descriptors.rebuild.message"), getTitle(), null);
|
||||
if (result == Messages.CANCEL) {
|
||||
return;
|
||||
}
|
||||
if (result == Messages.YES) {
|
||||
compilerManager.compile(scope, (aborted, errors, warnings, compileContext) -> {
|
||||
if (!aborted && errors == 0) {
|
||||
generate(project);
|
||||
}
|
||||
});
|
||||
return;
|
||||
switch (result) {
|
||||
case Messages.YES:
|
||||
compilerManager.compile(scope, (aborted, errors, warnings, compileContext) -> {
|
||||
if (!aborted && errors == 0) generate(project);
|
||||
});
|
||||
return;
|
||||
case Messages.CANCEL:
|
||||
return;
|
||||
}
|
||||
}
|
||||
generate(project);
|
||||
}
|
||||
|
||||
private static void generate(@NotNull Project project) {
|
||||
DumbService.getInstance(project).smartInvokeLater(
|
||||
() -> generateWhenSmart(project, new UniqueModuleNames(project)));
|
||||
DumbService.getInstance(project).smartInvokeLater(() -> generateWhenSmart(project));
|
||||
}
|
||||
|
||||
private static void generateWhenSmart(@NotNull Project project, @NotNull UniqueModuleNames uniqueModuleNames) {
|
||||
private static void generateWhenSmart(@NotNull Project project) {
|
||||
LOG.assertTrue(!DumbService.isDumb(project), "Module name index should be ready");
|
||||
final UniqueModuleNames names = new UniqueModuleNames(project);
|
||||
ProgressManager.getInstance().run(
|
||||
new Task.Backgroundable(project, getTitle(), true) {
|
||||
@Override
|
||||
public void run(@NotNull ProgressIndicator indicator) {
|
||||
Map<Module, List<File>> classFiles = new HashMap<>();
|
||||
int totalFiles = collectClassFiles(project, classFiles);
|
||||
if (totalFiles != 0) {
|
||||
new DescriptorsGenerator(project, uniqueModuleNames).generate(classFiles, indicator, totalFiles);
|
||||
final List<ModuleFiles> moduleFiles = collectClassFiles(project);
|
||||
if (ContainerUtil.exists(moduleFiles, module -> !module.files().isEmpty())) {
|
||||
new DescriptorsGenerator(project, names, LOG).generate(moduleFiles, indicator);
|
||||
}
|
||||
else {
|
||||
CommonRefactoringUtil.showErrorHint(project, null,
|
||||
JavaRefactoringBundle.message("generate.module.descriptors.build.required.message"),
|
||||
getTitle(), null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static int collectClassFiles(@NotNull Project project, @NotNull Map<Module, List<File>> classFiles) {
|
||||
@NotNull
|
||||
private static List<ModuleFiles> collectClassFiles(@NotNull Project project) {
|
||||
ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
|
||||
indicator.setIndeterminate(true);
|
||||
indicator.setText(JavaRefactoringBundle.message("generate.module.descriptors.scanning.message"));
|
||||
|
||||
Module[] modules = StreamEx.of(ModuleManager.getInstance(project).getModules())
|
||||
.filter(module -> mayContainModuleInfo(module))
|
||||
.toArray(Module.EMPTY_ARRAY);
|
||||
if (modules.length == 0) {
|
||||
CommonRefactoringUtil.showErrorHint(
|
||||
project, null, JavaRefactoringBundle.message("generate.module.descriptors.no.suitable.modules.message"), getTitle(), null);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int totalFiles = 0;
|
||||
for (Module module : modules) {
|
||||
List<File> moduleClasses = new ArrayList<>();
|
||||
classFiles.put(module, moduleClasses);
|
||||
|
||||
String production = CompilerPaths.getModuleOutputPath(module, false);
|
||||
File productionRoot = production != null ? new File(production) : null;
|
||||
if (productionRoot != null) {
|
||||
collectClassFiles(productionRoot, moduleClasses);
|
||||
List<ModuleFiles> moduleFiles = new ArrayList<>();
|
||||
for (Module module : ModuleManager.getInstance(project).getModules()) {
|
||||
if (!mayContainModuleInfo(module)) continue;
|
||||
try {
|
||||
String production = CompilerPaths.getModuleOutputPath(module, false);
|
||||
Path productionRoot = production != null ? Paths.get(production) : null;
|
||||
moduleFiles.add(new ModuleFiles(module, collectClassFiles(productionRoot)));
|
||||
}
|
||||
totalFiles += moduleClasses.size();
|
||||
}
|
||||
if (totalFiles == 0) {
|
||||
CommonRefactoringUtil.showErrorHint(
|
||||
project, null, JavaRefactoringBundle.message("generate.module.descriptors.build.required.message"), getTitle(), null);
|
||||
}
|
||||
return totalFiles;
|
||||
}
|
||||
|
||||
private static boolean mayContainModuleInfo(Module module) {
|
||||
return ReadAction.compute(() -> LanguageLevelUtil.getEffectiveLanguageLevel(module).isAtLeast(LanguageLevel.JDK_1_9)
|
||||
);
|
||||
}
|
||||
|
||||
private static void collectClassFiles(@NotNull File file, @NotNull List<? super File> files) {
|
||||
final File[] children = file.listFiles();
|
||||
if (children != null) { // is Directory
|
||||
for (File child : children) {
|
||||
collectClassFiles(child, files);
|
||||
catch (IOException e) {
|
||||
CommonRefactoringUtil.showErrorHint(
|
||||
project, null,
|
||||
JavaRefactoringBundle.message("generate.module.descriptors.io.exceptions.message", module.getName()),
|
||||
getTitle(), null);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
else if (file.getName().endsWith(CommonClassNames.CLASS_FILE_EXTENSION)) {
|
||||
files.add(file);
|
||||
if (moduleFiles.isEmpty()) {
|
||||
CommonRefactoringUtil.showErrorHint(project, null,
|
||||
JavaRefactoringBundle.message("generate.module.descriptors.no.suitable.modules.message"),
|
||||
getTitle(), null);
|
||||
}
|
||||
return moduleFiles;
|
||||
}
|
||||
|
||||
private static boolean mayContainModuleInfo(@NotNull final Module module) {
|
||||
return ReadAction.compute(() -> LanguageLevelUtil.getEffectiveLanguageLevel(module).isAtLeast(LanguageLevel.JDK_1_9));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<Path> collectClassFiles(@Nullable Path file) throws IOException {
|
||||
if (file == null) return Collections.emptyList();
|
||||
try (Stream<Path> stream = Files.walk(file)) {
|
||||
final PathMatcher matcher = FileSystems.getDefault().getPathMatcher(CLASS_FILE_PATTERN);
|
||||
return stream.filter(path -> matcher.matches(path.getFileName())).toList();
|
||||
}
|
||||
}
|
||||
|
||||
private static @NlsContexts.DialogTitle String getTitle() {
|
||||
return JavaRefactoringBundle.message("generate.module.descriptors.title");
|
||||
}
|
||||
|
||||
private static @NlsContexts.Command String getCommandTitle() {
|
||||
return JavaRefactoringBundle.message("generate.module.descriptors.command.title");
|
||||
}
|
||||
|
||||
private static class ProgressTracker {
|
||||
ProgressIndicator myIndicator;
|
||||
|
||||
int myCount;
|
||||
int mySize;
|
||||
int myPhase;
|
||||
double myUpToNow;
|
||||
private final double[] myPhases;
|
||||
|
||||
ProgressTracker(double... phases) {
|
||||
myPhases = phases;
|
||||
}
|
||||
|
||||
void startPhase(@NlsContexts.ProgressText String text, int size) {
|
||||
myIndicator.setText(text);
|
||||
myCount = 0;
|
||||
mySize = Math.min(size, 1);
|
||||
}
|
||||
|
||||
public void nextPhase() {
|
||||
myUpToNow += myPhases[myPhase++];
|
||||
}
|
||||
|
||||
public void increment() {
|
||||
myIndicator.setFraction(myUpToNow + myPhases[myPhase] * ++myCount / (double)mySize);
|
||||
}
|
||||
|
||||
public void init(ProgressIndicator indicator) {
|
||||
myIndicator = indicator;
|
||||
myIndicator.setFraction(0);
|
||||
myIndicator.setIndeterminate(false);
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
myIndicator = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class DescriptorsGenerator {
|
||||
private final Project myProject;
|
||||
private final UniqueModuleNames myUniqueModuleNames;
|
||||
|
||||
private final List<ModuleNode> myModuleNodes = new ArrayList<>();
|
||||
private final Set<String> myUsedExternallyPackages = new HashSet<>();
|
||||
|
||||
private final ProgressTracker myProgressTracker = new ProgressTracker(0.5, 0.3, 0.2);
|
||||
|
||||
DescriptorsGenerator(@NotNull Project project, @NotNull UniqueModuleNames uniqueModuleNames) {
|
||||
myProject = project;
|
||||
myUniqueModuleNames = uniqueModuleNames;
|
||||
}
|
||||
|
||||
void generate(Map<Module, List<File>> classFiles, ProgressIndicator indicator, int totalFiles) {
|
||||
myProgressTracker.init(indicator);
|
||||
List<ModuleInfo> moduleInfos;
|
||||
try {
|
||||
myProgressTracker.startPhase(JavaRefactoringBundle.message("generate.module.descriptors.collecting.message"), totalFiles);
|
||||
Map<String, Set<ModuleNode>> packagesDeclaredInModules = collectDependencies(classFiles);
|
||||
myProgressTracker.nextPhase();
|
||||
|
||||
myProgressTracker.startPhase(JavaRefactoringBundle.message("generate.module.descriptors.analysing.message"), myModuleNodes.size());
|
||||
analyseDependencies(packagesDeclaredInModules);
|
||||
myProgressTracker.nextPhase();
|
||||
|
||||
myProgressTracker.startPhase(JavaRefactoringBundle.message("generate.module.descriptors.preparing.message"), myModuleNodes.size());
|
||||
moduleInfos = prepareModuleInfos();
|
||||
myProgressTracker.nextPhase();
|
||||
}
|
||||
finally {
|
||||
myProgressTracker.dispose();
|
||||
}
|
||||
createFilesLater(moduleInfos);
|
||||
}
|
||||
|
||||
private void createFilesLater(List<ModuleInfo> moduleInfos) {
|
||||
Runnable createFiles = () -> {
|
||||
if (myProject.isDisposed()) return;
|
||||
CommandProcessor.getInstance().executeCommand(myProject, () ->
|
||||
ApplicationManagerEx.getApplicationEx().runWriteActionWithCancellableProgressInDispatchThread(
|
||||
getCommandTitle(), myProject, null,
|
||||
indicator -> createFiles(myProject, moduleInfos, indicator)), getCommandTitle(), null);
|
||||
};
|
||||
|
||||
if (CoreProgressManager.shouldKeepTasksAsynchronous()) {
|
||||
ApplicationManager.getApplication().invokeLater(createFiles);
|
||||
}
|
||||
else {
|
||||
ApplicationManager.getApplication().invokeAndWait(createFiles);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Set<ModuleNode>> collectDependencies(Map<Module, List<File>> classFiles) {
|
||||
PackageNamesCache packageNamesCache = new PackageNamesCache(myProject);
|
||||
Map<String, Set<ModuleNode>> packagesDeclaredInModules = new HashMap<>();
|
||||
|
||||
for (Map.Entry<Module, List<File>> entry : classFiles.entrySet()) {
|
||||
Module module = entry.getKey();
|
||||
|
||||
ModuleVisitor visitor = new ModuleVisitor(packageNamesCache);
|
||||
List<File> files = entry.getValue();
|
||||
if (files.isEmpty()) {
|
||||
LOG.info("Output directory for module " + module.getName() + " doesn't contain .class files");
|
||||
continue;
|
||||
}
|
||||
for (File file : files) {
|
||||
visitor.processFile(file);
|
||||
myProgressTracker.increment();
|
||||
}
|
||||
Set<String> declaredPackages = visitor.getDeclaredPackages();
|
||||
Set<String> requiredPackages = visitor.getRequiredPackages();
|
||||
requiredPackages.removeAll(declaredPackages);
|
||||
|
||||
myUsedExternallyPackages.addAll(requiredPackages);
|
||||
ModuleNode moduleNode = new ModuleNode(module, declaredPackages, requiredPackages, myUniqueModuleNames);
|
||||
myModuleNodes.add(moduleNode);
|
||||
for (String declaredPackage : declaredPackages) {
|
||||
packagesDeclaredInModules.computeIfAbsent(declaredPackage, __ -> new HashSet<>()).add(moduleNode);
|
||||
}
|
||||
}
|
||||
return packagesDeclaredInModules;
|
||||
}
|
||||
|
||||
private void analyseDependencies(Map<String, Set<ModuleNode>> packagesDeclaredInModules) {
|
||||
Map<PsiJavaModule, ModuleNode> nodesByDescriptor = new HashMap<>();
|
||||
for (ModuleNode moduleNode : myModuleNodes) {
|
||||
if (moduleNode.getDescriptor() != null) {
|
||||
nodesByDescriptor.put(moduleNode.getDescriptor(), moduleNode);
|
||||
}
|
||||
|
||||
for (String packageName : moduleNode.getRequiredPackages()) {
|
||||
Set<ModuleNode> set = packagesDeclaredInModules.get(packageName);
|
||||
if (set == null) {
|
||||
PsiPackage psiPackage = JavaPsiFacade.getInstance(myProject).findPackage(packageName);
|
||||
if (psiPackage != null) {
|
||||
PsiJavaModule descriptor = ReadAction.compute(() -> findDescriptor(psiPackage));
|
||||
if (descriptor != null) {
|
||||
ModuleNode dependencyNode = nodesByDescriptor.computeIfAbsent(descriptor, ModuleNode::new);
|
||||
moduleNode.getDependencies().add(dependencyNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (set.size() != 1) {
|
||||
LOG.info("Split package " + packageName + " found in " + set);
|
||||
}
|
||||
moduleNode.getDependencies().addAll(set);
|
||||
}
|
||||
}
|
||||
myProgressTracker.increment();
|
||||
}
|
||||
}
|
||||
|
||||
private List<ModuleInfo> prepareModuleInfos() {
|
||||
List<ModuleInfo> moduleInfo = new ArrayList<>();
|
||||
for (ModuleNode moduleNode : myModuleNodes) {
|
||||
if (moduleNode.getDescriptor() != null) {
|
||||
LOG.info("Module descriptor already exists in " + moduleNode);
|
||||
continue;
|
||||
}
|
||||
for (String packageName : moduleNode.getDeclaredPackages()) {
|
||||
if (myUsedExternallyPackages.contains(packageName)) {
|
||||
moduleNode.getExports().add(packageName);
|
||||
}
|
||||
}
|
||||
|
||||
PsiDirectory rootDir = moduleNode.getRootDir();
|
||||
if (rootDir != null) {
|
||||
List<String> dependencies = StreamEx.of(moduleNode.getSortedDependencies())
|
||||
.map(ModuleNode::getName)
|
||||
.filter(name -> !JAVA_BASE.equals(name))
|
||||
.toList();
|
||||
|
||||
List<String> exports = moduleNode.getSortedExports();
|
||||
moduleInfo.add(new ModuleInfo(rootDir, moduleNode.getName(), dependencies, exports));
|
||||
}
|
||||
else {
|
||||
LOG.info("Skipped module " + moduleNode + " because it doesn't have production source root");
|
||||
}
|
||||
myProgressTracker.increment();
|
||||
}
|
||||
return moduleInfo;
|
||||
}
|
||||
|
||||
private static void createFiles(Project project, List<ModuleInfo> moduleInfos, ProgressIndicator indicator) {
|
||||
indicator.setIndeterminate(false);
|
||||
int count = 0;
|
||||
double total = moduleInfos.size();
|
||||
FileTemplate template = FileTemplateManager.getInstance(project).getInternalTemplate(INTERNAL_MODULE_INFO_TEMPLATE_NAME);
|
||||
for (ModuleInfo moduleInfo : moduleInfos) {
|
||||
ProgressManager.getInstance().executeNonCancelableSection(() -> createFile(template, moduleInfo));
|
||||
indicator.setFraction(++count / total);
|
||||
}
|
||||
}
|
||||
|
||||
private static void createFile(FileTemplate template, ModuleInfo moduleInfo) {
|
||||
if (moduleInfo.fileAlreadyExists()) {
|
||||
return;
|
||||
}
|
||||
Project project = moduleInfo.myRootDir.getProject();
|
||||
Properties properties = FileTemplateManager.getInstance(project).getDefaultProperties();
|
||||
FileTemplateUtil.fillDefaultProperties(properties, moduleInfo.myRootDir);
|
||||
properties.setProperty(FileTemplate.ATTRIBUTE_NAME, MODULE_INFO_CLASS);
|
||||
try {
|
||||
PsiJavaFile moduleInfoFile = // this is done to copy the file header to the output
|
||||
(PsiJavaFile)FileTemplateUtil.createFromTemplate(template, MODULE_INFO_FILE, properties, moduleInfo.myRootDir);
|
||||
PsiJavaModule javaModule = moduleInfoFile.getModuleDeclaration();
|
||||
LOG.assertTrue(javaModule != null, "module-info file should contain module declaration");
|
||||
|
||||
CharSequence moduleText = moduleInfo.createModuleText();
|
||||
PsiJavaFile dummyFile = (PsiJavaFile)PsiFileFactory.getInstance(project)
|
||||
.createFileFromText(MODULE_INFO_FILE, JavaLanguage.INSTANCE, moduleText);
|
||||
PsiJavaModule actualModule = dummyFile.getModuleDeclaration();
|
||||
LOG.assertTrue(actualModule != null, "module declaration wasn't created");
|
||||
javaModule.replace(actualModule);
|
||||
CodeStyleManager.getInstance(project).reformat(moduleInfoFile);
|
||||
}
|
||||
catch (Exception e) {
|
||||
LOG.error("Failed to create module-info.java in " + moduleInfo.myRootDir.getVirtualFile().getPath() + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PsiJavaModule findDescriptor(PsiPackage psiPackage) {
|
||||
PsiManager psiManager = psiPackage.getManager();
|
||||
return StreamEx.of(psiPackage.getDirectories())
|
||||
.map(PsiDirectory::getVirtualFile)
|
||||
.nonNull()
|
||||
.map(psiManager::findDirectory)
|
||||
.nonNull()
|
||||
.findAny()
|
||||
.map(JavaModuleGraphUtil::findDescriptorByElement)
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ModuleNode implements Comparable<ModuleNode> {
|
||||
private final Module myModule;
|
||||
private final Set<String> myDeclaredPackages;
|
||||
private final Set<String> myRequiredPackages;
|
||||
private final Set<ModuleNode> myDependencies = new HashSet<>();
|
||||
private final Set<String> myExports = new HashSet<>();
|
||||
private final PsiJavaModule myDescriptor;
|
||||
private final String myName;
|
||||
|
||||
ModuleNode(@NotNull Module module,
|
||||
@NotNull Set<String> declaredPackages,
|
||||
@NotNull Set<String> requiredPackages,
|
||||
@NotNull UniqueModuleNames uniqueModuleNames) {
|
||||
myModule = module;
|
||||
myDeclaredPackages = declaredPackages;
|
||||
myRequiredPackages = requiredPackages;
|
||||
|
||||
myDescriptor = ReadAction.compute(() -> {
|
||||
VirtualFile[] sourceRoots = ModuleRootManager.getInstance(module).getSourceRoots(false);
|
||||
return sourceRoots.length != 0 ? findDescriptor(module, sourceRoots[0]) : null;
|
||||
});
|
||||
myName = myDescriptor != null ? myDescriptor.getName() : uniqueModuleNames.getUniqueName(myModule);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PsiJavaModule findDescriptor(@NotNull Module module, VirtualFile root) {
|
||||
return JavaModuleGraphUtil.findDescriptorByFile(root, module.getProject());
|
||||
}
|
||||
|
||||
ModuleNode(@NotNull PsiJavaModule descriptor) {
|
||||
myModule = ReadAction.compute(() -> ModuleUtilCore.findModuleForPsiElement(descriptor));
|
||||
myDeclaredPackages = Collections.emptySet();
|
||||
myRequiredPackages = Collections.emptySet();
|
||||
myDescriptor = descriptor;
|
||||
myName = myDescriptor.getName();
|
||||
}
|
||||
|
||||
public Set<String> getDeclaredPackages() {
|
||||
return myDeclaredPackages;
|
||||
}
|
||||
|
||||
public Set<String> getRequiredPackages() {
|
||||
return myRequiredPackages;
|
||||
}
|
||||
|
||||
public Set<ModuleNode> getDependencies() {
|
||||
return myDependencies;
|
||||
}
|
||||
|
||||
public List<ModuleNode> getSortedDependencies() {
|
||||
ArrayList<ModuleNode> list = new ArrayList<>(myDependencies);
|
||||
list.sort(ModuleNode::compareTo);
|
||||
return list;
|
||||
}
|
||||
|
||||
public Set<String> getExports() {
|
||||
return myExports;
|
||||
}
|
||||
|
||||
public List<String> getSortedExports() {
|
||||
ArrayList<String> list = new ArrayList<>(myExports);
|
||||
list.sort(String::compareTo);
|
||||
return list;
|
||||
}
|
||||
|
||||
public PsiJavaModule getDescriptor() {
|
||||
return myDescriptor;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getName() {
|
||||
return myName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return myName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this == o || o instanceof ModuleNode && myName.equals(((ModuleNode)o).myName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return myName.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull ModuleNode o) {
|
||||
int m1 = myModule == null ? 0 : 1, m2 = o.myModule == null ? 0 : 1;
|
||||
if (m1 != m2) return m1 - m2;
|
||||
int j1 = myName.startsWith("java.") || myName.startsWith("javax.") ? 0 : 1;
|
||||
int j2 = o.myName.startsWith("java.") || o.myName.startsWith("javax.") ? 0 : 1;
|
||||
if (j1 != j2) return j1 - j2;
|
||||
return StringUtil.compare(myName, o.myName, false);
|
||||
}
|
||||
|
||||
public PsiDirectory getRootDir() {
|
||||
if (myModule == null) return null;
|
||||
return ReadAction.compute(() -> {
|
||||
ModuleRootManager moduleManager = ModuleRootManager.getInstance(myModule);
|
||||
PsiManager psiManager = PsiManager.getInstance(myModule.getProject());
|
||||
return findJavaDirectory(psiManager, moduleManager.getSourceRoots(false));
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PsiDirectory findJavaDirectory(PsiManager psiManager, VirtualFile[] roots) {
|
||||
return StreamEx.of(roots)
|
||||
.sorted(Comparator.comparingInt((VirtualFile vFile) -> "java".equals(vFile.getName()) ? 0 : 1)
|
||||
.thenComparing(VirtualFile::getName))
|
||||
.map(psiManager::findDirectory)
|
||||
.nonNull()
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class NameConverter { // "public" is for tests
|
||||
private static final Pattern NON_NAME = Pattern.compile("[^A-Za-z0-9]");
|
||||
private static final Pattern DOT_SEQUENCE = Pattern.compile("\\.{2,}");
|
||||
private static final Pattern SINGLE_DOT = Pattern.compile("\\.");
|
||||
|
||||
@NotNull
|
||||
public static String convertModuleName(@NotNull String name) {
|
||||
// All non-alphanumeric characters ([^A-Za-z0-9]) are replaced with a dot (".") ...
|
||||
name = NON_NAME.matcher(name).replaceAll(".");
|
||||
// ... all repeating dots are replaced with one dot ...
|
||||
name = DOT_SEQUENCE.matcher(name).replaceAll(".");
|
||||
// ... and all leading and trailing dots are removed.
|
||||
name = StringUtil.trimLeading(StringUtil.trimTrailing(name, '.'), '.');
|
||||
|
||||
// sanitize keywords and leading digits
|
||||
String[] parts = splitByDots(name);
|
||||
StringBuilder builder = new StringBuilder();
|
||||
boolean first = true;
|
||||
for (String part : parts) {
|
||||
if (part.length() == 0) {
|
||||
continue;
|
||||
}
|
||||
if (Character.isJavaIdentifierStart(part.charAt(0))) {
|
||||
if (!first) {
|
||||
builder.append('.');
|
||||
}
|
||||
builder.append(part);
|
||||
if (JavaLexer.isKeyword(part, LanguageLevel.JDK_1_9)) {
|
||||
builder.append('x');
|
||||
}
|
||||
}
|
||||
else { // it's a leading digit
|
||||
if (first) {
|
||||
builder.append("module");
|
||||
}
|
||||
builder.append(part);
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
static @NotNull String @NotNull [] splitByDots(@NotNull String text) {
|
||||
return SINGLE_DOT.split(text);
|
||||
}
|
||||
}
|
||||
|
||||
private static class UniqueModuleNames {
|
||||
private final UniqueNameGenerator myNameGenerator;
|
||||
|
||||
UniqueModuleNames(@NotNull Project project) {
|
||||
LOG.assertTrue(!DumbService.isDumb(project), "Module name index should be ready");
|
||||
|
||||
JavaModuleNameIndex index = JavaModuleNameIndex.getInstance();
|
||||
GlobalSearchScope scope = ProjectScope.getAllScope(project);
|
||||
|
||||
List<PsiJavaModule> modules = new ArrayList<>();
|
||||
for (String key : index.getAllKeys(project)) {
|
||||
modules.addAll(index.getModules(key, project, scope));
|
||||
}
|
||||
myNameGenerator = new UniqueNameGenerator(modules, module -> ReadAction.compute(() -> module.getName()));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getUniqueName(@NotNull Module module) {
|
||||
String name = NameConverter.convertModuleName(module.getName());
|
||||
return myNameGenerator.generateUniqueName(name);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PackageNamesCache {
|
||||
private final Map<String, Boolean> myPackages = new HashMap<>();
|
||||
private final JavaPsiFacade myPsiFacade;
|
||||
|
||||
PackageNamesCache(Project project) {
|
||||
myPsiFacade = JavaPsiFacade.getInstance(project);
|
||||
}
|
||||
|
||||
private String getPackageName(String className) {
|
||||
for (int dotPos = className.lastIndexOf('.'); dotPos > 0; dotPos = className.lastIndexOf('.', dotPos - 1)) {
|
||||
String packageName = className.substring(0, dotPos);
|
||||
Boolean isPackage = myPackages.computeIfAbsent(packageName, this::isExistingPackage);
|
||||
if (isPackage) {
|
||||
return packageName;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Boolean isExistingPackage(String packageName) {
|
||||
return ReadAction.compute(() -> myPsiFacade.findPackage(packageName) != null);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ModuleVisitor extends AbstractDependencyVisitor {
|
||||
private final Set<String> myRequiredPackages = new HashSet<>();
|
||||
private final Set<String> myDeclaredPackages = new HashSet<>();
|
||||
private final PackageNamesCache myPackageNamesCache;
|
||||
|
||||
ModuleVisitor(PackageNamesCache packageNamesCache) {
|
||||
myPackageNamesCache = packageNamesCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addClassName(String className) {
|
||||
String packageName = myPackageNamesCache.getPackageName(className);
|
||||
if (packageName != null) {
|
||||
myRequiredPackages.add(packageName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||
super.visit(version, access, name, signature, superName, interfaces);
|
||||
|
||||
String packageName = myPackageNamesCache.getPackageName(getCurrentClassName());
|
||||
if (packageName != null) {
|
||||
myDeclaredPackages.add(packageName);
|
||||
}
|
||||
}
|
||||
|
||||
public Set<String> getRequiredPackages() {
|
||||
return myRequiredPackages;
|
||||
}
|
||||
|
||||
public Set<String> getDeclaredPackages() {
|
||||
return myDeclaredPackages;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ModuleInfo {
|
||||
final PsiDirectory myRootDir;
|
||||
final String myName;
|
||||
final List<String> myRequires;
|
||||
final List<String> myExports;
|
||||
|
||||
private ModuleInfo(@NotNull PsiDirectory rootDir,
|
||||
@NotNull String name,
|
||||
@NotNull List<String> requires,
|
||||
@NotNull List<String> exports) {
|
||||
myRootDir = rootDir;
|
||||
myName = name;
|
||||
myRequires = requires;
|
||||
myExports = exports;
|
||||
}
|
||||
|
||||
boolean fileAlreadyExists() {
|
||||
return StreamEx.of(myRootDir.getChildren())
|
||||
.select(PsiFile.class)
|
||||
.map(PsiFileSystemItem::getName)
|
||||
.anyMatch(MODULE_INFO_FILE::equals);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
CharSequence createModuleText() {
|
||||
StringBuilder text = new StringBuilder();
|
||||
text.append("module ").append(myName).append(" {");
|
||||
for (String dependency : myRequires) {
|
||||
if ("java.base".equals(dependency)) {
|
||||
continue;
|
||||
}
|
||||
boolean isBadSyntax = StreamEx.of(NameConverter.splitByDots(dependency))
|
||||
.anyMatch(part -> JavaLexer.isKeyword(part, LanguageLevel.JDK_1_9));
|
||||
text.append('\n').append(isBadSyntax ? "// " : " ").append(PsiKeyword.REQUIRES).append(' ').append(dependency).append(";");
|
||||
}
|
||||
if (!myRequires.isEmpty() && !myExports.isEmpty()) {
|
||||
text.append('\n');
|
||||
}
|
||||
for (String packageName : myExports) {
|
||||
text.append("\n ").append(PsiKeyword.EXPORTS).append(' ').append(packageName).append(";");
|
||||
}
|
||||
text.append("\n}");
|
||||
return text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInspection.java19api;
|
||||
|
||||
import com.intellij.lang.java.lexer.JavaLexer;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
import com.intellij.psi.PsiDirectory;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.PsiFileSystemItem;
|
||||
import com.intellij.psi.PsiKeyword;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import static com.intellij.psi.PsiJavaModule.JAVA_BASE;
|
||||
import static com.intellij.psi.PsiJavaModule.MODULE_INFO_FILE;
|
||||
|
||||
record ModuleInfo(@NotNull PsiDirectory rootDir,
|
||||
@NotNull String name,
|
||||
@NotNull Set<ModuleNode> requires,
|
||||
@NotNull Set<String> exports) {
|
||||
boolean fileAlreadyExists() {
|
||||
return StreamEx.of(rootDir().getChildren())
|
||||
.select(PsiFile.class)
|
||||
.map(PsiFileSystemItem::getName)
|
||||
.anyMatch(MODULE_INFO_FILE::equals);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
CharSequence createModuleText() {
|
||||
CharSequence requires = requiresText();
|
||||
CharSequence exports = exportsText();
|
||||
|
||||
return new StringBuilder().append(PsiKeyword.MODULE).append(" ").append(name()).append(" {\n")
|
||||
.append(requires)
|
||||
.append((!requires.isEmpty() && !exports.isEmpty()) ? "\n" : "")
|
||||
.append(exports)
|
||||
.append("}");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private CharSequence requiresText() {
|
||||
StringBuilder text = new StringBuilder();
|
||||
for (ModuleNode dependency : requires()) {
|
||||
final String dependencyName = dependency.getName();
|
||||
if (JAVA_BASE.equals(dependencyName)) continue;
|
||||
boolean isBadSyntax = ContainerUtil.or(dependencyName.split("\\."),
|
||||
part -> JavaLexer.isKeyword(part, LanguageLevel.JDK_1_9));
|
||||
text.append(isBadSyntax ? "// " : " ").append(PsiKeyword.REQUIRES).append(' ').append(dependencyName).append(";\n");
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private CharSequence exportsText() {
|
||||
StringBuilder text = new StringBuilder();
|
||||
for (String packageName : exports()) {
|
||||
text.append(PsiKeyword.EXPORTS).append(' ').append(packageName).append(";\n");
|
||||
}
|
||||
return text;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInspection.java19api;
|
||||
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.JavaModuleGraphUtil;
|
||||
import com.intellij.openapi.application.ReadAction;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.module.ModuleUtilCore;
|
||||
import com.intellij.openapi.roots.ModuleRootManager;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiDirectory;
|
||||
import com.intellij.psi.PsiJavaModule;
|
||||
import com.intellij.psi.PsiManager;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
class ModuleNode implements Comparable<ModuleNode> {
|
||||
private final Module myModule;
|
||||
private final Set<String> myDeclaredPackages;
|
||||
private final Set<String> myRequiredPackages;
|
||||
private final Set<ModuleNode> myDependencies = new TreeSet<>();
|
||||
private final Set<String> myExports = new TreeSet<>();
|
||||
private final PsiJavaModule myDescriptor;
|
||||
private final String myName;
|
||||
|
||||
ModuleNode(@NotNull Module module,
|
||||
@NotNull Set<String> declaredPackages,
|
||||
@NotNull Set<String> requiredPackages,
|
||||
@NotNull UniqueModuleNames uniqueModuleNames) {
|
||||
myModule = module;
|
||||
myDeclaredPackages = declaredPackages;
|
||||
myRequiredPackages = requiredPackages;
|
||||
|
||||
myDescriptor = ReadAction.compute(() -> {
|
||||
VirtualFile[] sourceRoots = ModuleRootManager.getInstance(module).getSourceRoots(false);
|
||||
return sourceRoots.length != 0 ? findDescriptor(module, sourceRoots[0]) : null;
|
||||
});
|
||||
myName = myDescriptor != null ? myDescriptor.getName() : uniqueModuleNames.getUniqueName(myModule);
|
||||
}
|
||||
|
||||
ModuleNode(@NotNull PsiJavaModule descriptor) {
|
||||
myModule = ReadAction.compute(() -> ModuleUtilCore.findModuleForPsiElement(descriptor));
|
||||
myDeclaredPackages = Collections.emptySet();
|
||||
myRequiredPackages = Collections.emptySet();
|
||||
myDescriptor = descriptor;
|
||||
myName = myDescriptor.getName();
|
||||
}
|
||||
|
||||
Set<String> getDeclaredPackages() {
|
||||
return myDeclaredPackages;
|
||||
}
|
||||
|
||||
Set<String> getRequiredPackages() {
|
||||
return myRequiredPackages;
|
||||
}
|
||||
|
||||
Set<ModuleNode> getDependencies() {
|
||||
return myDependencies;
|
||||
}
|
||||
|
||||
Set<String> getExports() {
|
||||
return myExports;
|
||||
}
|
||||
|
||||
void addExport(String packageName) {
|
||||
myExports.add(packageName);
|
||||
}
|
||||
|
||||
|
||||
PsiJavaModule getDescriptor() {
|
||||
return myDescriptor;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
String getName() {
|
||||
return myName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return myName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this == o || o instanceof ModuleNode && myName.equals(((ModuleNode)o).myName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return myName.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull ModuleNode o) {
|
||||
int m1 = myModule == null ? 0 : 1, m2 = o.myModule == null ? 0 : 1;
|
||||
if (m1 != m2) return m1 - m2;
|
||||
int j1 = myName.startsWith("java.") || myName.startsWith("javax.") ? 0 : 1;
|
||||
int j2 = o.myName.startsWith("java.") || o.myName.startsWith("javax.") ? 0 : 1;
|
||||
if (j1 != j2) return j1 - j2;
|
||||
return StringUtil.compare(myName, o.myName, false);
|
||||
}
|
||||
|
||||
PsiDirectory getRootDir() {
|
||||
if (myModule == null) return null;
|
||||
return ReadAction.compute(() -> {
|
||||
ModuleRootManager moduleManager = ModuleRootManager.getInstance(myModule);
|
||||
PsiManager psiManager = PsiManager.getInstance(myModule.getProject());
|
||||
return findJavaDirectory(psiManager, moduleManager.getSourceRoots(false));
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PsiDirectory findJavaDirectory(PsiManager psiManager, VirtualFile[] roots) {
|
||||
return StreamEx.of(roots)
|
||||
.sorted(Comparator.comparingInt((VirtualFile vFile) -> "java".equals(vFile.getName()) ? 0 : 1).thenComparing(VirtualFile::getName))
|
||||
.map(psiManager::findDirectory).nonNull().findFirst().orElse(null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PsiJavaModule findDescriptor(@NotNull Module module, VirtualFile root) {
|
||||
return JavaModuleGraphUtil.findDescriptorByFile(root, module.getProject());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInspection.java19api;
|
||||
|
||||
import com.intellij.codeInspection.AbstractDependencyVisitor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
class ModuleVisitor extends AbstractDependencyVisitor {
|
||||
private final Set<String> myRequiredPackages = new HashSet<>();
|
||||
private final Set<String> myDeclaredPackages = new HashSet<>();
|
||||
private final Function<String, String> myCheckPackage;
|
||||
|
||||
ModuleVisitor(@NotNull Function<String, String> checkPackage) {
|
||||
myCheckPackage = checkPackage;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addClassName(String className) {
|
||||
final String packageName = myCheckPackage.apply(className);
|
||||
if (packageName != null) {
|
||||
// todo if transitive
|
||||
myRequiredPackages.add(packageName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(int version,
|
||||
int access,
|
||||
@NotNull String name,
|
||||
@Nullable String signature,
|
||||
@Nullable String superName,
|
||||
String @NotNull [] interfaces) {
|
||||
super.visit(version, access, name, signature, superName, interfaces);
|
||||
|
||||
String packageName = myCheckPackage.apply(getCurrentClassName());
|
||||
if (packageName != null) {
|
||||
myDeclaredPackages.add(packageName);
|
||||
}
|
||||
}
|
||||
|
||||
Set<String> getRequiredPackages() {
|
||||
myRequiredPackages.removeAll(getDeclaredPackages());
|
||||
return myRequiredPackages;
|
||||
}
|
||||
|
||||
Set<String> getDeclaredPackages() {
|
||||
return myDeclaredPackages;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInspection.java19api;
|
||||
|
||||
import com.intellij.lang.java.lexer.JavaLexer;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public final class NameConverterUtil {
|
||||
private static final Pattern NON_NAME = Pattern.compile("[^A-Za-z0-9]");
|
||||
private static final Pattern DOT_SEQUENCE = Pattern.compile("\\.{2,}");
|
||||
|
||||
private NameConverterUtil() {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String convertModuleName(@NotNull String name) { // "public" is for testing
|
||||
// All non-alphanumeric characters ([^A-Za-z0-9]) are replaced with a dot (".") ...
|
||||
name = NON_NAME.matcher(name).replaceAll(".");
|
||||
// ... all repeating dots are replaced with one dot ...
|
||||
name = DOT_SEQUENCE.matcher(name).replaceAll(".");
|
||||
// ... and all leading and trailing dots are removed.
|
||||
name = StringUtil.trimLeading(StringUtil.trimTrailing(name, '.'), '.');
|
||||
|
||||
// sanitize keywords and leading digits
|
||||
String[] parts = name.split("\\.");
|
||||
StringBuilder builder = new StringBuilder();
|
||||
boolean first = true;
|
||||
for (String part : parts) {
|
||||
if (part.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (Character.isJavaIdentifierStart(part.charAt(0))) {
|
||||
if (!first) {
|
||||
builder.append('.');
|
||||
}
|
||||
builder.append(part);
|
||||
if (JavaLexer.isKeyword(part, LanguageLevel.JDK_1_9)) {
|
||||
builder.append('x');
|
||||
}
|
||||
}
|
||||
else { // it's a leading digit
|
||||
if (first) {
|
||||
builder.append("module");
|
||||
}
|
||||
builder.append(part);
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInspection.java19api;
|
||||
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.util.NlsContexts;
|
||||
|
||||
class ProgressTracker {
|
||||
private ProgressIndicator myIndicator;
|
||||
|
||||
private int myCount;
|
||||
private int mySize;
|
||||
private int myPhase;
|
||||
private double myUpToNow;
|
||||
private final double[] myPhases;
|
||||
|
||||
public ProgressTracker(double... phases) {
|
||||
myPhases = phases;
|
||||
}
|
||||
|
||||
public void startPhase(@NlsContexts.ProgressText String text, int size) {
|
||||
myIndicator.setText(text);
|
||||
myCount = 0;
|
||||
mySize = Math.min(size, 1);
|
||||
}
|
||||
|
||||
public void nextPhase() {
|
||||
myUpToNow += myPhases[myPhase++];
|
||||
}
|
||||
|
||||
public void increment() {
|
||||
myIndicator.setFraction(myUpToNow + myPhases[myPhase] * ++myCount / (double)mySize);
|
||||
}
|
||||
|
||||
public void init(ProgressIndicator indicator) {
|
||||
myIndicator = indicator;
|
||||
myIndicator.setFraction(0);
|
||||
myIndicator.setIndeterminate(false);
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
myIndicator = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInspection.java19api;
|
||||
|
||||
import com.intellij.openapi.application.ReadAction;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.PsiJavaModule;
|
||||
import com.intellij.psi.impl.java.stubs.index.JavaModuleNameIndex;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.search.ProjectScope;
|
||||
import com.intellij.util.text.UniqueNameGenerator;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class UniqueModuleNames {
|
||||
private final UniqueNameGenerator myNameGenerator;
|
||||
|
||||
UniqueModuleNames(@NotNull Project project) {
|
||||
final JavaModuleNameIndex index = JavaModuleNameIndex.getInstance();
|
||||
final GlobalSearchScope scope = ProjectScope.getAllScope(project);
|
||||
|
||||
final List<PsiJavaModule> modules = new ArrayList<>();
|
||||
for (String key : index.getAllKeys(project)) {
|
||||
modules.addAll(index.getModules(key, project, scope));
|
||||
}
|
||||
myNameGenerator = new UniqueNameGenerator(modules, module -> ReadAction.compute(() -> module.getName()));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
String getUniqueName(@NotNull Module module) {
|
||||
final String name = NameConverterUtil.convertModuleName(module.getName());
|
||||
return myNameGenerator.generateUniqueName(name);
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,7 @@ package com.intellij.java.codeInsight.actions;
|
||||
|
||||
import com.intellij.JavaTestUtil;
|
||||
import com.intellij.compiler.CompilerManagerImpl;
|
||||
import com.intellij.conversion.ModuleSettings;
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.ide.highlighter.ModuleFileType;
|
||||
import com.intellij.openapi.actionSystem.ActionManager;
|
||||
import com.intellij.openapi.actionSystem.AnAction;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
@@ -14,52 +13,60 @@ import com.intellij.openapi.compiler.CompileScope;
|
||||
import com.intellij.openapi.compiler.CompilerManager;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.module.ModuleManager;
|
||||
import com.intellij.openapi.module.ModuleWithNameAlreadyExists;
|
||||
import com.intellij.openapi.project.DumbService;
|
||||
import com.intellij.openapi.project.DumbServiceImpl;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.project.impl.ProjectImpl;
|
||||
import com.intellij.openapi.projectRoots.Sdk;
|
||||
import com.intellij.openapi.roots.*;
|
||||
import com.intellij.openapi.roots.CompilerModuleExtension;
|
||||
import com.intellij.openapi.roots.CompilerProjectExtension;
|
||||
import com.intellij.openapi.roots.LanguageLevelModuleExtension;
|
||||
import com.intellij.openapi.roots.ModuleRootModificationUtil;
|
||||
import com.intellij.openapi.util.JDOMUtil;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VfsUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.openapi.vfs.VirtualFileManager;
|
||||
import com.intellij.openapi.vfs.pointers.VirtualFilePointer;
|
||||
import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
import com.intellij.refactoring.LightMultiFileTestCase;
|
||||
import com.intellij.testFramework.*;
|
||||
import com.intellij.testFramework.fixtures.DefaultLightProjectDescriptor;
|
||||
import com.intellij.util.LazyInitializer;
|
||||
import kotlinx.coroutines.CoroutineScopeKt;
|
||||
import kotlinx.coroutines.JobKt;
|
||||
import org.jdom.Element;
|
||||
import org.jdom.JDOMException;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.jps.model.java.JavaSourceRootType;
|
||||
import org.jetbrains.jps.model.module.JpsModuleSourceRootType;
|
||||
import org.jetbrains.jps.model.serialization.JDomSerializationUtil;
|
||||
import org.jetbrains.jps.model.serialization.JpsProjectLoader;
|
||||
import org.jetbrains.jps.model.serialization.java.JpsJavaModelSerializerExtension;
|
||||
import org.jetbrains.jps.model.serialization.module.JpsModuleRootModelSerializer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.intellij.conversion.ModuleSettings.MODULE_ROOT_MANAGER_COMPONENT;
|
||||
import static com.intellij.openapi.project.Project.DIRECTORY_STORE_FOLDER;
|
||||
import static org.jetbrains.jps.model.serialization.JpsProjectLoader.*;
|
||||
import static org.jetbrains.jps.model.serialization.java.JpsJavaModelSerializerExtension.*;
|
||||
import static org.jetbrains.jps.model.serialization.module.JpsModuleRootModelSerializer.*;
|
||||
|
||||
public class Java9GenerateModuleDescriptorsActionTest extends LightMultiFileTestCase {
|
||||
private MultiModuleProjectDescriptor myDescriptor = new MultiModuleProjectDescriptor(Paths.get(getTestDataPath() + "/" + getTestName(true)));
|
||||
|
||||
@Override
|
||||
protected @NotNull LightProjectDescriptor getProjectDescriptor() {
|
||||
return new FakeModuleDescriptor(Paths.get(getTestDataPath() + "/" + getTestName(true)));
|
||||
return myDescriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -92,51 +99,102 @@ public class Java9GenerateModuleDescriptorsActionTest extends LightMultiFileTest
|
||||
action.actionPerformed(event);
|
||||
|
||||
// CHECK
|
||||
final FakeModuleDescriptor descriptor = (FakeModuleDescriptor)getProjectDescriptor();
|
||||
final MultiModuleProjectDescriptor descriptor = (MultiModuleProjectDescriptor)getProjectDescriptor();
|
||||
|
||||
PlatformTestUtil.assertDirectoriesEqual(LocalFileSystem.getInstance().findFileByNioFile(descriptor.myAfterPath),
|
||||
LocalFileSystem.getInstance().findFileByPath(getProject().getBasePath()));
|
||||
PlatformTestUtil.assertDirectoriesEqual(LocalFileSystem.getInstance().findFileByNioFile(descriptor.getAfterPath()),
|
||||
LocalFileSystem.getInstance().findFileByNioFile(descriptor.getProjectPath()),
|
||||
file -> "java".equals(file.getExtension()));
|
||||
}
|
||||
|
||||
private static class FakeModuleDescriptor extends DefaultLightProjectDescriptor {
|
||||
private final Path myBeforePath;
|
||||
private final Path myAfterPath;
|
||||
private final Path myProjectPath;
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
try {
|
||||
WriteAction.run(() -> {
|
||||
ModuleManager moduleManager = ModuleManager.getInstance(getProject());
|
||||
for (Module module : moduleManager.getModules()) {
|
||||
if (!module.isDisposed()) moduleManager.disposeModule(module);
|
||||
}
|
||||
((MultiModuleProjectDescriptor)getProjectDescriptor()).cleanup();
|
||||
LightPlatformTestCase.closeAndDeleteProject();
|
||||
});
|
||||
myDescriptor = null;
|
||||
}
|
||||
catch (Throwable e) {
|
||||
addSuppressedException(e);
|
||||
}
|
||||
finally {
|
||||
super.tearDown();
|
||||
}
|
||||
}
|
||||
|
||||
FakeModuleDescriptor(@NotNull Path path) {
|
||||
myBeforePath = path.resolve("before");
|
||||
myAfterPath = path.resolve("after");
|
||||
myProjectPath = TemporaryDirectory.generateTemporaryPath(ProjectImpl.LIGHT_PROJECT_NAME);
|
||||
private static class MultiModuleProjectDescriptor extends DefaultLightProjectDescriptor {
|
||||
private final Path myPath;
|
||||
private final LazyInitializer.LazyValue<ProjectModel> myProjectModel;
|
||||
|
||||
MultiModuleProjectDescriptor(@NotNull Path path) {
|
||||
myPath = path;
|
||||
Path projectPath = TemporaryDirectory.generateTemporaryPath(ProjectImpl.LIGHT_PROJECT_NAME);
|
||||
|
||||
try {
|
||||
FileUtil.copyDir(getBeforePath().toFile(), projectPath.toFile());
|
||||
myProjectModel = LazyInitializer.create(() -> new ProjectModel(projectPath));
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
Path getBeforePath() {
|
||||
return myPath.resolve("before");
|
||||
}
|
||||
|
||||
Path getAfterPath() {
|
||||
return myPath.resolve("after");
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
for (ModuleDescriptor module : myProjectModel.get().getModules()) {
|
||||
try {
|
||||
if (module.src() != null) {
|
||||
for (VirtualFile child : module.src().getChildren()) {
|
||||
child.delete(this);
|
||||
}
|
||||
}
|
||||
if (module.testSrc() != null) {
|
||||
for (VirtualFile child : module.testSrc().getChildren()) {
|
||||
child.delete(this);
|
||||
}
|
||||
}
|
||||
for (VirtualFile child : VirtualFileManager.getInstance().refreshAndFindFileByNioPath(myProjectModel.get().getProjectPath())
|
||||
.getChildren()) {
|
||||
child.delete(this);
|
||||
}
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Path generateProjectPath() {
|
||||
return myProjectModel.get().getProjectPath();
|
||||
}
|
||||
|
||||
public @NotNull Path getProjectPath() {
|
||||
return myProjectPath;
|
||||
return myProjectModel.get().getProjectPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Sdk getSdk() {
|
||||
return IdeaTestUtil.getMockJdk11(); // TODO
|
||||
return myProjectModel.get().getSdk();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUpProject(@NotNull Project project, @NotNull SetupHandler handler) throws Exception {
|
||||
WriteAction.run(() -> {
|
||||
final Path basePath = Paths.get(project.getBasePath());
|
||||
FileUtil.copyDir(myBeforePath.toFile(), basePath.toFile());
|
||||
VfsUtil.markDirtyAndRefresh(false, true, true, basePath.toFile());
|
||||
|
||||
final Element miscXml = JDomSerializationUtil.findComponent(JDOMUtil.load(basePath.resolve(".idea").resolve("misc.xml")),
|
||||
"ProjectRootManager");
|
||||
final String outputUrl = miscXml.getChild("output").getAttributeValue("url").replace("$PROJECT_DIR$", basePath.toString());
|
||||
final CompilerProjectExtension compilerProjectExtension = CompilerProjectExtension.getInstance(project);
|
||||
|
||||
compilerProjectExtension.setCompilerOutputUrl(outputUrl);
|
||||
final VirtualFilePointer pointer = VirtualFilePointerManager.getInstance()
|
||||
.create(outputUrl, (Disposable)compilerProjectExtension, null);
|
||||
compilerProjectExtension.setCompilerOutputPointer(pointer);
|
||||
//CompilerModuleExtension
|
||||
//ModuleElementsEditor
|
||||
VfsUtil.markDirtyAndRefresh(false, true, true, myProjectModel.get().getProjectPath().toFile());
|
||||
// replace services
|
||||
CompilerProjectExtension.getInstance(project).setCompilerOutputUrl(myProjectModel.get().getOutputUrl());
|
||||
ServiceContainerUtil.replaceService(project, DumbService.class,
|
||||
new DumbServiceImpl(project, CoroutineScopeKt.CoroutineScope(JobKt.Job(null))) {
|
||||
@Override
|
||||
@@ -151,105 +209,151 @@ public class Java9GenerateModuleDescriptorsActionTest extends LightMultiFileTest
|
||||
}
|
||||
}, project);
|
||||
|
||||
final Element modulesXml = JDomSerializationUtil.findComponent(JDOMUtil.load(basePath.resolve(".idea").resolve("modules.xml")),
|
||||
JpsProjectLoader.MODULE_MANAGER_COMPONENT);
|
||||
final Element modulesElement = modulesXml.getChild(JpsProjectLoader.MODULES_TAG);
|
||||
final List<Element> moduleElements = modulesElement.getChildren(JpsProjectLoader.MODULE_TAG);
|
||||
for (Element moduleAttr : moduleElements) {
|
||||
Path modulePath = Paths.get(moduleAttr.getAttributeValue(JpsProjectLoader.FILE_PATH_ATTRIBUTE)
|
||||
.replace("$PROJECT_DIR$", basePath.toString()));
|
||||
final ModuleDescriptor descriptor = new ModuleDescriptor(modulePath);
|
||||
final Module module = makeModule(project, descriptor);
|
||||
for (ModuleDescriptor descriptor : myProjectModel.get().getModules()) {
|
||||
Path iml = descriptor.basePath().resolve(descriptor.name() + ModuleFileType.DOT_DEFAULT_EXTENSION);
|
||||
final Module module = Files.exists(iml)
|
||||
? ModuleManager.getInstance(project).loadModule(iml)
|
||||
: createModule(project, iml);
|
||||
handler.moduleCreated(module);
|
||||
final VirtualFile vSrc = VirtualFileManager.getInstance().refreshAndFindFileByNioPath(descriptor.src());
|
||||
handler.sourceRootCreated(vSrc);
|
||||
createContentEntry(module, vSrc);
|
||||
|
||||
ModuleRootModificationUtil.updateModel(module, model -> {
|
||||
model.getModuleExtension(LanguageLevelModuleExtension.class).setLanguageLevel(descriptor.languageLevel());
|
||||
model.setSdk(IdeaTestUtil.getMockJdk(descriptor.languageLevel().toJavaVersion()));
|
||||
if (descriptor.src() != null) {
|
||||
model.addContentEntry(descriptor.src()).addSourceFolder(descriptor.src(), JavaSourceRootType.SOURCE);
|
||||
handler.sourceRootCreated(descriptor.src());
|
||||
}
|
||||
if (descriptor.testSrc() != null) {
|
||||
model.addContentEntry(descriptor.testSrc()).addSourceFolder(descriptor.testSrc(), JavaSourceRootType.TEST_SOURCE);
|
||||
handler.sourceRootCreated(descriptor.testSrc());
|
||||
}
|
||||
|
||||
// maven
|
||||
final Path mavenOutputPath = descriptor.basePath().resolve("target").resolve("classes");
|
||||
if (Files.exists(mavenOutputPath)) {
|
||||
final CompilerModuleExtension compiler = model.getModuleExtension(CompilerModuleExtension.class);
|
||||
compiler.setCompilerOutputPath(mavenOutputPath.toString());
|
||||
compiler.inheritCompilerOutputPath(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Module makeModule(@NotNull Project project, @NotNull ModuleDescriptor descriptor)
|
||||
throws ModuleWithNameAlreadyExists, IOException {
|
||||
final Module module;
|
||||
Path iml = getIml(descriptor.basePath);
|
||||
if (iml != null && Files.exists(iml)) {
|
||||
module = ModuleManager.getInstance(project).loadModule(iml);
|
||||
private static class ProjectModel {
|
||||
private final Path myProjectPath;
|
||||
private String myOutputUrl;
|
||||
private LanguageLevel myLanguageLevel;
|
||||
private Sdk mySdk;
|
||||
private final List<ModuleDescriptor> myModules = new ArrayList<>();
|
||||
|
||||
private ProjectModel(Path path) {
|
||||
try {
|
||||
myProjectPath = path;
|
||||
load();
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Path getProjectPath() {
|
||||
return myProjectPath;
|
||||
}
|
||||
|
||||
private String getOutputUrl() {
|
||||
return myOutputUrl;
|
||||
}
|
||||
|
||||
private LanguageLevel getLanguageLevel() {
|
||||
return myLanguageLevel == null ? LanguageLevel.JDK_11 : myLanguageLevel;
|
||||
}
|
||||
|
||||
private Sdk getSdk() {
|
||||
return mySdk == null ? IdeaTestUtil.getMockJdk11() : mySdk;
|
||||
}
|
||||
|
||||
private List<ModuleDescriptor> getModules() {
|
||||
return myModules;
|
||||
}
|
||||
|
||||
private void load() throws IOException, JDOMException {
|
||||
final Path projectConfigurationPath = myProjectPath.resolve(DIRECTORY_STORE_FOLDER);
|
||||
final Element miscXml = JDomSerializationUtil.findComponent(JDOMUtil.load(projectConfigurationPath.resolve("misc.xml")),
|
||||
"ProjectRootManager");
|
||||
|
||||
myLanguageLevel = parseLanguageLevel(miscXml.getAttributeValue(LANGUAGE_LEVEL_ATTRIBUTE), LanguageLevel.JDK_11);
|
||||
mySdk = IdeaTestUtil.getMockJdk(getLanguageLevel().toJavaVersion());
|
||||
|
||||
myOutputUrl = prepare(miscXml.getChild(OUTPUT_TAG).getAttributeValue(JpsJavaModelSerializerExtension.URL_ATTRIBUTE));
|
||||
|
||||
final Element modulesXml = JDomSerializationUtil.findComponent(JDOMUtil.load(projectConfigurationPath.resolve("modules.xml")),
|
||||
MODULE_MANAGER_COMPONENT);
|
||||
final Element modulesElement = modulesXml.getChild(MODULES_TAG);
|
||||
final List<Element> moduleElements = modulesElement.getChildren(MODULE_TAG);
|
||||
for (Element moduleAttr : moduleElements) {
|
||||
final Path iml = Paths.get(prepare(moduleAttr.getAttributeValue(FILE_PATH_ATTRIBUTE))); // .iml
|
||||
final String moduleName = FileUtil.getNameWithoutExtension(iml.toFile()); // module name
|
||||
|
||||
if (Files.exists(iml)) {
|
||||
final Element component = JDomSerializationUtil.findComponent(JDOMUtil.load(iml), MODULE_ROOT_MANAGER_COMPONENT);
|
||||
final LanguageLevel moduleLanguageLevel =
|
||||
parseLanguageLevel(component.getAttributeValue(MODULE_LANGUAGE_LEVEL_ATTRIBUTE), getLanguageLevel());
|
||||
final Element content = component.getChild(CONTENT_TAG);
|
||||
Map<Boolean, String> sources = Collections.emptyMap();
|
||||
if (content != null) {
|
||||
// true -> testSrc, false -> src
|
||||
sources = content.getChildren(SOURCE_FOLDER_TAG).stream()
|
||||
.collect(Collectors.toMap(src -> Boolean.valueOf(src.getAttributeValue(IS_TEST_SOURCE_ATTRIBUTE)),
|
||||
src -> prepare(src.getAttributeValue(JpsModuleRootModelSerializer.URL_ATTRIBUTE),
|
||||
iml.getParent().toString())));
|
||||
}
|
||||
myModules.add(new ModuleDescriptor(moduleName, iml.getParent(),
|
||||
urlToVirtualFile(sources.get(Boolean.FALSE)),
|
||||
urlToVirtualFile(sources.get(Boolean.TRUE)),
|
||||
moduleLanguageLevel));
|
||||
}
|
||||
else {
|
||||
myModules.add(new ModuleDescriptor(moduleName, iml.getParent(), null, null, getLanguageLevel()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static LanguageLevel parseLanguageLevel(@Nullable String level, LanguageLevel... levels) {
|
||||
LanguageLevel result = null;
|
||||
if (level != null) result = LanguageLevel.valueOf(level);
|
||||
if (result != null) return result;
|
||||
for (LanguageLevel languageLevel : levels) {
|
||||
if (languageLevel != null) return languageLevel;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Contract("null->null")
|
||||
@Nullable
|
||||
private static VirtualFile urlToVirtualFile(@Nullable String url) {
|
||||
if (url == null) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
iml = descriptor.basePath.resolve(descriptor.basePath.getFileName() + ".iml");
|
||||
module = createModule(project, iml);
|
||||
}
|
||||
ModuleRootModificationUtil.updateModel(module, model -> configureModule(module, model, descriptor));
|
||||
return module;
|
||||
}
|
||||
|
||||
private void configureModule(@NotNull Module module, @NotNull ModifiableRootModel model, @NotNull ModuleDescriptor descriptor) {
|
||||
model.getModuleExtension(LanguageLevelModuleExtension.class).setLanguageLevel(descriptor.languageLevel());
|
||||
model.setSdk(IdeaTestUtil.getMockJdk(descriptor.languageLevel().toJavaVersion()));
|
||||
final BiConsumer<Path, JpsModuleSourceRootType<?>> register = (path, type) -> {
|
||||
if (path == null) return;
|
||||
final VirtualFile src = Files.exists(path)
|
||||
? VirtualFileManager.getInstance().refreshAndFindFileByNioPath(path)
|
||||
: createSourceRoot(module, path.toString());
|
||||
registerSourceRoot(module.getProject(), src);
|
||||
model.addContentEntry(src).addSourceFolder(src, type);
|
||||
};
|
||||
register.accept(descriptor.src(), JavaSourceRootType.SOURCE);
|
||||
register.accept(descriptor.testSrc(), JavaSourceRootType.TEST_SOURCE);
|
||||
//JavaResourceRootType.RESOURCE
|
||||
//JavaResourceRootType.TEST_RESOURCE
|
||||
|
||||
// Maven
|
||||
final Path mavenOutputPath = Paths.get(module.getModuleFilePath()).getParent().resolve("target").resolve("classes");
|
||||
if (Files.exists(mavenOutputPath)) {
|
||||
final CompilerModuleExtension compiler = model.getModuleExtension(CompilerModuleExtension.class);
|
||||
compiler.setCompilerOutputPath(mavenOutputPath.toString());
|
||||
compiler.inheritCompilerOutputPath(false);
|
||||
return VirtualFileManager.getInstance().refreshAndFindFileByUrl(url);
|
||||
}
|
||||
}
|
||||
|
||||
private record ModuleDescriptor(@NotNull String name, @NotNull Path basePath, @NotNull Path src, @Nullable Path testSrc,
|
||||
@NotNull LanguageLevel languageLevel) {
|
||||
@SuppressWarnings("SwitchStatementWithTooFewBranches")
|
||||
private ModuleDescriptor(@NotNull Path iml) throws IOException, JDOMException {
|
||||
this(iml.getFileName().toString().replace(".iml", ""), iml.getParent(),
|
||||
Paths.get(new URL(getData(iml, List.of(CONTENT_TAG, SOURCE_FOLDER_TAG), e -> switch (e.getName()) {
|
||||
case SOURCE_FOLDER_TAG -> e.getAttributeValue(IS_TEST_SOURCE_ATTRIBUTE).equals("false");
|
||||
default -> true;
|
||||
}).stream().findFirst().orElseThrow().getAttributeValue(URL_ATTRIBUTE)
|
||||
.replace("$MODULE_DIR$", iml.getParent().toString())).getPath()), null, LanguageLevel.JDK_11);
|
||||
}
|
||||
@NotNull
|
||||
private String prepare(@NotNull String path) {
|
||||
return path.replace("$PROJECT_DIR$", myProjectPath.toString());
|
||||
}
|
||||
|
||||
private static List<Element> getData(@NotNull Path iml, List<String> tags, @NotNull Predicate<Element> condition)
|
||||
throws IOException, JDOMException {
|
||||
final Element component = JDomSerializationUtil.findComponent(JDOMUtil.load(iml), ModuleSettings.MODULE_ROOT_MANAGER_COMPONENT);
|
||||
List<Element> elements = List.of(component);
|
||||
for (String tag : tags) {
|
||||
List<Element> newElements = new ArrayList<>();
|
||||
for (Element element : elements) {
|
||||
newElements.addAll(element.getChildren(tag).stream().filter(condition).toList());
|
||||
}
|
||||
elements = newElements;
|
||||
}
|
||||
return elements;
|
||||
}
|
||||
@NotNull
|
||||
private String prepare(@NotNull String path, @NotNull String moduleDir) {
|
||||
return prepare(path).replace("$MODULE_DIR$", moduleDir);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Path getIml(@NotNull Path path) {
|
||||
return findFiles(path, "glob:**/*.iml").stream().findFirst().orElse(null);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<Path> findFiles(@NotNull Path path, @NotNull String mask) {
|
||||
final PathMatcher matcher = FileSystems.getDefault().getPathMatcher(mask);
|
||||
try (final Stream<Path> stream = Files.walk(path)) {
|
||||
return stream.filter(matcher::matches).sorted().toList();
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
private record ModuleDescriptor(@NotNull String name, @NotNull Path basePath, @Nullable VirtualFile src, @Nullable VirtualFile testSrc,
|
||||
@NotNull LanguageLevel languageLevel) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.java.codeInspection.java19api
|
||||
|
||||
import com.intellij.codeInspection.java19api.Java9GenerateModuleDescriptorsAction
|
||||
import com.intellij.codeInspection.java19api.NameConverterUtil
|
||||
import junit.framework.TestCase
|
||||
|
||||
class Java9GenerateModuleDescriptorsTest : TestCase() {
|
||||
@@ -25,6 +25,6 @@ class Java9GenerateModuleDescriptorsTest : TestCase() {
|
||||
}
|
||||
|
||||
private fun doTestName(expected:String, name:String) {
|
||||
assertEquals(expected, Java9GenerateModuleDescriptorsAction.NameConverter.convertModuleName(name))
|
||||
assertEquals(expected, NameConverterUtil.convertModuleName(name))
|
||||
}
|
||||
}
|
||||
@@ -277,6 +277,7 @@ generate.module.descriptors.build.required.message=Couldn't generate module desc
|
||||
generate.module.descriptors.collecting.message=Collecting Dependencies
|
||||
generate.module.descriptors.command.title=Generate module-info Descriptors
|
||||
generate.module.descriptors.no.suitable.modules.message=Found no modules which may contain module-info
|
||||
generate.module.descriptors.io.exceptions.message=Problem with reading a module {0} files
|
||||
generate.module.descriptors.preparing.message=Preparing Code
|
||||
generate.module.descriptors.rebuild.message=The project needs to be built for better accuracy of dependencies calculation. \n\
|
||||
Start the build before generating module-info descriptors?
|
||||
|
||||
@@ -170,7 +170,7 @@ public abstract class LightPlatformTestCase extends UsefulTestCase implements Da
|
||||
}
|
||||
ApplicationManager.getApplication().runWriteAction(() -> cleanPersistedVFSContent());
|
||||
|
||||
Path tempDirectory = descriptor.getProjectPath();
|
||||
Path tempDirectory = descriptor.generateProjectPath();
|
||||
ourProject = Objects.requireNonNull(ProjectManagerEx.getInstanceEx().newProject(tempDirectory, descriptor.getOpenProjectOptions()));
|
||||
HeavyPlatformTestCase.synchronizeTempDirVfs(tempDirectory);
|
||||
ourPsiManager = null;
|
||||
|
||||
@@ -37,11 +37,6 @@ public class LightProjectDescriptor {
|
||||
public static final LightProjectDescriptor EMPTY_PROJECT_DESCRIPTOR = new LightProjectDescriptor();
|
||||
|
||||
public static final String TEST_MODULE_NAME = "light_idea_test_case";
|
||||
private final Path myProjectPath;
|
||||
|
||||
public LightProjectDescriptor() {
|
||||
myProjectPath = TemporaryDirectory.generateTemporaryPath(ProjectImpl.LIGHT_PROJECT_NAME + ProjectFileType.DOT_DEFAULT_EXTENSION);
|
||||
}
|
||||
|
||||
public void setUpProject(@NotNull Project project, @NotNull SetupHandler handler) throws Exception {
|
||||
WriteAction.run(() -> {
|
||||
@@ -184,8 +179,8 @@ public class LightProjectDescriptor {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Path getProjectPath() {
|
||||
return myProjectPath;
|
||||
public Path generateProjectPath() {
|
||||
return TemporaryDirectory.generateTemporaryPath(ProjectImpl.LIGHT_PROJECT_NAME + ProjectFileType.DOT_DEFAULT_EXTENSION);
|
||||
}
|
||||
|
||||
protected void configureModule(@NotNull Module module, @NotNull ModifiableRootModel model, @NotNull ContentEntry contentEntry) { }
|
||||
|
||||
Reference in New Issue
Block a user