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