add ProjectFileIndex#getSourceFolder to simplify clients (IDEA-CR-57371)
[idea/community.git] / platform / lang-impl / src / com / intellij / psi / impl / source / resolve / reference / impl / providers / JpsFileTargetContextUtils.java
1 // Copyright 2000-2019 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.psi.impl.source.resolve.reference.impl.providers;
3
4 import com.intellij.openapi.project.Project;
5 import com.intellij.openapi.roots.ProjectFileIndex;
6 import com.intellij.openapi.roots.SourceFolder;
7 import com.intellij.openapi.vfs.VirtualFile;
8 import com.intellij.psi.PsiFileSystemItem;
9 import com.intellij.util.containers.ContainerUtil;
10 import org.jetbrains.annotations.NotNull;
11 import org.jetbrains.annotations.Nullable;
12 import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes;
13 import org.jetbrains.jps.model.java.JavaResourceRootType;
14 import org.jetbrains.jps.model.java.JavaSourceRootProperties;
15 import org.jetbrains.jps.model.java.JavaSourceRootType;
16 import org.jetbrains.jps.model.module.JpsModuleSourceRoot;
17 import org.jetbrains.jps.model.module.JpsModuleSourceRootType;
18
19 import java.util.Collection;
20 import java.util.List;
21
22 public final class JpsFileTargetContextUtils {
23   private JpsFileTargetContextUtils() {
24   }
25
26   /**
27    * Sorts and filters out contexts depending on their {@link JpsModuleSourceRootType} for Create File quick fixes.
28    *
29    * @see com.intellij.codeInsight.daemon.quickFix.CreateFilePathFix
30    * @see com.intellij.codeInsight.daemon.quickFix.FileReferenceQuickFixProvider
31    */
32   @NotNull
33   public static Collection<FileTargetContext> prepareTargetContexts(@NotNull Project project,
34                                                                     @NotNull VirtualFile file,
35                                                                     @NotNull Collection<FileTargetContext> targetContexts) {
36     // here we try to sort target locations depending on src/test origin
37     if (targetContexts.size() <= 1) {
38       return targetContexts;
39     }
40
41     ProjectFileIndex projectFileIndex = ProjectFileIndex.getInstance(project);
42     boolean isInSources = projectFileIndex.isInSourceContent(file)
43                           && !projectFileIndex.isInTestSourceContent(file);
44
45     List<FileTargetContextWrapper> targetContextWrappers = ContainerUtil.filter(findSourceRootTypes(targetContexts), tc -> {
46       if (isInSources) {
47         // exclude test directories for sources options
48         if (tc.getSourceRootType() != null
49             && tc.getSourceRootType().isForTests()) {
50           return false;
51         }
52       }
53
54       if (tc.getJpsModuleSourceRoot() == null) {
55         return true;
56       }
57       JavaSourceRootProperties srcProperties = tc.getJpsModuleSourceRoot().getProperties(JavaModuleSourceRootTypes.SOURCES);
58       if (srcProperties == null) {
59         return true;
60       }
61
62       return !srcProperties.isForGeneratedSources();
63     });
64
65     // sort only if we have different source root types
66     if (hasEqualSourceRootTypes(targetContextWrappers)) {
67       return targetContexts;
68     }
69
70     // if file is under sources root then src/resources directories at the top
71     // if file is under test sources root then test/resources directories at the top
72     if (projectFileIndex.isInTestSourceContent(file)) {
73       targetContextWrappers.sort(JpsFileTargetContextUtils::compareTargetsForTests);
74     }
75     else {
76       // it could be a file from web resource root, it is not in source content, thus we do not check isInSourceContent(file)
77       targetContextWrappers.sort(JpsFileTargetContextUtils::compareTargetsForProduction);
78     }
79     return ContainerUtil.map(targetContextWrappers, FileTargetContextWrapper::getTargetContext);
80   }
81
82   private static boolean hasEqualSourceRootTypes(@NotNull List<FileTargetContextWrapper> wrappers) {
83     if (wrappers.size() <= 1) {
84       return true;
85     }
86
87     JpsModuleSourceRootType<?> sourceRootType = null;
88     for (FileTargetContextWrapper item : wrappers) {
89       JpsModuleSourceRootType<?> itemSourceRootType = item.getSourceRootType();
90       if (sourceRootType == null) {
91         if (itemSourceRootType != null) {
92           sourceRootType = itemSourceRootType;
93         }
94       }
95       else if (sourceRootType != itemSourceRootType) {
96         return false;
97       }
98     }
99
100     return true;
101   }
102
103   private static List<FileTargetContextWrapper> findSourceRootTypes(Collection<FileTargetContext> targetContexts) {
104     return ContainerUtil.map(targetContexts, c -> {
105       Project project = c.getFileSystemItem().getProject();
106
107       SourceFolder sourceFolder = null;
108       VirtualFile file = c.getFileSystemItem().getVirtualFile();
109       if (file != null) {
110         sourceFolder = getSourceFolder(project, file);
111       }
112
113       return new FileTargetContextWrapper(c, sourceFolder);
114     });
115   }
116
117   @Nullable
118   private static SourceFolder getSourceFolder(@NotNull Project project, @NotNull VirtualFile directory) {
119     ProjectFileIndex projectFileIndex = ProjectFileIndex.getInstance(project);
120     return projectFileIndex.getSourceFolder(directory);
121   }
122
123   private static int compareTargetsForTests(@NotNull FileTargetContextWrapper d1, @NotNull FileTargetContextWrapper d2) {
124     int o1 = getTestsTargetOrdinal(d1);
125     int o2 = getTestsTargetOrdinal(d2);
126
127     if (o1 > 0 && o2 > 0) {
128       return Integer.compare(o1, o2);
129     }
130
131     return compareDirectoryPaths(d1, d2);
132   }
133
134   private static int compareTargetsForProduction(@NotNull FileTargetContextWrapper d1, @NotNull FileTargetContextWrapper d2) {
135     int o1 = getSourcesTargetOrdinal(d1);
136     int o2 = getSourcesTargetOrdinal(d2);
137
138     if (o1 > 0 && o2 > 0) {
139       return Integer.compare(o1, o2);
140     }
141
142     return compareDirectoryPaths(d1, d2);
143   }
144
145   private static int getTestsTargetOrdinal(@NotNull FileTargetContextWrapper item) {
146     JpsModuleSourceRootType<?> type = item.getSourceRootType();
147
148     if (isSourceItem(type)) return 4;
149     if (isTestSourceItem(type)) return 3;
150     if (isResourceItem(type)) return 2;
151     if (isTestResourceItem(type)) return 1;
152
153     return 0;
154   }
155
156   private static int getSourcesTargetOrdinal(@NotNull FileTargetContextWrapper item) {
157     JpsModuleSourceRootType<?> type = item.getSourceRootType();
158
159     if (isTestSourceItem(type)) return 4;
160     if (isSourceItem(type)) return 3;
161     if (isTestResourceItem(type)) return 2;
162     if (isResourceItem(type)) return 1;
163
164     return 0;
165   }
166
167   private static int compareDirectoryPaths(@NotNull FileTargetContextWrapper d1, @NotNull FileTargetContextWrapper d2) {
168     PsiFileSystemItem directory1 = d1.getTargetContext().getFileSystemItem();
169     PsiFileSystemItem directory2 = d2.getTargetContext().getFileSystemItem();
170
171     assert directory1 != null : "Invalid PsiFileSystemItem instances found";
172     assert directory2 != null : "Invalid PsiFileSystemItem instances found";
173
174     VirtualFile f1 = directory1.getVirtualFile();
175     VirtualFile f2 = directory2.getVirtualFile();
176     return f1.getPath().compareTo(f2.getPath());
177   }
178
179   private static boolean isTestResourceItem(@Nullable JpsModuleSourceRootType<?> type) {
180     return type == JavaResourceRootType.TEST_RESOURCE;
181   }
182
183   private static boolean isResourceItem(@Nullable JpsModuleSourceRootType<?> type) {
184     return type == JavaResourceRootType.RESOURCE;
185   }
186
187   private static boolean isTestSourceItem(@Nullable JpsModuleSourceRootType<?> type) {
188     return type == JavaSourceRootType.TEST_SOURCE;
189   }
190
191   private static boolean isSourceItem(@Nullable JpsModuleSourceRootType<?> type) {
192     return type == JavaSourceRootType.SOURCE;
193   }
194
195   private static class FileTargetContextWrapper {
196     private final FileTargetContext myTargetContext;
197     private final SourceFolder mySourceFolder;
198
199     private FileTargetContextWrapper(FileTargetContext context, @Nullable SourceFolder sourceFolder) {
200       myTargetContext = context;
201       mySourceFolder = sourceFolder;
202     }
203
204     public FileTargetContext getTargetContext() {
205       return myTargetContext;
206     }
207
208     @Nullable
209     public JpsModuleSourceRootType<?> getSourceRootType() {
210       return mySourceFolder != null ? mySourceFolder.getRootType() : null;
211     }
212
213     public JpsModuleSourceRoot getJpsModuleSourceRoot() {
214       return mySourceFolder != null ? mySourceFolder.getJpsElement() : null;
215     }
216   }
217 }