cleanup
[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   private final Project myProject;
36   private final AnalysisScope myScope;
37   private final Map<String, PsiPackage> myPackages = new HashMap<>();
38   private Graph<PsiPackage> myGraph;
39   private final Map<PsiPackage, Map<PsiPackage, Set<PsiFile>>> myFilesInDependentPackages = new HashMap<>();
40   private final Map<PsiPackage, Map<PsiPackage, Set<PsiFile>>> myBackwardFilesInDependentPackages = new HashMap<>();
41   private final Map<PsiPackage, Set<PsiPackage>> myPackageDependencies = new HashMap<>();
42   private HashMap<PsiPackage, Set<List<PsiPackage>>> myCyclicDependencies = new HashMap<>();
43   private int myFileCount;
44   private final ForwardDependenciesBuilder myForwardBuilder;
45
46   private String myRootNodeNameInUsageView;
47
48   public CyclicDependenciesBuilder(final Project project, final AnalysisScope scope) {
49     myProject = project;
50     myScope = scope;
51     myForwardBuilder = new ForwardDependenciesBuilder(myProject, myScope){
52       public String getRootNodeNameInUsageView() {
53         return CyclicDependenciesBuilder.this.getRootNodeNameInUsageView();
54       }
55
56       public String getInitialUsagesPosition() {
57         return AnalysisScopeBundle.message("cyclic.dependencies.usage.view.initial.text");
58       }
59     };
60   }
61
62   public String getRootNodeNameInUsageView() {
63     return myRootNodeNameInUsageView;
64   }
65
66   public void setRootNodeNameInUsageView(final String rootNodeNameInUsageView) {
67     myRootNodeNameInUsageView = rootNodeNameInUsageView;
68   }
69
70   public Project getProject() {
71     return myProject;
72   }
73
74   public AnalysisScope getScope() {
75     return myScope;
76   }
77
78   public DependenciesBuilder getForwardBuilder() {
79     return myForwardBuilder;
80   }
81
82   public void analyze() {
83     final ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(getProject()).getFileIndex();
84     getScope().accept(new PsiRecursiveElementVisitor() {
85       @Override public void visitFile(PsiFile file) {
86         if (file instanceof PsiJavaFile) {
87           PsiJavaFile psiJavaFile = (PsiJavaFile)file;
88           if (getScope().contains(psiJavaFile)) {
89             final PsiPackage aPackage = findPackage(psiJavaFile.getPackageName());
90             if (aPackage != null) {
91               myPackages.put(psiJavaFile.getPackageName(), aPackage);
92             }
93           }
94           final Set<PsiPackage> packs = getPackageHierarhy(psiJavaFile.getPackageName());
95           final ForwardDependenciesBuilder builder = new ForwardDependenciesBuilder(getProject(), new AnalysisScope(psiJavaFile));
96           builder.setTotalFileCount(getScope().getFileCount());
97           builder.setInitialFileCount(++myFileCount);
98           builder.analyze();
99           final Set<PsiFile> psiFiles = builder.getDependencies().get(psiJavaFile);
100           if (psiFiles == null) return;
101           for (PsiPackage pack : packs) {
102             Set<PsiPackage> pack2Packages = myPackageDependencies.get(pack);
103             if (pack2Packages == null) {
104               pack2Packages = new HashSet<>();
105               myPackageDependencies.put(pack, pack2Packages);
106             }
107             for (PsiFile psiFile : psiFiles) {
108               if (!(psiFile instanceof PsiJavaFile) ||
109                   !projectFileIndex.isInSourceContent(psiFile.getVirtualFile()) ||
110                   !getScope().contains(psiFile)) {
111                 continue;
112               }
113
114               // construct dependent packages
115               final String packageName = ((PsiJavaFile)psiFile).getPackageName();
116               //do not depend on parent packages
117               if (packageName.startsWith(pack.getQualifiedName())) {
118                 continue;
119               }
120               final PsiPackage depPackage = findPackage(packageName);
121               if (depPackage == null) { //not from analyze scope
122                 continue;
123               }
124               pack2Packages.add(depPackage);
125
126               constractFilesInDependenciesPackagesMap(pack, depPackage, psiFile, myFilesInDependentPackages);
127               constractFilesInDependenciesPackagesMap(depPackage, pack, psiJavaFile, myBackwardFilesInDependentPackages);
128               constractWholeDependenciesMap(psiJavaFile, psiFile);
129             }
130           }
131         }
132       }
133     });
134     ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
135     if (indicator != null) {
136       if (indicator.isCanceled()) {
137         throw new ProcessCanceledException();
138       }
139       indicator.setText(AnalysisScopeBundle.message("cyclic.dependencies.progress.text"));
140       indicator.setText2("");
141       indicator.setIndeterminate(true);
142     }
143     myCyclicDependencies = getCycles(myPackages.values());
144   }
145
146   private void constractFilesInDependenciesPackagesMap(final PsiPackage pack,
147                                                        final PsiPackage depPackage,
148                                                        final PsiFile file,
149                                                        final Map<PsiPackage, Map<PsiPackage, Set<PsiFile>>> filesInDependentPackages) {
150     Map<PsiPackage, Set<PsiFile>> dependentPackages2Files = filesInDependentPackages.get(pack);
151     if (dependentPackages2Files == null) {
152       dependentPackages2Files = new HashMap<>();
153       filesInDependentPackages.put(pack, dependentPackages2Files);
154     }
155     Set<PsiFile> depFiles = dependentPackages2Files.get(depPackage);
156     if (depFiles == null) {
157       depFiles = new HashSet<>();
158       dependentPackages2Files.put(depPackage, depFiles);
159     }
160     depFiles.add(file);
161   }
162
163 //construct all dependencies for usage view
164   private void constractWholeDependenciesMap(final PsiJavaFile psiJavaFile, final PsiFile psiFile) {
165     Set<PsiFile> wholeDependencies = myForwardBuilder.getDependencies().get(psiJavaFile);
166     if (wholeDependencies == null) {
167       wholeDependencies = new HashSet<>();
168       myForwardBuilder.getDependencies().put(psiJavaFile, wholeDependencies);
169     }
170     wholeDependencies.add(psiFile);
171   }
172
173   public Set<PsiFile> getDependentFilesInPackage(PsiPackage pack, PsiPackage depPack) {
174     Set<PsiFile> psiFiles = new HashSet<>();
175     final Map<PsiPackage, Set<PsiFile>> map = myFilesInDependentPackages.get(pack);
176     if (map != null){
177       psiFiles = map.get(depPack);
178     }
179     if (psiFiles == null) {
180       psiFiles = new HashSet<>();
181     }
182     return psiFiles;
183   }
184
185   public Set<PsiFile> getDependentFilesInPackage(PsiPackage firstPack, PsiPackage middlePack, PsiPackage lastPack) {
186     Set<PsiFile> result = new HashSet<>();
187     final Map<PsiPackage, Set<PsiFile>> forwardMap = myFilesInDependentPackages.get(firstPack);
188     if (forwardMap != null && forwardMap.get(middlePack) != null){
189       result.addAll(forwardMap.get(middlePack));
190     }
191     final Map<PsiPackage, Set<PsiFile>> backwardMap = myBackwardFilesInDependentPackages.get(lastPack);
192     if (backwardMap != null && backwardMap.get(middlePack) != null){
193       result.addAll(backwardMap.get(middlePack));
194     }
195     return result;
196   }
197
198
199   public HashMap<PsiPackage, Set<List<PsiPackage>>> getCyclicDependencies() {
200     return myCyclicDependencies;
201   }
202
203   public HashMap<PsiPackage, Set<List<PsiPackage>>> getCycles(Collection<PsiPackage> packages) {
204     if (myGraph == null){
205       myGraph = buildGraph();
206     }
207     final HashMap<PsiPackage, Set<List<PsiPackage>>> result = new HashMap<>();
208     for (Iterator<PsiPackage> iterator = packages.iterator(); iterator.hasNext();) {
209       PsiPackage psiPackage = iterator.next();
210         Set<List<PsiPackage>> paths2Pack = result.get(psiPackage);
211         if (paths2Pack == null) {
212           paths2Pack = new HashSet<>();
213           result.put(psiPackage, paths2Pack);
214         }
215         paths2Pack.addAll(GraphAlgorithms.getInstance().findCycles(myGraph, psiPackage));
216     }
217     return result;
218   }
219
220   public Map<String, PsiPackage> getAllScopePackages() {
221     if (myPackages.isEmpty()) {
222       final PsiManager psiManager = PsiManager.getInstance(getProject());
223       getScope().accept(new PsiRecursiveElementVisitor() {
224         @Override public void visitFile(PsiFile file) {
225           if (file instanceof PsiJavaFile) {
226             PsiJavaFile psiJavaFile = (PsiJavaFile)file;
227             final PsiPackage aPackage = JavaPsiFacade.getInstance(psiManager.getProject()).findPackage(psiJavaFile.getPackageName());
228             if (aPackage != null) {
229               myPackages.put(aPackage.getQualifiedName(), aPackage);
230             }
231           }
232         }
233       });
234     }
235     return myPackages;
236   }
237
238   private Graph<PsiPackage> buildGraph() {
239     return GraphGenerator.generate(CachingSemiGraph.cache(new InboundSemiGraph<PsiPackage>() {
240       @Override
241       @NotNull
242       public Collection<PsiPackage> getNodes() {
243         return getAllScopePackages().values();
244       }
245
246       @NotNull
247       @Override
248       public Iterator<PsiPackage> getIn(PsiPackage psiPack) {
249         final Set<PsiPackage> psiPackages = myPackageDependencies.get(psiPack);
250         if (psiPackages == null) {     //for packs without java classes
251           return new HashSet<PsiPackage>().iterator();
252         }
253         return psiPackages.iterator();
254       }
255     }));
256   }
257
258   public Set<PsiPackage> getPackageHierarhy(String packageName) {
259     final Set<PsiPackage> result = new HashSet<>();
260     PsiPackage psiPackage = findPackage(packageName);
261     if (psiPackage != null) {
262       result.add(psiPackage);
263     }
264     else {
265       return result;
266     }
267     while (psiPackage.getParentPackage() != null && psiPackage.getParentPackage().getQualifiedName().length() != 0) {
268       final PsiPackage aPackage = findPackage(psiPackage.getParentPackage().getQualifiedName());
269       if (aPackage == null) {
270         break;
271       }
272       result.add(aPackage);
273       psiPackage = psiPackage.getParentPackage();
274     }
275     return result;
276   }
277
278   private PsiPackage findPackage(String packName) {
279     final PsiPackage psiPackage = getAllScopePackages().get(packName);
280     return psiPackage;
281   }
282 }