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