emptyIterator() used; misc warning fixes
[idea/community.git] / java / java-impl / src / com / intellij / cyclicDependencies / CyclicDependenciesBuilder.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.cyclicDependencies;
17
18 import com.intellij.analysis.AnalysisScope;
19 import com.intellij.analysis.AnalysisScopeBundle;
20 import com.intellij.openapi.progress.ProcessCanceledException;
21 import com.intellij.openapi.progress.ProgressIndicator;
22 import com.intellij.openapi.progress.ProgressManager;
23 import com.intellij.openapi.project.Project;
24 import com.intellij.openapi.roots.ProjectFileIndex;
25 import com.intellij.openapi.roots.ProjectRootManager;
26 import com.intellij.packageDependencies.DependenciesBuilder;
27 import com.intellij.packageDependencies.ForwardDependenciesBuilder;
28 import com.intellij.psi.*;
29 import com.intellij.util.graph.*;
30 import org.jetbrains.annotations.NotNull;
31
32 import java.util.*;
33
34 public class CyclicDependenciesBuilder{
35   @NotNull
36   private final Project myProject;
37   private final AnalysisScope myScope;
38   private final Map<String, PsiPackage> myPackages = new HashMap<>();
39   private Graph<PsiPackage> myGraph;
40   private final Map<PsiPackage, Map<PsiPackage, Set<PsiFile>>> myFilesInDependentPackages = new HashMap<>();
41   private final Map<PsiPackage, Map<PsiPackage, Set<PsiFile>>> myBackwardFilesInDependentPackages = new HashMap<>();
42   private final Map<PsiPackage, Set<PsiPackage>> myPackageDependencies = new HashMap<>();
43   private HashMap<PsiPackage, Set<List<PsiPackage>>> myCyclicDependencies = new HashMap<>();
44   private int myFileCount;
45   private final ForwardDependenciesBuilder myForwardBuilder;
46
47   private String myRootNodeNameInUsageView;
48
49   public CyclicDependenciesBuilder(@NotNull Project project, final AnalysisScope scope) {
50     myProject = project;
51     myScope = scope;
52     myForwardBuilder = new ForwardDependenciesBuilder(myProject, myScope){
53       @Override
54       public String getRootNodeNameInUsageView() {
55         return CyclicDependenciesBuilder.this.getRootNodeNameInUsageView();
56       }
57
58       @Override
59       public String getInitialUsagesPosition() {
60         return AnalysisScopeBundle.message("cyclic.dependencies.usage.view.initial.text");
61       }
62     };
63   }
64
65   public String getRootNodeNameInUsageView() {
66     return myRootNodeNameInUsageView;
67   }
68
69   public void setRootNodeNameInUsageView(final String rootNodeNameInUsageView) {
70     myRootNodeNameInUsageView = rootNodeNameInUsageView;
71   }
72
73   @NotNull
74   public Project getProject() {
75     return myProject;
76   }
77
78   public AnalysisScope getScope() {
79     return myScope;
80   }
81
82   public DependenciesBuilder getForwardBuilder() {
83     return myForwardBuilder;
84   }
85
86   public void analyze() {
87     final ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(getProject()).getFileIndex();
88     getScope().accept(new PsiRecursiveElementVisitor() {
89       @Override public void visitFile(PsiFile file) {
90         if (file instanceof PsiJavaFile) {
91           PsiJavaFile psiJavaFile = (PsiJavaFile)file;
92           if (getScope().contains(psiJavaFile)) {
93             final PsiPackage aPackage = findPackage(psiJavaFile.getPackageName());
94             if (aPackage != null) {
95               myPackages.put(psiJavaFile.getPackageName(), aPackage);
96             }
97           }
98           final Set<PsiPackage> packs = getPackageHierarhy(psiJavaFile.getPackageName());
99           final ForwardDependenciesBuilder builder = new ForwardDependenciesBuilder(getProject(), new AnalysisScope(psiJavaFile));
100           builder.setTotalFileCount(getScope().getFileCount());
101           builder.setInitialFileCount(++myFileCount);
102           builder.analyze();
103           final Set<PsiFile> psiFiles = builder.getDependencies().get(psiJavaFile);
104           if (psiFiles == null) return;
105           for (PsiPackage pack : packs) {
106             Set<PsiPackage> pack2Packages = myPackageDependencies.get(pack);
107             if (pack2Packages == null) {
108               pack2Packages = new HashSet<>();
109               myPackageDependencies.put(pack, pack2Packages);
110             }
111             for (PsiFile psiFile : psiFiles) {
112               if (!(psiFile instanceof PsiJavaFile) ||
113                   !projectFileIndex.isInSourceContent(psiFile.getVirtualFile()) ||
114                   !getScope().contains(psiFile)) {
115                 continue;
116               }
117
118               // construct dependent packages
119               final String packageName = ((PsiJavaFile)psiFile).getPackageName();
120               //do not depend on parent packages
121               if (packageName.startsWith(pack.getQualifiedName())) {
122                 continue;
123               }
124               final PsiPackage depPackage = findPackage(packageName);
125               if (depPackage == null) { //not from analyze scope
126                 continue;
127               }
128               pack2Packages.add(depPackage);
129
130               constractFilesInDependenciesPackagesMap(pack, depPackage, psiFile, myFilesInDependentPackages);
131               constractFilesInDependenciesPackagesMap(depPackage, pack, psiJavaFile, myBackwardFilesInDependentPackages);
132               constractWholeDependenciesMap(psiJavaFile, psiFile);
133             }
134           }
135         }
136       }
137     });
138     ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
139     if (indicator != null) {
140       if (indicator.isCanceled()) {
141         throw new ProcessCanceledException();
142       }
143       indicator.setText(AnalysisScopeBundle.message("cyclic.dependencies.progress.text"));
144       indicator.setText2("");
145       indicator.setIndeterminate(true);
146     }
147     myCyclicDependencies = getCycles(myPackages.values());
148   }
149
150   private void constractFilesInDependenciesPackagesMap(final PsiPackage pack,
151                                                        final PsiPackage depPackage,
152                                                        final PsiFile file,
153                                                        final Map<PsiPackage, Map<PsiPackage, Set<PsiFile>>> filesInDependentPackages) {
154     Map<PsiPackage, Set<PsiFile>> dependentPackages2Files = filesInDependentPackages.get(pack);
155     if (dependentPackages2Files == null) {
156       dependentPackages2Files = new HashMap<>();
157       filesInDependentPackages.put(pack, dependentPackages2Files);
158     }
159     Set<PsiFile> depFiles = dependentPackages2Files.get(depPackage);
160     if (depFiles == null) {
161       depFiles = new HashSet<>();
162       dependentPackages2Files.put(depPackage, depFiles);
163     }
164     depFiles.add(file);
165   }
166
167 //construct all dependencies for usage view
168   private void constractWholeDependenciesMap(final PsiJavaFile psiJavaFile, final PsiFile psiFile) {
169     Set<PsiFile> wholeDependencies = myForwardBuilder.getDependencies().get(psiJavaFile);
170     if (wholeDependencies == null) {
171       wholeDependencies = new HashSet<>();
172       myForwardBuilder.getDependencies().put(psiJavaFile, wholeDependencies);
173     }
174     wholeDependencies.add(psiFile);
175   }
176
177   public Set<PsiFile> getDependentFilesInPackage(PsiPackage pack, PsiPackage depPack) {
178     Set<PsiFile> psiFiles = new HashSet<>();
179     final Map<PsiPackage, Set<PsiFile>> map = myFilesInDependentPackages.get(pack);
180     if (map != null){
181       psiFiles = map.get(depPack);
182     }
183     if (psiFiles == null) {
184       psiFiles = new HashSet<>();
185     }
186     return psiFiles;
187   }
188
189   public Set<PsiFile> getDependentFilesInPackage(PsiPackage firstPack, PsiPackage middlePack, PsiPackage lastPack) {
190     Set<PsiFile> result = new HashSet<>();
191     final Map<PsiPackage, Set<PsiFile>> forwardMap = myFilesInDependentPackages.get(firstPack);
192     if (forwardMap != null && forwardMap.get(middlePack) != null){
193       result.addAll(forwardMap.get(middlePack));
194     }
195     final Map<PsiPackage, Set<PsiFile>> backwardMap = myBackwardFilesInDependentPackages.get(lastPack);
196     if (backwardMap != null && backwardMap.get(middlePack) != null){
197       result.addAll(backwardMap.get(middlePack));
198     }
199     return result;
200   }
201
202
203   public HashMap<PsiPackage, Set<List<PsiPackage>>> getCyclicDependencies() {
204     return myCyclicDependencies;
205   }
206
207   public HashMap<PsiPackage, Set<List<PsiPackage>>> getCycles(Collection<PsiPackage> packages) {
208     if (myGraph == null){
209       myGraph = buildGraph();
210     }
211     final HashMap<PsiPackage, Set<List<PsiPackage>>> result = new HashMap<>();
212     for (PsiPackage psiPackage : packages) {
213       Set<List<PsiPackage>> paths2Pack = result.get(psiPackage);
214       if (paths2Pack == null) {
215         paths2Pack = new HashSet<>();
216         result.put(psiPackage, paths2Pack);
217       }
218       paths2Pack.addAll(GraphAlgorithms.getInstance().findCycles(myGraph, psiPackage));
219     }
220     return result;
221   }
222
223   public Map<String, PsiPackage> getAllScopePackages() {
224     if (myPackages.isEmpty()) {
225       final PsiManager psiManager = PsiManager.getInstance(getProject());
226       getScope().accept(new PsiRecursiveElementVisitor() {
227         @Override public void visitFile(PsiFile file) {
228           if (file instanceof PsiJavaFile) {
229             PsiJavaFile psiJavaFile = (PsiJavaFile)file;
230             final PsiPackage aPackage = JavaPsiFacade.getInstance(psiManager.getProject()).findPackage(psiJavaFile.getPackageName());
231             if (aPackage != null) {
232               myPackages.put(aPackage.getQualifiedName(), aPackage);
233             }
234           }
235         }
236       });
237     }
238     return myPackages;
239   }
240
241   private Graph<PsiPackage> buildGraph() {
242     return GraphGenerator.generate(CachingSemiGraph.cache(new InboundSemiGraph<PsiPackage>() {
243       @Override
244       @NotNull
245       public Collection<PsiPackage> getNodes() {
246         return getAllScopePackages().values();
247       }
248
249       @NotNull
250       @Override
251       public Iterator<PsiPackage> getIn(PsiPackage psiPack) {
252         final Set<PsiPackage> psiPackages = myPackageDependencies.get(psiPack);
253         if (psiPackages == null) {     //for packs without java classes
254           return Collections.emptyIterator();
255         }
256         return psiPackages.iterator();
257       }
258     }));
259   }
260
261   public Set<PsiPackage> getPackageHierarhy(String packageName) {
262     final Set<PsiPackage> result = new HashSet<>();
263     PsiPackage psiPackage = findPackage(packageName);
264     if (psiPackage != null) {
265       result.add(psiPackage);
266     }
267     else {
268       return result;
269     }
270     while (psiPackage.getParentPackage() != null && psiPackage.getParentPackage().getQualifiedName().length() != 0) {
271       final PsiPackage aPackage = findPackage(psiPackage.getParentPackage().getQualifiedName());
272       if (aPackage == null) {
273         break;
274       }
275       result.add(aPackage);
276       psiPackage = psiPackage.getParentPackage();
277     }
278     return result;
279   }
280
281   private PsiPackage findPackage(String packName) {
282     final PsiPackage psiPackage = getAllScopePackages().get(packName);
283     return psiPackage;
284   }
285 }