1b818b8647eb03d6879c822b05184996f69d6110
[idea/community.git] / java / idea-ui / src / com / intellij / ide / util / importProject / RootDetectionProcessor.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.importProject;
3
4 import com.intellij.framework.detection.impl.FrameworkDetectionProcessor;
5 import com.intellij.ide.JavaUiBundle;
6 import com.intellij.ide.util.projectWizard.importSources.DetectedContentRoot;
7 import com.intellij.ide.util.projectWizard.importSources.DetectedProjectRoot;
8 import com.intellij.ide.util.projectWizard.importSources.DetectedSourceRoot;
9 import com.intellij.ide.util.projectWizard.importSources.ProjectStructureDetector;
10 import com.intellij.openapi.diagnostic.Logger;
11 import com.intellij.openapi.fileTypes.FileTypeManager;
12 import com.intellij.openapi.module.ModuleType;
13 import com.intellij.openapi.progress.ProgressIndicator;
14 import com.intellij.openapi.progress.ProgressManager;
15 import com.intellij.openapi.util.Pair;
16 import com.intellij.openapi.util.io.FileSystemUtil;
17 import com.intellij.openapi.util.io.FileUtil;
18 import com.intellij.util.ArrayUtilRt;
19 import com.intellij.util.SmartList;
20 import com.intellij.util.containers.ContainerUtil;
21 import com.intellij.util.containers.FileCollectionFactory;
22 import com.intellij.util.containers.MultiMap;
23 import org.jetbrains.annotations.NotNull;
24
25 import java.io.File;
26 import java.io.IOException;
27 import java.util.*;
28
29 public class RootDetectionProcessor {
30   private static final Logger LOG = Logger.getInstance(RootDetectionProcessor.class);
31   private final File myBaseDir;
32   private final ProjectStructureDetector[] myDetectors;
33   private final List<DetectedProjectRoot>[] myDetectedRoots;
34   private final FileTypeManager myTypeManager;
35   private final ProgressIndicator myProgressIndicator;
36
37   @NotNull
38   public static List<DetectedRootData> detectRoots(@NotNull File baseProjectFile) {
39     return new RootDetectionProcessor(baseProjectFile, ProjectStructureDetector.EP_NAME.getExtensions()).detectRoots();
40   }
41
42   public RootDetectionProcessor(File baseDir, final ProjectStructureDetector[] detectors) {
43     myBaseDir = getCanonicalDir(baseDir);
44     myDetectors = detectors;
45     //noinspection unchecked
46     myDetectedRoots = new List[myDetectors.length];
47     myTypeManager = FileTypeManager.getInstance();
48     myProgressIndicator = ProgressManager.getInstance().getProgressIndicator();
49   }
50
51   private static File getCanonicalDir(File baseDir) {
52     try {
53       return new File(FileUtil.resolveShortWindowsName(baseDir.getAbsolutePath()));
54     }
55     catch (IOException e) {
56       LOG.info(e);
57       return baseDir;
58     }
59   }
60
61   public static MultiMap<ProjectStructureDetector, DetectedProjectRoot> createRootsMap(List<? extends DetectedRootData> list) {
62     MultiMap<ProjectStructureDetector, DetectedProjectRoot> roots = new MultiMap<>();
63     for (final DetectedRootData rootData : list) {
64       for (ProjectStructureDetector detector : rootData.getSelectedDetectors()) {
65         roots.putValue(detector, rootData.getSelectedRoot());
66       }
67     }
68     return roots;
69   }
70
71
72   public Map<ProjectStructureDetector, List<DetectedProjectRoot>> runDetectors() {
73     if (!myBaseDir.isDirectory()) {
74       return Collections.emptyMap();
75     }
76
77     BitSet enabledDetectors = new BitSet(myDetectors.length);
78     enabledDetectors.set(0, myDetectors.length);
79     for (int i = 0; i < myDetectors.length; i++) {
80       myDetectedRoots[i] = new ArrayList<>();
81     }
82
83     Set<File> parentDirectories = FileCollectionFactory.createCanonicalFileSet();
84     File parent = myBaseDir.getParentFile();
85     while (parent != null) {
86       parentDirectories.add(parent);
87       parent = parent.getParentFile();
88     }
89     processRecursively(myBaseDir, enabledDetectors, parentDirectories);
90
91     final Map<ProjectStructureDetector, List<DetectedProjectRoot>> result = new LinkedHashMap<>();
92     for (int i = 0; i < myDetectors.length; i++) {
93       if (!myDetectedRoots[i].isEmpty()) {
94         result.put(myDetectors[i], myDetectedRoots[i]);
95       }
96     }
97     return result;
98   }
99
100   private List<Pair<File, Integer>> processRecursively(File dir, BitSet enabledDetectors, Set<? super File> parentDirectories) {
101     List<Pair<File, Integer>> parentsToSkip = new SmartList<>();
102
103     if (myTypeManager.isFileIgnored(dir.getName())) {
104       return parentsToSkip;
105     }
106     if (myProgressIndicator != null) {
107       if (myProgressIndicator.isCanceled()) {
108         return parentsToSkip;
109       }
110       myProgressIndicator.setText2(dir.getPath());
111     }
112
113     if (FileSystemUtil.isSymLink(dir)) {
114       try {
115         if (parentDirectories.contains(dir.getCanonicalFile())) {
116           return parentsToSkip;
117         }
118       }
119       catch (IOException ignored) {
120       }
121     }
122
123     try {
124       parentDirectories.add(dir);
125       File[] children = dir.listFiles();
126
127       if (children == null) {
128         children = ArrayUtilRt.EMPTY_FILE_ARRAY;
129       }
130
131       BitSet enabledForChildren = enabledDetectors;
132       for (int i = 0, detectorsLength = myDetectors.length; i < detectorsLength; i++) {
133         if (!enabledDetectors.get(i)) continue;
134
135         final ProjectStructureDetector.DirectoryProcessingResult result = myDetectors[i].detectRoots(dir, children, myBaseDir, myDetectedRoots[i]);
136
137         if (!result.isProcessChildren()) {
138           if (enabledForChildren == enabledDetectors) {
139             enabledForChildren = new BitSet();
140             enabledForChildren.or(enabledDetectors);
141           }
142           enabledForChildren.set(i, false);
143         }
144
145         final File parentToSkip = result.getParentToSkip();
146         if (parentToSkip != null && !FileUtil.filesEqual(parentToSkip, dir)) {
147           parentsToSkip.add(Pair.create(parentToSkip, i));
148         }
149       }
150
151       if (!enabledForChildren.isEmpty()) {
152         for (File child : children) {
153           if (child.isDirectory() && !FrameworkDetectionProcessor.SKIPPED_DIRECTORIES.contains(child.getName())) {
154             final List<Pair<File, Integer>> toSkip = processRecursively(child, enabledForChildren, parentDirectories);
155             if (!toSkip.isEmpty()) {
156               if (enabledForChildren == enabledDetectors) {
157                 enabledForChildren = new BitSet();
158                 enabledForChildren.or(enabledDetectors);
159               }
160               for (Pair<File, Integer> pair : toSkip) {
161                 enabledForChildren.set(pair.getSecond(), false);
162                 if (!FileUtil.filesEqual(pair.getFirst(), dir)) {
163                   parentsToSkip.add(pair);
164                 }
165               }
166               if (enabledForChildren.isEmpty()) {
167                 break;
168               }
169             }
170           }
171         }
172       }
173       return parentsToSkip;
174     }
175     finally {
176       parentDirectories.remove(dir);
177     }
178   }
179
180   private static void removeIncompatibleRoots(DetectedProjectRoot root, Map<File, DetectedRootData> rootData) {
181     DetectedRootData[] allRoots = rootData.values().toArray(new DetectedRootData[0]);
182     for (DetectedRootData child : allRoots) {
183       final File childDirectory = child.getDirectory();
184       if (FileUtil.isAncestor(root.getDirectory(), childDirectory, true)) {
185         for (DetectedProjectRoot projectRoot : child.getAllRoots()) {
186           if (!root.canContainRoot(projectRoot)) {
187             child.removeRoot(projectRoot);
188           }
189         }
190         if (child.isEmpty()) {
191           rootData.remove(childDirectory);
192         }
193       }
194     }
195   }
196
197   private static boolean isUnderIncompatibleRoot(DetectedProjectRoot root, Map<File, DetectedRootData> rootData) {
198     File directory = root.getDirectory().getParentFile();
199     while (directory != null) {
200       final DetectedRootData data = rootData.get(directory);
201       if (data != null) {
202         for (DetectedProjectRoot parentRoot : data.getAllRoots()) {
203           if (!parentRoot.canContainRoot(root)) {
204             return true;
205           }
206         }
207       }
208       directory = directory.getParentFile();
209     }
210     return false;
211   }
212
213   private List<DetectedRootData> detectRoots() {
214     Map<ProjectStructureDetector, List<DetectedProjectRoot>> roots = runDetectors();
215     if (myProgressIndicator != null) {
216       myProgressIndicator.setText2(JavaUiBundle.message("progress.text.processing.0.project.roots", roots.values().size()));
217     }
218
219     Map<File, DetectedRootData> rootData = new LinkedHashMap<>();
220     for (ProjectStructureDetector detector : roots.keySet()) {
221       for (DetectedProjectRoot detectedRoot : roots.get(detector)) {
222         if (isUnderIncompatibleRoot(detectedRoot, rootData)) {
223           continue;
224         }
225
226         final DetectedRootData data = rootData.get(detectedRoot.getDirectory());
227         if (data == null) {
228           rootData.put(detectedRoot.getDirectory(), new DetectedRootData(detector, detectedRoot));
229         }
230         else {
231           detectedRoot = data.addRoot(detector, detectedRoot);
232         }
233         removeIncompatibleRoots(detectedRoot, rootData);
234       }
235     }
236
237     List<DetectedRootData> dataCollection = mergeContentRoots(rootData);
238     if (myProgressIndicator != null) {
239       myProgressIndicator.setText2("");
240     }
241     return dataCollection;
242   }
243
244   private List<DetectedRootData> mergeContentRoots(Map<File, DetectedRootData> rootData) {
245     LOG.debug(rootData.size() + " roots found, merging content roots");
246     boolean hasSourceRoots = false;
247     Set<ModuleType> typesToReplace = new HashSet<>();
248     Set<ModuleType> moduleTypes = new HashSet<>();
249     for (DetectedRootData data : rootData.values()) {
250       for (DetectedProjectRoot root : data.getAllRoots()) {
251         if (root instanceof DetectedContentRoot) {
252           Collections.addAll(typesToReplace, ((DetectedContentRoot)root).getTypesToReplace());
253           moduleTypes.add(((DetectedContentRoot)root).getModuleType());
254         }
255         else if (root instanceof DetectedSourceRoot) {
256           LOG.debug("Source root found: " + root.getDirectory() + ", content roots will be ignored");
257           hasSourceRoots = true;
258           break;
259         }
260       }
261     }
262     moduleTypes.removeAll(typesToReplace);
263
264     if (hasSourceRoots || moduleTypes.size() <= 1) {
265       Iterator<DetectedRootData> iterator = rootData.values().iterator();
266       DetectedContentRoot firstRoot = null;
267       ProjectStructureDetector firstDetector = null;
268       while (iterator.hasNext()) {
269         DetectedRootData data = iterator.next();
270         for (DetectedProjectRoot root : data.getAllRoots()) {
271           if (root instanceof DetectedContentRoot) {
272             LOG.debug("Removed detected " + root.getRootTypeName() + " content root: " + root.getDirectory());
273             Collection<ProjectStructureDetector> detectors = data.removeRoot(root);
274             if ((firstRoot == null || firstDetector == null) && moduleTypes.contains(((DetectedContentRoot)root).getModuleType())) {
275               firstRoot = (DetectedContentRoot)root;
276               firstDetector = ContainerUtil.getFirstItem(detectors);
277             }
278           }
279         }
280         if (data.isEmpty()) {
281           iterator.remove();
282         }
283       }
284       if (!hasSourceRoots && firstRoot != null && firstDetector != null) {
285         DetectedContentRoot baseRoot = new DetectedContentRoot(myBaseDir, firstRoot.getRootTypeName(), firstRoot.getModuleType());
286         DetectedRootData data = rootData.get(myBaseDir);
287         if (data == null) {
288           rootData.put(myBaseDir, new DetectedRootData(firstDetector, baseRoot));
289         }
290         else {
291           data.addRoot(firstDetector, baseRoot);
292         }
293         LOG.debug("Added " + firstRoot.getRootTypeName() + " content root for " + myBaseDir);
294       }
295     }
296     return new ArrayList<>(rootData.values());
297   }
298 }