[java, action] refactoring "Generate module-info descriptors" IDEA-187523

GitOrigin-RevId: f4a1450f82cc684c67838172ec4d1a28bb7b7e6d
This commit is contained in:
Aleksey Dobrynin
2023-12-13 17:05:17 +01:00
committed by intellij-monorepo-bot
parent 7d7ba2c4b3
commit 7ac5997e0c
13 changed files with 928 additions and 761 deletions

View File

@@ -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) {
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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) {
}
}

View File

@@ -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))
}
}

View File

@@ -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?

View File

@@ -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;

View File

@@ -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) { }