a25e72c322296ff3fb409f291738bbb106257196
[idea/community.git] / jps / jps-builders / src / org / jetbrains / jps / javac / ast / JavacReferencesCollector.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 org.jetbrains.jps.javac.ast;
17
18 import com.intellij.util.ReflectionUtil;
19 import com.intellij.util.SmartList;
20 import com.sun.source.tree.Tree;
21 import com.sun.source.util.JavacTask;
22 import com.sun.source.util.TaskEvent;
23 import com.sun.source.util.TaskListener;
24 import com.sun.tools.javac.code.Symbol;
25 import com.sun.tools.javac.tree.JCTree;
26 import com.sun.tools.javac.util.ClientCodeException;
27 import com.sun.tools.javac.util.Name;
28 import gnu.trove.THashSet;
29 import org.jetbrains.jps.javac.ast.api.JavacFileReferencesRegistrar;
30 import org.jetbrains.jps.javac.ast.api.JavacRefSymbol;
31 import org.jetbrains.jps.service.JpsServiceManager;
32
33 import javax.lang.model.element.ElementKind;
34 import javax.tools.*;
35 import java.lang.reflect.InvocationTargetException;
36 import java.lang.reflect.Method;
37 import java.util.List;
38 import java.util.Set;
39
40 public class JavacReferencesCollector {
41   public static void installOn(JavacTask task) {
42     List<JavacFileReferencesRegistrar> fullASTListeners = new SmartList<JavacFileReferencesRegistrar>();
43     List<JavacFileReferencesRegistrar> onlyImportsListeners = new SmartList<JavacFileReferencesRegistrar>();
44     for (JavacFileReferencesRegistrar listener : JpsServiceManager.getInstance().getExtensions(JavacFileReferencesRegistrar.class)) {
45       if (!listener.initialize()) {
46         continue;
47       }
48       (listener.onlyImports() ? onlyImportsListeners : fullASTListeners).add(listener);
49     }
50
51     final JavacFileReferencesRegistrar[] fullASTListenerArray = fullASTListeners.toArray(new JavacFileReferencesRegistrar[fullASTListeners.size()]);
52     final JavacFileReferencesRegistrar[] onlyImportsListenerArray = onlyImportsListeners.toArray(new JavacFileReferencesRegistrar[onlyImportsListeners.size()]);
53     if (fullASTListenerArray.length == 0 && onlyImportsListenerArray.length == 0) {
54       return;
55     }
56
57     Method addTaskMethod = ReflectionUtil.getMethod(JavacTask.class, "addTaskListener", TaskListener.class); // jdk >= 8
58     if (addTaskMethod == null) {
59       addTaskMethod = ReflectionUtil.getMethod(JavacTask.class, "setTaskListener", TaskListener.class); // jdk 6-7
60     }
61     assert addTaskMethod != null;
62
63     try {
64       addTaskMethod.invoke(task, new MyTaskListener(fullASTListenerArray, onlyImportsListenerArray));
65     }
66     catch (IllegalAccessException e) {
67       throw new RuntimeException(e);
68     }
69     catch (InvocationTargetException e) {
70       throw new RuntimeException(e);
71     }
72   }
73
74   private static final class MyTaskListener implements TaskListener {
75     private JCTree.JCCompilationUnit myCurrentCompilationUnit;
76     private final JavacFileReferencesRegistrar[] myFullASTListeners;
77     private final JavacFileReferencesRegistrar[] myOnlyImportsListeners;
78     private final JavacTreeRefScanner myAstScanner;
79
80     private int myCurrentSize;
81     private Name myAsteriks;
82
83     public MyTaskListener(JavacFileReferencesRegistrar[] fullASTListenerArray, JavacFileReferencesRegistrar[] importsListenerArray) {
84       myFullASTListeners = fullASTListenerArray;
85       myOnlyImportsListeners = importsListenerArray;
86       myAstScanner = JavacTreeRefScanner.createASTScanner();
87     }
88
89     @Override
90     public void started(TaskEvent e) {
91
92     }
93
94     @Override
95     public void finished(TaskEvent e) {
96       try {
97         if (e.getKind() == TaskEvent.Kind.ANALYZE) {
98           // javac creates event on each processed class not file
99           if (myCurrentCompilationUnit != e.getCompilationUnit()) {
100             myCurrentCompilationUnit = (JCTree.JCCompilationUnit)e.getCompilationUnit();
101             myCurrentSize = myCurrentCompilationUnit.getTypeDecls().size() - 1;
102           }
103           else {
104             myCurrentSize--;
105           }
106
107           if (myCurrentSize == 0) {
108             final JavaFileObject sourceFile = e.getSourceFile();
109             final Set<JavacRefSymbol> symbols = new THashSet<JavacRefSymbol>();
110             scanImports(myCurrentCompilationUnit, symbols);
111             for (JavacFileReferencesRegistrar listener : myOnlyImportsListeners) {
112               listener.registerReferences(sourceFile, symbols);
113             }
114             if (myFullASTListeners.length != 0) {
115               myAstScanner.scan(myCurrentCompilationUnit, new JavacTreeScannerSink() {
116                 @Override
117                 public void sinkReference(JavacRefSymbol ref) {
118                   symbols.add(ref);
119                 }
120
121                 @Override
122                 public void sinkClassDeclaration(Symbol className, Symbol[] supers) {
123                   for (JavacFileReferencesRegistrar listener : myFullASTListeners) {
124                     listener.registerClassDeclaration(className, supers);
125                   }
126                 }
127               });
128               for (JavacFileReferencesRegistrar listener : myFullASTListeners) {
129                 listener.registerReferences(sourceFile, symbols);
130               }
131             }
132           }
133         }
134       }
135       catch (Exception ex) {
136         throw new ClientCodeException(ex);
137       }
138     }
139
140     private Name getAsteriksFromCurrentNameTable(Name tableRepresentative) {
141       if (myAsteriks == null) {
142         myAsteriks = tableRepresentative.table.fromChars(new char[]{'*'}, 0, 1);
143       }
144       return myAsteriks;
145     }
146
147     private void scanImports(JCTree.JCCompilationUnit compilationUnit, Set<JavacRefSymbol> symbols) {
148       for (JCTree.JCImport anImport : compilationUnit.getImports()) {
149         final JCTree.JCFieldAccess id = (JCTree.JCFieldAccess)anImport.getQualifiedIdentifier();
150         final Symbol sym = id.sym;
151         if (sym == null) {
152           final JCTree.JCExpression qExpr = id.getExpression();
153           if (qExpr instanceof JCTree.JCFieldAccess) {
154             final JCTree.JCFieldAccess classImport = (JCTree.JCFieldAccess)qExpr;
155             final Symbol ownerSym = classImport.sym;
156             final Name name = id.getIdentifier();
157             if (name != getAsteriksFromCurrentNameTable(name)) {
158               // member import
159               for (Symbol memberSymbol : ownerSym.members().getElements()) {
160                 if (memberSymbol.getSimpleName() == name) {
161                   symbols.add(new JavacRefSymbol(memberSymbol, Tree.Kind.IMPORT));
162                 }
163               }
164             }
165             collectClassImports(ownerSym, symbols);
166           }
167         } else {
168           // class import
169           collectClassImports(sym, symbols);
170         }
171       }
172     }
173   }
174
175   private static void collectClassImports(Symbol baseImport, Set<JavacRefSymbol> collector) {
176     for (Symbol symbol = baseImport;
177          symbol != null && symbol.getKind() != ElementKind.PACKAGE;
178          symbol = symbol.owner) {
179       collector.add(new JavacRefSymbol(symbol, Tree.Kind.IMPORT));
180     }
181   }
182 }