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