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
;
35 import java
.io
.IOException
;
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
) {
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());
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
);
104 private void removeModulesOrIgnoreMavenProjects(final Map
<MavenProject
, Module
> projectsWithInconsistentModuleType
) {
105 if (projectsWithInconsistentModuleType
.isEmpty()) {
109 final int[] result
= new int[1];
110 MavenUtil
.invokeAndWait(myProject
, ModalityState
.NON_MODAL
, new Runnable() {
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
);
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() + "'";
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() + "'";
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;
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
);
174 deleteObsoleteModules();
175 removeUnusedProjectLibraries();
176 myModelsProvider
.commit();
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() {
202 LocalFileSystem
.getInstance().refreshIoFiles(files
);
206 if (ApplicationManager
.getApplication().isUnitTestMode()) {
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...");
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();
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() {
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
)) {
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
);
367 private void deleteExistingImlFile(final String path
) {
369 protected void run(Result result
) throws Throwable
{
371 VirtualFile file
= LocalFileSystem
.getInstance().findFileByIoFile(new File(path
));
372 if (file
!= null) file
.delete(this);
374 catch (IOException ignore
) {
380 private MavenModuleImporter
createModuleImporter(Module module
, MavenProject mavenProject
) {
381 return new MavenModuleImporter(module
,
384 myMavenProjectToModuleName
,
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() {
398 public void visit(MavenProject each
) {
401 String name
= myMavenProjectToModuleName
.get(each
);
403 if (shouldCreateGroup(each
)) {
404 groups
.push(ProjectBundle
.message("module.group.name", name
));
407 if (!shouldCreateModuleFor(each
)) {
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
);
424 myModuleModel
.setModuleGroupPath(module
, groups
.isEmpty() ?
null : groups
.toArray(new String
[groups
.size()]));
427 public void leave(MavenProject each
) {
428 if (shouldCreateGroup(each
)) {
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
;