CompilerReferenceServiceImpl: allow multiple threads processing; do not search inheri...
[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.*;
38
39 public class JavacReferencesCollector {
40   public static void installOn(JavacTask task) {
41     List<JavacFileReferencesRegistrar> fullASTListeners = new SmartList<JavacFileReferencesRegistrar>();
42     List<JavacFileReferencesRegistrar> onlyImportsListeners = new SmartList<JavacFileReferencesRegistrar>();
43     for (JavacFileReferencesRegistrar listener : JpsServiceManager.getInstance().getExtensions(JavacFileReferencesRegistrar.class)) {
44       if (!listener.initialize()) {
45         continue;
46       }
47       (listener.onlyImports() ? onlyImportsListeners : fullASTListeners).add(listener);
48     }
49
50     final JavacFileReferencesRegistrar[] fullASTListenerArray = fullASTListeners.toArray(new JavacFileReferencesRegistrar[fullASTListeners.size()]);
51     final JavacFileReferencesRegistrar[] onlyImportsListenerArray = onlyImportsListeners.toArray(new JavacFileReferencesRegistrar[onlyImportsListeners.size()]);
52     if (fullASTListenerArray.length == 0 && onlyImportsListenerArray.length == 0) {
53       return;
54     }
55
56     Method addTaskMethod = ReflectionUtil.getMethod(JavacTask.class, "addTaskListener", TaskListener.class); // jdk >= 8
57     if (addTaskMethod == null) {
58       addTaskMethod = ReflectionUtil.getMethod(JavacTask.class, "setTaskListener", TaskListener.class); // jdk 6-7
59     }
60     assert addTaskMethod != null;
61
62     try {
63       addTaskMethod.invoke(task, new MyTaskListener(fullASTListenerArray, onlyImportsListenerArray));
64     }
65     catch (IllegalAccessException e) {
66       throw new RuntimeException(e);
67     }
68     catch (InvocationTargetException e) {
69       throw new RuntimeException(e);
70     }
71   }
72
73   private static final class MyTaskListener implements TaskListener {
74     private JCTree.JCCompilationUnit myCurrentCompilationUnit;
75     private final JavacFileReferencesRegistrar[] myFullASTListeners;
76     private final JavacFileReferencesRegistrar[] myOnlyImportsListeners;
77     private final JavacTreeRefScanner myAstScanner;
78
79     private int myCurrentSize;
80     private Name myAsteriks;
81
82     public MyTaskListener(JavacFileReferencesRegistrar[] fullASTListenerArray, JavacFileReferencesRegistrar[] importsListenerArray) {
83       myFullASTListeners = fullASTListenerArray;
84       myOnlyImportsListeners = importsListenerArray;
85       myAstScanner = JavacTreeRefScanner.createASTScanner();
86     }
87
88     @Override
89     public void started(TaskEvent e) {
90
91     }
92
93     @Override
94     public void finished(TaskEvent e) {
95       try {
96         if (e.getKind() == TaskEvent.Kind.ANALYZE) {
97           // javac creates event on each processed class not file
98           if (myCurrentCompilationUnit != e.getCompilationUnit()) {
99             myCurrentCompilationUnit = (JCTree.JCCompilationUnit)e.getCompilationUnit();
100             myCurrentSize = myCurrentCompilationUnit.getTypeDecls().size() - 1;
101           }
102           else {
103             myCurrentSize--;
104           }
105
106           if (myCurrentSize == 0) {
107             final JavaFileObject sourceFile = e.getSourceFile();
108             final Set<JavacRefSymbol> symbols = new THashSet<JavacRefSymbol>();
109             scanImports(myCurrentCompilationUnit, symbols);
110             for (JavacFileReferencesRegistrar listener : myOnlyImportsListeners) {
111               listener.registerFile(sourceFile, symbols, Collections.<JavacRefSymbol>emptySet());
112             }
113             if (myFullASTListeners.length != 0) {
114               final Collection<JavacRefSymbol> defs = new ArrayList<JavacRefSymbol>();
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 sinkDeclaration(JavacRefSymbol def) {
123                   defs.add(def);
124                 }
125               });
126               for (JavacFileReferencesRegistrar listener : myFullASTListeners) {
127                 listener.registerFile(sourceFile, symbols, defs);
128               }
129             }
130           }
131         }
132       }
133       catch (Exception ex) {
134         throw new ClientCodeException(ex);
135       }
136     }
137
138     private Name getAsteriskFromCurrentNameTable(Name tableRepresentative) {
139       if (myAsteriks == null) {
140         myAsteriks = tableRepresentative.table.fromChars(new char[]{'*'}, 0, 1);
141       }
142       return myAsteriks;
143     }
144
145     private void scanImports(JCTree.JCCompilationUnit compilationUnit, Set<JavacRefSymbol> symbols) {
146       for (JCTree.JCImport anImport : compilationUnit.getImports()) {
147         final JCTree.JCFieldAccess id = (JCTree.JCFieldAccess)anImport.getQualifiedIdentifier();
148         final Symbol sym = id.sym;
149         if (sym == null) {
150           final JCTree.JCExpression qExpr = id.getExpression();
151           if (qExpr instanceof JCTree.JCFieldAccess) {
152             final JCTree.JCFieldAccess classImport = (JCTree.JCFieldAccess)qExpr;
153             final Symbol ownerSym = classImport.sym;
154             final Name name = id.getIdentifier();
155             if (name != getAsteriskFromCurrentNameTable(name)) {
156               // member import
157               for (Symbol memberSymbol : ownerSym.members().getElements()) {
158                 if (memberSymbol.getSimpleName() == name) {
159                   symbols.add(new JavacRefSymbol(memberSymbol, Tree.Kind.IMPORT));
160                 }
161               }
162             }
163             collectClassImports(ownerSym, symbols);
164           }
165         } else {
166           // class import
167           collectClassImports(sym, symbols);
168         }
169       }
170     }
171   }
172
173   private static void collectClassImports(Symbol baseImport, Set<JavacRefSymbol> collector) {
174     for (Symbol symbol = baseImport;
175          symbol != null && symbol.getKind() != ElementKind.PACKAGE;
176          symbol = symbol.owner) {
177       collector.add(new JavacRefSymbol(symbol, Tree.Kind.IMPORT));
178     }
179   }
180 }