[#15434] assert: ApplicationImpl.runWriteAction (IDEADEV-39026)
[fedora-idea.git] / plugins / maven / src / main / java / org / jetbrains / idea / maven / importing / MavenProjectImporter.java
blobd5d7849660dbed9920ce811e8c0850927cbd5032
1 package org.jetbrains.idea.maven.importing;
3 import com.intellij.compiler.impl.javaCompiler.javac.JavacSettings;
4 import com.intellij.openapi.application.ApplicationManager;
5 import com.intellij.openapi.application.ModalityState;
6 import com.intellij.openapi.application.Result;
7 import com.intellij.openapi.application.WriteAction;
8 import com.intellij.openapi.module.JavaModuleType;
9 import com.intellij.openapi.module.ModifiableModuleModel;
10 import com.intellij.openapi.module.Module;
11 import com.intellij.openapi.module.StdModuleTypes;
12 import com.intellij.openapi.project.Project;
13 import com.intellij.openapi.project.ex.ProjectEx;
14 import com.intellij.openapi.roots.LibraryOrderEntry;
15 import com.intellij.openapi.roots.ModifiableRootModel;
16 import com.intellij.openapi.roots.ModuleRootModel;
17 import com.intellij.openapi.roots.OrderEntry;
18 import com.intellij.openapi.roots.libraries.Library;
19 import com.intellij.openapi.ui.Messages;
20 import com.intellij.openapi.util.text.StringUtil;
21 import com.intellij.openapi.vfs.LocalFileSystem;
22 import com.intellij.openapi.vfs.VirtualFile;
23 import com.intellij.util.Function;
24 import com.intellij.util.containers.Stack;
25 import gnu.trove.THashMap;
26 import gnu.trove.THashSet;
27 import org.jetbrains.idea.maven.embedder.MavenConsole;
28 import org.jetbrains.idea.maven.project.*;
29 import org.jetbrains.idea.maven.utils.MavenLog;
30 import org.jetbrains.idea.maven.utils.MavenProcessCanceledException;
31 import org.jetbrains.idea.maven.utils.MavenProgressIndicator;
32 import org.jetbrains.idea.maven.utils.MavenUtil;
34 import java.io.File;
35 import java.io.IOException;
36 import java.util.*;
37 import java.util.regex.Matcher;
38 import java.util.regex.Pattern;
40 public class MavenProjectImporter {
41 private final Project myProject;
42 private final MavenProjectsTree myProjectsTree;
43 private final Map<VirtualFile, Module> myFileToModuleMapping;
44 private final Set<MavenProject> myProjectsToImport;
45 private final MavenModifiableModelsProvider myModelsProvider;
46 private final MavenImportingSettings myImportingSettings;
48 private final ModifiableModuleModel myModuleModel;
50 private final List<Module> myCreatedModules = new ArrayList<Module>();
52 private final Map<MavenProject, Module> myMavenProjectToModule = new THashMap<MavenProject, Module>();
53 private final Map<MavenProject, String> myMavenProjectToModuleName = new THashMap<MavenProject, String>();
54 private final Map<MavenProject, String> myMavenProjectToModulePath = new THashMap<MavenProject, String>();
56 public MavenProjectImporter(Project p,
57 MavenProjectsTree projectsTree,
58 Map<VirtualFile, Module> fileToModuleMapping,
59 Set<MavenProject> projectsToImport,
60 final MavenModifiableModelsProvider modelsProvider,
61 MavenImportingSettings importingSettings) {
62 myProject = p;
63 myProjectsTree = projectsTree;
64 myFileToModuleMapping = fileToModuleMapping;
65 myModelsProvider = modelsProvider;
66 myImportingSettings = importingSettings;
68 myModuleModel = modelsProvider.getModuleModel();
69 myProjectsToImport = collectProjectsToImport(projectsToImport);
72 private Set<MavenProject> collectProjectsToImport(Set<MavenProject> projectsToImport) {
73 Set<MavenProject> result = new THashSet<MavenProject>(projectsToImport);
74 result.addAll(collectNewlyCreatedProjects());
75 return selectProjectsToImport(result);
78 private Set<MavenProject> collectNewlyCreatedProjects() {
79 Set<MavenProject> result = new THashSet<MavenProject>();
81 final Map<MavenProject, Module> projectsWithInconsistentModuleType = new HashMap<MavenProject, Module>();
83 for (MavenProject each : myProjectsTree.getProjects()) {
84 Module module = myFileToModuleMapping.get(each.getFile());
85 if (module == null) {
86 result.add(each);
87 } else {
88 if (shouldCreateModuleFor(each) && !(module.getModuleType() instanceof JavaModuleType)) {
89 projectsWithInconsistentModuleType.put(each, module);
94 removeModulesOrIgnoreMavenProjects(projectsWithInconsistentModuleType);
95 for (final MavenProject mavenProject : projectsWithInconsistentModuleType.keySet()) {
96 if (!myProjectsTree.isIgnored(mavenProject)) {
97 result.add(mavenProject);
101 return result;
104 private void removeModulesOrIgnoreMavenProjects(final Map<MavenProject, Module> projectsWithInconsistentModuleType) {
105 if (projectsWithInconsistentModuleType.isEmpty()) {
106 return;
109 final int[] result = new int[1];
110 MavenUtil.invokeAndWait(myProject, ModalityState.NON_MODAL, new Runnable() {
111 public void run() {
112 result[0] = Messages.showDialog(myProject, ProjectBundle.message("maven.import.message.remove.modules",
113 formatModules(projectsWithInconsistentModuleType.values()),
114 ProjectBundle.message("maven.continue.button"),
115 ProjectBundle.message("maven.skip.button"),
116 formatProjects(projectsWithInconsistentModuleType.keySet())),
117 ProjectBundle.message("maven.tab.importing"),
118 new String[]{ProjectBundle.message("maven.continue.button"),
119 ProjectBundle.message("maven.skip.button")}, 0, Messages.getQuestionIcon());
122 if (result[0] == 0) {
123 for (Map.Entry<MavenProject, Module> entry : projectsWithInconsistentModuleType.entrySet()) {
124 myFileToModuleMapping.remove(entry.getKey().getFile());
125 final Module module = entry.getValue();
126 if (!module.isDisposed()) {
127 myModuleModel.disposeModule(module);
131 else {
132 myProjectsTree.setIgnoredStateDoNotFireEvent(projectsWithInconsistentModuleType.keySet(), true);
136 private static String formatModules(final Collection<Module> modules) {
137 return StringUtil.join(modules, new Function<Module, String>() {
138 public String fun(final Module m) {
139 return "'" + m.getName() + "'";
141 }, "\n");
144 private static String formatProjects(final Collection<MavenProject> projects) {
145 return StringUtil.join(projects, new Function<MavenProject, String>() {
146 public String fun(final MavenProject project) {
147 return "'" + project.getName() + "'";
149 }, "\n");
152 private Set<MavenProject> selectProjectsToImport(Collection<MavenProject> originalProjects) {
153 Set<MavenProject> result = new THashSet<MavenProject>();
154 for (MavenProject each : originalProjects) {
155 if (!shouldCreateModuleFor(each)) continue;
156 result.add(each);
158 return result;
161 private boolean shouldCreateModuleFor(MavenProject project) {
162 if (myProjectsTree.isIgnored(project)) return false;
163 return !project.isAggregator() || myImportingSettings.isCreateModulesForAggregators();
166 public List<MavenProjectsProcessorTask> importProject() {
167 final List<MavenProjectsProcessorTask> postTasks = new ArrayList<MavenProjectsProcessorTask>();
169 mapModulesToMavenProjects();
170 importModules(postTasks);
171 configModuleGroups();
172 scheduleRefreshResolvedArtifacts(postTasks);
173 configSettings();
174 deleteObsoleteModules();
175 removeUnusedProjectLibraries();
176 myModelsProvider.commit();
178 return postTasks;
181 private void scheduleRefreshResolvedArtifacts(List<MavenProjectsProcessorTask> postTasks) {
182 // We have to refresh all the resolved artifacts manually in order to
183 // update all the VirtualFilePointers. It is not enough to call
184 // VirtualFileManager.refresh() since the newly created files will be only
185 // picked by FS when FileWathcer finishes its work. And in the case of import
186 // it doesn't finish in time.
187 // I couldn't manage to write a test for this since behaviour of VirtualFileManager
188 // and FileWatcher differs from real-life execution.
190 List<MavenArtifact> artifacts = new ArrayList<MavenArtifact>();
191 for (MavenProject each : myProjectsToImport) {
192 artifacts.addAll(each.getDependencies());
195 final Set<File> files = new THashSet<File>();
196 for (MavenArtifact each : artifacts) {
197 if (each.isResolved()) files.add(each.getFile());
200 final Runnable r = new Runnable() {
201 public void run() {
202 LocalFileSystem.getInstance().refreshIoFiles(files);
206 if (ApplicationManager.getApplication().isUnitTestMode()) {
207 r.run();
209 else {
210 postTasks.add(new MavenProjectsProcessorTask() {
211 public void perform(Project project, MavenEmbeddersManager embeddersManager, MavenConsole console, MavenProgressIndicator indicator)
212 throws MavenProcessCanceledException {
213 indicator.setText("Refreshing files...");
214 r.run();
220 private void mapModulesToMavenProjects() {
221 List<MavenProject> projects = myProjectsTree.getProjects();
222 for (MavenProject each : projects) {
223 Module module = myFileToModuleMapping.get(each.getFile());
224 if (module != null) {
225 myMavenProjectToModule.put(each, module);
229 MavenModuleNameMapper.map(projects,
230 myMavenProjectToModule,
231 myMavenProjectToModuleName,
232 myMavenProjectToModulePath,
233 myImportingSettings.getDedicatedModuleDir());
236 private void configSettings() {
237 ((ProjectEx)myProject).setSavePathsRelative(true);
239 String level = calcTargetLevel();
240 if (level == null) return;
242 String options = JavacSettings.getInstance(myProject).ADDITIONAL_OPTIONS_STRING;
243 Pattern pattern = Pattern.compile("(-target (\\S+))");
244 Matcher matcher = pattern.matcher(options);
246 if (matcher.find()) {
247 String currentValue = MavenProject.normalizeCompilerLevel(matcher.group(2));
249 if (currentValue == null || compareCompilerLevel(level, currentValue) < 0) {
250 StringBuffer buffer = new StringBuffer();
251 matcher.appendReplacement(buffer, "-target " + level);
252 matcher.appendTail(buffer);
253 options = buffer.toString();
256 else {
257 if (!StringUtil.isEmptyOrSpaces(options)) options += " ";
258 options += "-target " + level;
260 JavacSettings.getInstance(myProject).ADDITIONAL_OPTIONS_STRING = options;
263 private String calcTargetLevel() {
264 String maxSource = null;
265 String minTarget = null;
266 for (MavenProject each : myProjectsTree.getProjects()) {
267 String source = each.getSourceLevel();
268 String target = each.getTargetLevel();
269 if (source != null && (maxSource == null || compareCompilerLevel(maxSource, source) < 0)) maxSource = source;
270 if (target != null && (minTarget == null || compareCompilerLevel(minTarget, target) > 0)) minTarget = target;
272 return (maxSource != null && compareCompilerLevel(minTarget, maxSource) < 0) ? maxSource : minTarget;
275 private int compareCompilerLevel(String left, String right) {
276 if (left == null && right == null) return 0;
277 if (left == null) return -1;
278 if (right == null) return 1;
279 return left.compareTo(right);
282 private void deleteObsoleteModules() {
283 final List<Module> obsolete = collectObsoleteModules();
284 if (obsolete.isEmpty()) return;
286 MavenProjectsManager.getInstance(myProject).setMavenizedModules(obsolete, false);
288 final int[] result = new int[1];
289 MavenUtil.invokeAndWait(myProject, ModalityState.NON_MODAL, new Runnable() {
290 public void run() {
291 result[0] = Messages.showYesNoDialog(myProject,
292 ProjectBundle.message("maven.import.message.delete.obsolete", formatModules(obsolete)),
293 ProjectBundle.message("maven.tab.importing"),
294 Messages.getQuestionIcon());
298 if (result[0] == 1) return;// NO
300 for (Module each : obsolete) {
301 myModuleModel.disposeModule(each);
305 private List<Module> collectObsoleteModules() {
306 List<Module> remainingModules = new ArrayList<Module>();
307 Collections.addAll(remainingModules, myModuleModel.getModules());
309 for (MavenProject each : selectProjectsToImport(myProjectsTree.getProjects())) {
310 remainingModules.remove(myMavenProjectToModule.get(each));
313 List<Module> obsolete = new ArrayList<Module>();
314 for (Module each : remainingModules) {
315 if (MavenProjectsManager.getInstance(myProject).isMavenizedModule(each)) {
316 obsolete.add(each);
319 return obsolete;
322 private void importModules(final List<MavenProjectsProcessorTask> postTasks) {
323 final Set<MavenProject> projects = myProjectsToImport;
324 Set<MavenProject> projectsWithNewlyCreatedModules = new THashSet<MavenProject>();
326 for (MavenProject each : projects) {
327 if (ensureModuleCreated(each)) {
328 projectsWithNewlyCreatedModules.add(each);
332 final Map<Module, MavenModuleImporter> moduleImporters = new THashMap<Module, MavenModuleImporter>();
333 for (MavenProject each : projects) {
334 Module module = myMavenProjectToModule.get(each);
335 MavenModuleImporter importer = createModuleImporter(module, each);
336 moduleImporters.put(module, importer);
338 importer.config(projectsWithNewlyCreatedModules.contains(each));
341 for (MavenProject each : projects) {
342 moduleImporters.get(myMavenProjectToModule.get(each)).preConfigFacets();
344 for (MavenProject each : projects) {
345 moduleImporters.get(myMavenProjectToModule.get(each)).configFacets(postTasks);
348 ArrayList<Module> modules = new ArrayList<Module>(moduleImporters.keySet());
349 MavenProjectsManager.getInstance(myProject).setMavenizedModules(modules, true);
352 private boolean ensureModuleCreated(MavenProject project) {
353 if (myMavenProjectToModule.get(project) != null) return false;
355 final String path = myMavenProjectToModulePath.get(project);
357 // for some reason newModule opens the existing iml file, so we
358 // have to remove it beforehand.
359 deleteExistingImlFile(path);
361 final Module module = myModuleModel.newModule(path, StdModuleTypes.JAVA);
362 myMavenProjectToModule.put(project, module);
363 myCreatedModules.add(module);
364 return true;
367 private void deleteExistingImlFile(final String path) {
368 new WriteAction() {
369 protected void run(Result result) throws Throwable {
370 try {
371 VirtualFile file = LocalFileSystem.getInstance().findFileByIoFile(new File(path));
372 if (file != null) file.delete(this);
374 catch (IOException ignore) {
377 }.execute();
380 private MavenModuleImporter createModuleImporter(Module module, MavenProject mavenProject) {
381 return new MavenModuleImporter(module,
382 myProjectsTree,
383 mavenProject,
384 myMavenProjectToModuleName,
385 myImportingSettings,
386 myModelsProvider);
389 private void configModuleGroups() {
390 if (!myImportingSettings.isCreateModuleGroups()) return;
392 final Stack<String> groups = new Stack<String>();
393 final boolean createTopLevelGroup = myProjectsTree.getRootProjects().size() > 1;
395 myProjectsTree.visit(new MavenProjectsTree.SimpleVisitor() {
396 int depth = 0;
398 public void visit(MavenProject each) {
399 depth++;
401 String name = myMavenProjectToModuleName.get(each);
403 if (shouldCreateGroup(each)) {
404 groups.push(ProjectBundle.message("module.group.name", name));
407 if (!shouldCreateModuleFor(each)) {
408 return;
411 Module module = myModuleModel.findModuleByName(name);
412 if (module == null) {
413 // todo: IDEADEV-30669 hook
414 String message = "Module " + name + "not found.";
415 message += "\nmavenProject=" + each.getFile();
416 module = myMavenProjectToModule.get(each);
417 message += "\nmyMavenProjectToModule=" + (module == null ? null : module.getName());
418 message += "\nmyMavenProjectToModuleName=" + myMavenProjectToModuleName.get(each);
419 message += "\nmyMavenProjectToModulePath=" + myMavenProjectToModulePath.get(each);
420 MavenLog.LOG.error(message);
421 return;
424 myModuleModel.setModuleGroupPath(module, groups.isEmpty() ? null : groups.toArray(new String[groups.size()]));
427 public void leave(MavenProject each) {
428 if (shouldCreateGroup(each)) {
429 groups.pop();
431 depth--;
434 private boolean shouldCreateGroup(MavenProject project) {
435 return !myProjectsTree.getModules(project).isEmpty()
436 && (createTopLevelGroup || depth > 1);
441 private void removeUnusedProjectLibraries() {
442 Set<Library> allLibraries = new THashSet<Library>();
443 Collections.addAll(allLibraries, myModelsProvider.getAllLibraries());
445 Set<Library> usedLibraries = new THashSet<Library>();
446 for (ModuleRootModel eachModel : collectModuleModels()) {
447 for (OrderEntry eachEntry : eachModel.getOrderEntries()) {
448 if (eachEntry instanceof LibraryOrderEntry) {
449 Library lib = ((LibraryOrderEntry)eachEntry).getLibrary();
450 if (MavenRootModelAdapter.isMavenLibrary(lib)) usedLibraries.add(lib);
455 Set<Library> unusedLibraries = new THashSet<Library>(allLibraries);
456 unusedLibraries.removeAll(usedLibraries);
458 for (Library each : unusedLibraries) {
459 if (!MavenRootModelAdapter.isChangedByUser(each)) {
460 myModelsProvider.removeLibrary(each);
465 private Collection<ModuleRootModel> collectModuleModels() {
466 Map<Module, ModuleRootModel> rootModels = new THashMap<Module, ModuleRootModel>();
467 for (MavenProject each : myProjectsToImport) {
468 Module module = myMavenProjectToModule.get(each);
469 ModifiableRootModel rootModel = myModelsProvider.getRootModel(module);
470 rootModels.put(module, rootModel);
472 for (Module each : myModuleModel.getModules()) {
473 if (rootModels.containsKey(each)) continue;
474 rootModels.put(each, myModelsProvider.getRootModel(each));
476 return rootModels.values();
479 public List<Module> getCreatedModules() {
480 return myCreatedModules;