i18n: templates (fix more clients) (IDEA-249136)
[idea/community.git] / java / idea-ui / src / com / intellij / ide / util / projectWizard / importSources / impl / ProjectFromSourcesBuilderImpl.java
1 // Copyright 2000-2020 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.
2 package com.intellij.ide.util.projectWizard.importSources.impl;
3
4 import com.intellij.icons.AllIcons;
5 import com.intellij.ide.IdeBundle;
6 import com.intellij.ide.JavaUiBundle;
7 import com.intellij.ide.util.importProject.LibraryDescriptor;
8 import com.intellij.ide.util.importProject.ModuleDescriptor;
9 import com.intellij.ide.util.importProject.ModuleInsight;
10 import com.intellij.ide.util.importProject.ProjectDescriptor;
11 import com.intellij.ide.util.projectWizard.ExistingModuleLoader;
12 import com.intellij.ide.util.projectWizard.ModuleBuilder;
13 import com.intellij.ide.util.projectWizard.WizardContext;
14 import com.intellij.ide.util.projectWizard.importSources.*;
15 import com.intellij.openapi.application.WriteAction;
16 import com.intellij.openapi.diagnostic.Logger;
17 import com.intellij.openapi.module.Module;
18 import com.intellij.openapi.module.*;
19 import com.intellij.openapi.project.Project;
20 import com.intellij.openapi.projectRoots.SdkTypeId;
21 import com.intellij.openapi.roots.*;
22 import com.intellij.openapi.roots.libraries.Library;
23 import com.intellij.openapi.roots.libraries.LibraryTable;
24 import com.intellij.openapi.roots.ui.configuration.DefaultModulesProvider;
25 import com.intellij.openapi.roots.ui.configuration.ModulesProvider;
26 import com.intellij.openapi.roots.ui.configuration.projectRoot.LibrariesContainer;
27 import com.intellij.openapi.roots.ui.configuration.projectRoot.LibrariesContainerFactory;
28 import com.intellij.openapi.ui.Messages;
29 import com.intellij.openapi.util.InvalidDataException;
30 import com.intellij.openapi.util.JDOMUtil;
31 import com.intellij.openapi.util.io.FileUtil;
32 import com.intellij.openapi.vfs.LocalFileSystem;
33 import com.intellij.openapi.vfs.VfsUtil;
34 import com.intellij.openapi.vfs.VfsUtilCore;
35 import com.intellij.openapi.vfs.VirtualFile;
36 import com.intellij.packaging.artifacts.ModifiableArtifactModel;
37 import com.intellij.projectImport.ProjectImportBuilder;
38 import com.intellij.util.ArrayUtil;
39 import com.intellij.util.containers.MultiMap;
40 import org.jdom.Element;
41 import org.jdom.JDOMException;
42 import org.jetbrains.annotations.NotNull;
43 import org.jetbrains.annotations.Nullable;
44
45 import javax.swing.*;
46 import java.io.File;
47 import java.io.IOException;
48 import java.util.*;
49
50 /**
51  * @author Eugene Zhuravlev
52  */
53 public final class ProjectFromSourcesBuilderImpl extends ProjectImportBuilder implements ProjectFromSourcesBuilder {
54   private static final Logger LOG = Logger.getInstance(ProjectFromSourcesBuilderImpl.class);
55   private String myBaseProjectPath;
56   private final List<ProjectConfigurationUpdater> myUpdaters = new ArrayList<>();
57   private final Map<ProjectStructureDetector, ProjectDescriptor> myProjectDescriptors = new LinkedHashMap<>();
58   private MultiMap<ProjectStructureDetector, DetectedProjectRoot> myRoots = MultiMap.empty();
59   private final WizardContext myContext;
60   private final ModulesProvider myModulesProvider;
61   private Set<String> myModuleNames;
62   private Set<String> myProjectLibrariesNames;
63
64   public ProjectFromSourcesBuilderImpl(WizardContext context, ModulesProvider modulesProvider) {
65     myContext = context;
66     myModulesProvider = modulesProvider;
67     for (ProjectStructureDetector detector : ProjectStructureDetector.EP_NAME.getExtensions()) {
68       myProjectDescriptors.put(detector, new ProjectDescriptor());
69     }
70   }
71
72   @NotNull
73   @Override
74   public Set<String> getExistingModuleNames() {
75     if (myModuleNames == null) {
76       myModuleNames = new HashSet<>();
77       for (Module module : myModulesProvider.getModules()) {
78         myModuleNames.add(module.getName());
79       }
80     }
81     return myModuleNames;
82   }
83
84   @NotNull
85   @Override
86   public Set<String> getExistingProjectLibraryNames() {
87     if (myProjectLibrariesNames == null) {
88       myProjectLibrariesNames = new HashSet<>();
89       final LibrariesContainer container = LibrariesContainerFactory.createContainer(myContext, myModulesProvider);
90       for (Library library : container.getLibraries(LibrariesContainer.LibraryLevel.PROJECT)) {
91         myProjectLibrariesNames.add(library.getName());
92       }
93     }
94     return myProjectLibrariesNames;
95   }
96
97   @NotNull
98   @Override
99   public WizardContext getContext() {
100     return myContext;
101   }
102
103   public void setBaseProjectPath(final String contentRootPath) {
104     myBaseProjectPath = contentRootPath;
105   }
106
107   @Override
108   public String getBaseProjectPath() {
109     return myBaseProjectPath;
110   }
111
112   public void setupProjectStructure(MultiMap<ProjectStructureDetector, DetectedProjectRoot> roots) {
113     myRoots = roots;
114     for (ProjectStructureDetector detector : roots.keySet()) {
115       detector.setupProjectStructure(roots.get(detector), getProjectDescriptor(detector), this);
116     }
117   }
118
119   @NotNull
120   @Override
121   public Collection<DetectedProjectRoot> getProjectRoots(@NotNull ProjectStructureDetector detector) {
122     return myRoots.get(detector);
123   }
124
125   @NotNull
126   @Override
127   public String getName() {
128     return JavaUiBundle.message("existing.sources");
129   }
130
131   @Override
132   public Icon getIcon() {
133     return AllIcons.Nodes.Folder;
134   }
135
136   @Override
137   public boolean isMarked(Object element) {
138     return false;
139   }
140
141   @Override
142   public void setOpenProjectSettingsAfter(boolean on) {
143   }
144
145   @Override
146   public void setFileToImport(@NotNull String path) {
147     setBaseProjectPath(path);
148   }
149
150   @Override
151   public List<Module> commit(@NotNull Project project, ModifiableModuleModel model, ModulesProvider modulesProvider) {
152     boolean fromProjectStructure = model != null;
153     ModifiableModelsProvider modelsProvider = new IdeaModifiableModelsProvider();
154     LibraryTable.ModifiableModel projectLibraryTable = modelsProvider.getLibraryTableModifiableModel(project);
155     Map<LibraryDescriptor, Library> projectLibs = new HashMap<>();
156     List<Module> result = new ArrayList<>();
157     try {
158       WriteAction.run(() -> {
159         // create project-level libraries
160         for (ProjectDescriptor projectDescriptor : getSelectedDescriptors()) {
161           for (LibraryDescriptor lib : projectDescriptor.getLibraries()) {
162             final Collection<File> files = lib.getJars();
163             final Library projectLib = projectLibraryTable.createLibrary(lib.getName());
164             final Library.ModifiableModel libraryModel = projectLib.getModifiableModel();
165             for (File file : files) {
166               libraryModel.addRoot(VfsUtil.getUrlForLibraryRoot(file), OrderRootType.CLASSES);
167             }
168             libraryModel.commit();
169             projectLibs.put(lib, projectLib);
170           }
171         }
172         if (!fromProjectStructure) {
173           projectLibraryTable.commit();
174         }
175       });
176     }
177     catch (Exception e) {
178       LOG.info(e);
179       Messages.showErrorDialog(IdeBundle.message("error.adding.module.to.project", e.getMessage()), IdeBundle.message("title.add.module"));
180     }
181
182     final Map<ModuleDescriptor, Module> descriptorToModuleMap = new HashMap<>();
183
184     ModulesProvider updatedModulesProvider = fromProjectStructure ? modulesProvider : new DefaultModulesProvider(project);
185     try {
186       WriteAction.run(() -> {
187         final ModifiableModuleModel moduleModel = fromProjectStructure ? model : ModuleManager.getInstance(project).getModifiableModel();
188         Map<String, Module> contentRootToModule = new HashMap<>();
189         for (Module module : moduleModel.getModules()) {
190           for (String url : updatedModulesProvider.getRootModel(module).getContentRootUrls()) {
191             contentRootToModule.put(url, module);
192           }
193         }
194         for (ProjectDescriptor descriptor : getSelectedDescriptors()) {
195           for (final ModuleDescriptor moduleDescriptor : descriptor.getModules()) {
196             for (File contentRoot : moduleDescriptor.getContentRoots()) {
197               String url = VfsUtilCore.fileToUrl(contentRoot);
198               Module existingModule = contentRootToModule.get(url);
199               if (existingModule != null && ArrayUtil.contains(existingModule, moduleModel.getModules())) {
200                 moduleModel.disposeModule(existingModule);
201               }
202             }
203             final Module module;
204             if (moduleDescriptor.isReuseExistingElement()) {
205               final ExistingModuleLoader moduleLoader =
206                 ExistingModuleLoader.setUpLoader(FileUtil.toSystemIndependentName(moduleDescriptor.computeModuleFilePath()));
207               module = moduleLoader.createModule(moduleModel);
208             }
209             else {
210               module = createModule(descriptor, moduleDescriptor, projectLibs, moduleModel);
211             }
212             result.add(module);
213             descriptorToModuleMap.put(moduleDescriptor, module);
214           }
215         }
216
217         if (!fromProjectStructure) {
218           moduleModel.commit();
219         }
220       });
221     }
222     catch (Exception e) {
223       LOG.info(e);
224       Messages.showErrorDialog(IdeBundle.message("error.adding.module.to.project", e.getMessage()), IdeBundle.message("title.add.module"));
225     }
226
227     // setup dependencies between modules
228     try {
229       WriteAction.run(() -> {
230         for (ProjectDescriptor data : getSelectedDescriptors()) {
231           for (final ModuleDescriptor descriptor : data.getModules()) {
232             final Module module = descriptorToModuleMap.get(descriptor);
233             if (module == null) {
234               continue;
235             }
236             final Set<ModuleDescriptor> deps = descriptor.getDependencies();
237             if (deps.size() == 0) {
238               continue;
239             }
240             final ModifiableRootModel rootModel = ModuleRootManager.getInstance(module).getModifiableModel();
241             for (ModuleDescriptor dependentDescriptor : deps) {
242               final Module dependentModule = descriptorToModuleMap.get(dependentDescriptor);
243               if (dependentModule != null) {
244                 rootModel.addModuleOrderEntry(dependentModule);
245               }
246             }
247             rootModel.commit();
248           }
249         }
250       });
251     }
252     catch (Exception e) {
253       LOG.info(e);
254       Messages.showErrorDialog(IdeBundle.message("error.adding.module.to.project", e.getMessage()), IdeBundle.message("title.add.module"));
255     }
256
257     WriteAction.run(() -> {
258       for (ProjectConfigurationUpdater updater : myUpdaters) {
259         updater.updateProject(project, modelsProvider, updatedModulesProvider);
260       }
261     });
262     return result;
263   }
264
265   @Override
266   public @NotNull List<Module> commit(Project project,
267                                       ModifiableModuleModel model,
268                                       ModulesProvider modulesProvider,
269                                       ModifiableArtifactModel artifactModel) {
270     return commit(project, model, modulesProvider);
271   }
272
273   public Collection<ProjectDescriptor> getSelectedDescriptors() {
274     return myProjectDescriptors.values();
275   }
276
277   public void addConfigurationUpdater(ProjectConfigurationUpdater updater) {
278     myUpdaters.add(updater);
279   }
280
281   @Override
282   public boolean hasRootsFromOtherDetectors(ProjectStructureDetector thisDetector) {
283     for (ProjectStructureDetector projectStructureDetector : ProjectStructureDetector.EP_NAME.getExtensionList()) {
284       if (projectStructureDetector != thisDetector && !getProjectRoots(projectStructureDetector).isEmpty()) {
285         return true;
286       }
287     }
288     return false;
289   }
290
291   @Override
292   public void setupModulesByContentRoots(ProjectDescriptor projectDescriptor, Collection<? extends DetectedProjectRoot> roots) {
293     if (projectDescriptor.getModules().isEmpty()) {
294       List<ModuleDescriptor> modules = new ArrayList<>();
295       for (DetectedProjectRoot root : roots) {
296         if (root instanceof DetectedContentRoot) {
297           modules.add(new ModuleDescriptor(root.getDirectory(), ((DetectedContentRoot)root).getModuleType(), Collections.emptyList()));
298         }
299       }
300       projectDescriptor.setModules(modules);
301     }
302   }
303
304   @NotNull
305   private static Module createModule(ProjectDescriptor projectDescriptor, final ModuleDescriptor descriptor,
306                                      final Map<LibraryDescriptor, Library> projectLibs, final ModifiableModuleModel moduleModel)
307     throws InvalidDataException {
308
309     final String moduleFilePath = descriptor.computeModuleFilePath();
310     ModuleBuilder.deleteModuleFile(moduleFilePath);
311
312     final Module module = moduleModel.newModule(moduleFilePath, descriptor.getModuleType().getId());
313     final ModifiableRootModel modifiableModel = ModuleRootManager.getInstance(module).getModifiableModel();
314     setupRootModel(projectDescriptor, descriptor, modifiableModel, projectLibs);
315     descriptor.updateModuleConfiguration(module, modifiableModel);
316     modifiableModel.commit();
317     return module;
318   }
319
320   private static void setupRootModel(ProjectDescriptor projectDescriptor, final ModuleDescriptor descriptor,
321                                      final ModifiableRootModel rootModel, final Map<LibraryDescriptor, Library> projectLibs) {
322     final CompilerModuleExtension compilerModuleExtension = rootModel.getModuleExtension(CompilerModuleExtension.class);
323     compilerModuleExtension.setExcludeOutput(true);
324     rootModel.inheritSdk();
325
326     final Set<File> contentRoots = descriptor.getContentRoots();
327     for (File contentRoot : contentRoots) {
328       final LocalFileSystem lfs = LocalFileSystem.getInstance();
329       VirtualFile moduleContentRoot = lfs.refreshAndFindFileByPath(FileUtil.toSystemIndependentName(contentRoot.getPath()));
330       if (moduleContentRoot != null) {
331         final ContentEntry contentEntry = rootModel.addContentEntry(moduleContentRoot);
332         final Collection<DetectedSourceRoot> sourceRoots = descriptor.getSourceRoots(contentRoot);
333         for (DetectedSourceRoot srcRoot : sourceRoots) {
334           final String srcpath = FileUtil.toSystemIndependentName(srcRoot.getDirectory().getPath());
335           final VirtualFile sourceRoot = lfs.refreshAndFindFileByPath(srcpath);
336           if (sourceRoot != null) {
337             contentEntry.addSourceFolder(sourceRoot, shouldBeTestRoot(srcRoot.getDirectory()), getPackagePrefix(srcRoot));
338           }
339         }
340       }
341     }
342     compilerModuleExtension.inheritCompilerOutputPath(true);
343     final LibraryTable moduleLibraryTable = rootModel.getModuleLibraryTable();
344     for (LibraryDescriptor libDescriptor : ModuleInsight.getLibraryDependencies(descriptor, projectDescriptor.getLibraries())) {
345       final Library projectLib = projectLibs.get(libDescriptor);
346       if (projectLib != null) {
347         rootModel.addLibraryEntry(projectLib);
348       }
349       else {
350         // add as module library
351         final Collection<File> jars = libDescriptor.getJars();
352         for (File file : jars) {
353           Library library = moduleLibraryTable.createLibrary();
354           Library.ModifiableModel modifiableModel = library.getModifiableModel();
355           modifiableModel.addRoot(VfsUtil.getUrlForLibraryRoot(file), OrderRootType.CLASSES);
356           modifiableModel.commit();
357         }
358       }
359     }
360   }
361
362   public static String getPackagePrefix(final DetectedSourceRoot srcRoot) {
363     return srcRoot.getPackagePrefix();
364   }
365
366   @NotNull
367   @Override
368   public ProjectDescriptor getProjectDescriptor(@NotNull ProjectStructureDetector detector) {
369     return myProjectDescriptors.get(detector);
370   }
371
372   private static boolean shouldBeTestRoot(final File srcRoot) {
373     if (isTestRootName(srcRoot.getName())) {
374       return true;
375     }
376     final File parentFile = srcRoot.getParentFile();
377     return parentFile != null && isTestRootName(parentFile.getName());
378   }
379
380   private static boolean isTestRootName(final String name) {
381     return "test".equalsIgnoreCase(name) ||
382            "tests".equalsIgnoreCase(name) ||
383            "testSource".equalsIgnoreCase(name) ||
384            "testSources".equalsIgnoreCase(name) ||
385            "testSrc".equalsIgnoreCase(name) ||
386            "tst".equalsIgnoreCase(name);
387   }
388
389   public interface ProjectConfigurationUpdater {
390     void updateProject(@NotNull Project project, @NotNull ModifiableModelsProvider modelsProvider, @NotNull ModulesProvider modulesProvider);
391   }
392
393   @Override
394   public boolean isSuitableSdkType(final SdkTypeId sdkTypeId) {
395     for (ProjectDescriptor projectDescriptor : getSelectedDescriptors()) {
396       for (ModuleDescriptor moduleDescriptor : projectDescriptor.getModules()) {
397         try {
398           final ModuleType<?> moduleType = getModuleType(moduleDescriptor);
399           if (moduleType != null && !moduleType.createModuleBuilder().isSuitableSdkType(sdkTypeId)) {
400             return false;
401           }
402         }
403         catch (Exception ignore) {
404         }
405       }
406     }
407     return true;
408   }
409
410   @Nullable
411   private static ModuleType<?> getModuleType(ModuleDescriptor moduleDescriptor) throws InvalidDataException, JDOMException, IOException {
412     if (moduleDescriptor.isReuseExistingElement()) {
413       final File file = new File(moduleDescriptor.computeModuleFilePath());
414       if (file.exists()) {
415         final Element rootElement = JDOMUtil.load(file);
416         final String type = rootElement.getAttributeValue("type");
417         if (type != null) {
418           return ModuleTypeManager.getInstance().findByID(type);
419         }
420       }
421       return null;
422     }
423     else {
424       return moduleDescriptor.getModuleType();
425     }
426   }
427 }