cache cls navigation elements that are not so cheap to calculate each time
[idea/community.git] / java / java-psi-impl / src / com / intellij / psi / impl / compiled / ClsFileImpl.java
1 /*
2  * Copyright 2000-2013 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.psi.impl.compiled;
17
18 import com.intellij.ide.caches.FileContent;
19 import com.intellij.ide.highlighter.JavaClassFileType;
20 import com.intellij.ide.highlighter.JavaFileType;
21 import com.intellij.lang.ASTNode;
22 import com.intellij.lang.FileASTNode;
23 import com.intellij.lang.java.JavaLanguage;
24 import com.intellij.openapi.application.ApplicationManager;
25 import com.intellij.openapi.diagnostic.Logger;
26 import com.intellij.openapi.extensions.Extensions;
27 import com.intellij.openapi.fileTypes.FileType;
28 import com.intellij.openapi.progress.NonCancelableSection;
29 import com.intellij.openapi.progress.ProgressIndicatorProvider;
30 import com.intellij.openapi.roots.FileIndexFacade;
31 import com.intellij.openapi.ui.Queryable;
32 import com.intellij.openapi.vfs.VirtualFile;
33 import com.intellij.pom.java.LanguageLevel;
34 import com.intellij.psi.*;
35 import com.intellij.psi.impl.JavaPsiImplementationHelper;
36 import com.intellij.psi.impl.PsiFileEx;
37 import com.intellij.psi.impl.PsiManagerEx;
38 import com.intellij.psi.impl.java.stubs.PsiClassStub;
39 import com.intellij.psi.impl.java.stubs.impl.PsiJavaFileStubImpl;
40 import com.intellij.psi.impl.source.PsiFileImpl;
41 import com.intellij.psi.impl.source.PsiFileWithStubSupport;
42 import com.intellij.psi.impl.source.SourceTreeToPsiMap;
43 import com.intellij.psi.impl.source.resolve.FileContextUtil;
44 import com.intellij.psi.impl.source.tree.JavaElementType;
45 import com.intellij.psi.impl.source.tree.TreeElement;
46 import com.intellij.psi.search.PsiElementProcessor;
47 import com.intellij.psi.stubs.*;
48 import com.intellij.psi.util.CachedValueProvider;
49 import com.intellij.psi.util.CachedValuesManager;
50 import com.intellij.psi.util.PsiUtil;
51 import com.intellij.reference.SoftReference;
52 import com.intellij.util.ArrayUtil;
53 import com.intellij.util.IncorrectOperationException;
54 import org.jetbrains.annotations.NonNls;
55 import org.jetbrains.annotations.NotNull;
56 import org.jetbrains.annotations.Nullable;
57
58 import java.util.Collections;
59 import java.util.List;
60 import java.util.Map;
61 import java.util.Set;
62
63 import static com.intellij.reference.SoftReference.dereference;
64
65 public class ClsFileImpl extends ClsRepositoryPsiElement<PsiClassHolderFileStub>
66                          implements PsiJavaFile, PsiFileWithStubSupport, PsiFileEx, Queryable, PsiClassOwnerEx, PsiCompiledFile {
67   private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.compiled.ClsFileImpl");
68
69   /** NOTE: you absolutely MUST NOT hold PsiLock under the mirror lock */
70   private final Object myMirrorLock = new Object();
71   private final Object myStubLock = new Object();
72
73   private final PsiManager myManager;
74   private final boolean myIsForDecompiling;
75   private final FileViewProvider myViewProvider;
76   private volatile SoftReference<StubTree> myStub;
77   private volatile TreeElement myMirrorFileElement;
78   private volatile ClsPackageStatementImpl myPackageStatement = null;
79   private boolean myIsPhysical = true;
80
81   private ClsFileImpl(@NotNull PsiManager manager, @NotNull FileViewProvider viewProvider, boolean forDecompiling) {
82     //noinspection ConstantConditions
83     super(null);
84     myManager = manager;
85     myIsForDecompiling = forDecompiling;
86     myViewProvider = viewProvider;
87     JavaElementType.CLASS.getIndex();  // initialize Java stubs
88   }
89
90   public ClsFileImpl(PsiManager manager, FileViewProvider viewProvider) {
91     this(manager, viewProvider, false);
92   }
93
94   @Override
95   public PsiManager getManager() {
96     return myManager;
97   }
98
99   @Override
100   @NotNull
101   public VirtualFile getVirtualFile() {
102     return myViewProvider.getVirtualFile();
103   }
104
105   @Override
106   public boolean processChildren(final PsiElementProcessor<PsiFileSystemItem> processor) {
107     return true;
108   }
109
110   @Override
111   public PsiDirectory getParent() {
112     return getContainingDirectory();
113   }
114
115   @Override
116   public PsiDirectory getContainingDirectory() {
117     VirtualFile parentFile = getVirtualFile().getParent();
118     if (parentFile == null) return null;
119     return getManager().findDirectory(parentFile);
120   }
121
122   @Override
123   public PsiFile getContainingFile() {
124     if (!isValid()) throw new PsiInvalidElementAccessException(this);
125     return this;
126   }
127
128   @Override
129   public boolean isValid() {
130     if (myIsForDecompiling) return true;
131     VirtualFile vFile = getVirtualFile();
132     return vFile.isValid();
133   }
134
135   @Override
136   @NotNull
137   public String getName() {
138     return getVirtualFile().getName();
139   }
140
141   @Override
142   @NotNull
143   public PsiElement[] getChildren() {
144     return getClasses(); // TODO : package statement?
145   }
146
147   @Override
148   @NotNull
149   public PsiClass[] getClasses() {
150     return getStub().getClasses();
151   }
152
153   @Override
154   public PsiPackageStatement getPackageStatement() {
155     getStub(); // Make sure myPackageStatement initializes.
156
157     ClsPackageStatementImpl statement = myPackageStatement;
158     if (statement == null) statement = new ClsPackageStatementImpl(this);
159     return statement.getPackageName() != null ? statement : null;
160   }
161
162   @Override
163   @NotNull
164   public String getPackageName() {
165     PsiPackageStatement statement = getPackageStatement();
166     return statement == null ? "" : statement.getPackageName();
167   }
168
169   @Override
170   public void setPackageName(final String packageName) throws IncorrectOperationException {
171     throw new IncorrectOperationException("Cannot set package name for compiled files");
172   }
173
174   @Override
175   public PsiImportList getImportList() {
176     return null;
177   }
178
179   @Override
180   public boolean importClass(PsiClass aClass) {
181     throw new UnsupportedOperationException("Cannot add imports to compiled classes");
182   }
183
184   @Override
185   @NotNull
186   public PsiElement[] getOnDemandImports(boolean includeImplicit, boolean checkIncludes) {
187     return PsiJavaCodeReferenceElement.EMPTY_ARRAY;
188   }
189
190   @Override
191   @NotNull
192   public PsiClass[] getSingleClassImports(boolean checkIncludes) {
193     return PsiClass.EMPTY_ARRAY;
194   }
195
196   @Override
197   @NotNull
198   public String[] getImplicitlyImportedPackages() {
199     return ArrayUtil.EMPTY_STRING_ARRAY;
200   }
201
202   @Override
203   public Set<String> getClassNames() {
204     return Collections.singleton(getVirtualFile().getNameWithoutExtension());
205   }
206
207   @Override
208   @NotNull
209   public PsiJavaCodeReferenceElement[] getImplicitlyImportedPackageReferences() {
210     return PsiJavaCodeReferenceElement.EMPTY_ARRAY;
211   }
212
213   @Override
214   public PsiJavaCodeReferenceElement findImportReferenceTo(PsiClass aClass) {
215     return null;
216   }
217
218   @Override
219   @NotNull
220   public LanguageLevel getLanguageLevel() {
221     List stubs = getStub().getChildrenStubs();
222     return !stubs.isEmpty() ? ((PsiClassStub<?>)stubs.get(0)).getLanguageLevel() : LanguageLevel.HIGHEST;
223   }
224
225   @Override
226   public PsiElement setName(@NotNull String name) throws IncorrectOperationException {
227     throw new IncorrectOperationException(CAN_NOT_MODIFY_MESSAGE);
228   }
229
230   @Override
231   public void checkSetName(String name) throws IncorrectOperationException {
232     throw new IncorrectOperationException(CAN_NOT_MODIFY_MESSAGE);
233   }
234
235   @Override
236   public boolean isDirectory() {
237     return false;
238   }
239
240   @Override
241   public void appendMirrorText(final int indentLevel, @NotNull final StringBuilder buffer) {
242     buffer.append("\n");
243     buffer.append("  // IntelliJ API Decompiler stub source generated from a class file\n");
244     buffer.append("  // Implementation of methods is not available\n");
245     buffer.append("\n");
246
247     appendText(getPackageStatement(), 0, buffer, "\n\n");
248
249     PsiClass[] classes = getClasses();
250     if (classes.length > 0) {
251       appendText(classes[0], 0, buffer);
252     }
253   }
254
255   @Override
256   public void setMirror(@NotNull TreeElement element) throws InvalidMirrorException {
257     PsiElement mirrorElement = SourceTreeToPsiMap.treeToPsiNotNull(element);
258     if (!(mirrorElement instanceof PsiJavaFile)) {
259       throw new InvalidMirrorException("Unexpected mirror file: " + mirrorElement);
260     }
261
262     PsiJavaFile mirrorFile = (PsiJavaFile)mirrorElement;
263     setMirrorIfPresent(getPackageStatement(), mirrorFile.getPackageStatement());
264     setMirrors(getClasses(), mirrorFile.getClasses());
265   }
266
267   @Override
268   @NotNull
269   public PsiElement getNavigationElement() {
270     return CachedValuesManager.getCachedValue(this, new CachedValueProvider<PsiElement>() {
271       @Nullable
272       @Override
273       public Result<PsiElement> compute() {
274         return Result.create(calcNavigationElement(), ClsFileImpl.this, FileIndexFacade.getInstance(getProject()).getRootModificationTracker());
275       }
276     });
277   }
278
279   private PsiElement calcNavigationElement() {
280     for (ClsCustomNavigationPolicy customNavigationPolicy : Extensions.getExtensions(ClsCustomNavigationPolicy.EP_NAME)) {
281       if (customNavigationPolicy instanceof ClsCustomNavigationPolicyEx) {
282         PsiFile navigationElement = ((ClsCustomNavigationPolicyEx)customNavigationPolicy).getFileNavigationElement(this);
283         if (navigationElement != null) {
284           return navigationElement;
285         }
286       }
287     }
288
289     return JavaPsiImplementationHelper.getInstance(getProject()).getClsFileNavigationElement(this);
290   }
291
292   @Override
293   public PsiElement getMirror() {
294     TreeElement mirrorTreeElement = myMirrorFileElement;
295     if (mirrorTreeElement == null) {
296       synchronized (myMirrorLock) {
297         mirrorTreeElement = myMirrorFileElement;
298         if (mirrorTreeElement == null) {
299           VirtualFile file = getVirtualFile();
300           String mirrorText = decompile(getManager(), file);
301
302           String ext = JavaFileType.INSTANCE.getDefaultExtension();
303           PsiClass[] classes = getClasses();
304           String fileName = (classes.length > 0 ? classes[0].getName() : file.getNameWithoutExtension()) + "." + ext;
305           PsiFileFactory factory = PsiFileFactory.getInstance(getManager().getProject());
306           PsiFile mirror = factory.createFileFromText(fileName, JavaLanguage.INSTANCE, mirrorText, false, false);
307           mirror.putUserData(PsiUtil.FILE_LANGUAGE_LEVEL_KEY, getLanguageLevel());
308           mirrorTreeElement = SourceTreeToPsiMap.psiToTreeNotNull(mirror);
309
310           // IMPORTANT: do not take lock too early - FileDocumentManager.saveToString() can run write action
311           NonCancelableSection section = ProgressIndicatorProvider.startNonCancelableSectionIfSupported();
312           try {
313             setMirror(mirrorTreeElement);
314           }
315           catch (InvalidMirrorException e) {
316             LOG.error(file.getPath(), e);
317           }
318           finally {
319             section.done();
320           }
321
322           myMirrorFileElement = mirrorTreeElement;
323         }
324       }
325     }
326     return mirrorTreeElement.getPsi();
327   }
328
329   @Override
330   public PsiFile getDecompiledPsiFile() {
331     for (ClsFileDecompiledPsiFileProvider provider : Extensions.getExtensions(ClsFileDecompiledPsiFileProvider.EP_NAME)) {
332       PsiFile decompiledPsiFile = provider.getDecompiledPsiFile(this);
333       if (decompiledPsiFile != null) {
334         return decompiledPsiFile;
335       }
336     }
337     return (PsiFile) getMirror();
338   }
339
340   @Override
341   public long getModificationStamp() {
342     return getVirtualFile().getModificationStamp();
343   }
344
345   @Override
346   public void accept(@NotNull PsiElementVisitor visitor) {
347     if (visitor instanceof JavaElementVisitor) {
348       ((JavaElementVisitor)visitor).visitJavaFile(this);
349     } else {
350       visitor.visitFile(this);
351     }
352   }
353
354   @NonNls
355   public String toString() {
356     return "PsiFile:" + getName();
357   }
358
359   @Override
360   @NotNull
361   public PsiFile getOriginalFile() {
362     return this;
363   }
364
365   @Override
366   @NotNull
367   public FileType getFileType() {
368     return JavaClassFileType.INSTANCE;
369   }
370
371   @Override
372   @NotNull
373   public PsiFile[] getPsiRoots() {
374     return new PsiFile[]{this};
375   }
376
377   @Override
378   @NotNull
379   public FileViewProvider getViewProvider() {
380     return myViewProvider;
381   }
382
383   @Override
384   public void subtreeChanged() {
385   }
386
387   public static String decompile(PsiManager manager, VirtualFile file) {
388     ClsFileImpl psiFile = null;
389
390     final FileViewProvider provider = ((PsiManagerEx)manager).getFileManager().findViewProvider(file);
391     if (provider != null) {
392       final PsiFile psi = provider.getPsi(provider.getBaseLanguage());
393       if (psi instanceof ClsFileImpl) {
394         psiFile = (ClsFileImpl)psi;
395       }
396     }
397
398     if (psiFile == null) {
399       psiFile = new ClsFileImpl(manager, new ClassFileViewProvider(manager, file), true);
400     }
401
402     final StringBuilder buffer = new StringBuilder();
403     psiFile.appendMirrorText(0, buffer);
404     return buffer.toString();
405   }
406
407   @Override
408   public PsiElement getContext() {
409     return FileContextUtil.getFileContext(this);
410   }
411
412   @Override
413   @NotNull
414   public PsiClassHolderFileStub getStub() {
415     return (PsiClassHolderFileStub)getStubTree().getRoot();
416   }
417
418   @Override
419   @NotNull
420   public StubTree getStubTree() {
421     ApplicationManager.getApplication().assertReadAccessAllowed();
422
423     StubTree stubTree = dereference(myStub);
424     if (stubTree != null) return stubTree;
425
426     // build newStub out of lock to avoid deadlock
427     StubTree newStubTree = (StubTree)StubTreeLoader.getInstance().readOrBuild(getProject(), getVirtualFile(), this);
428     if (newStubTree == null) {
429       LOG.warn("No stub for class file in index: " + getVirtualFile().getPresentableUrl());
430       newStubTree = new StubTree(new PsiJavaFileStubImpl("corrupted.classfiles", true));
431     }
432
433     synchronized (myStubLock) {
434       stubTree = dereference(myStub);
435       if (stubTree != null) return stubTree;
436
437       stubTree = newStubTree;
438
439       //noinspection unchecked
440       ((PsiFileStubImpl)stubTree.getRoot()).setPsi(this);
441
442       myStub = new SoftReference<StubTree>(stubTree);
443     }
444
445     return stubTree;
446   }
447
448   @Override
449   public ASTNode findTreeForStub(final StubTree tree, final StubElement<?> stub) {
450     return null;
451   }
452
453   @Override
454   public boolean isContentsLoaded() {
455     return myStub != null;
456   }
457
458   @Override
459   public void onContentReload() {
460     ApplicationManager.getApplication().assertWriteAccessAllowed();
461
462     synchronized (myStubLock) {
463       StubTree stubTree = dereference(myStub);
464       myStub = null;
465       if (stubTree != null) {
466         //noinspection unchecked
467         ((PsiFileStubImpl)stubTree.getRoot()).clearPsi("cls onContentReload");
468       }
469     }
470
471     ClsPackageStatementImpl packageStatement = new ClsPackageStatementImpl(this);
472     synchronized (myMirrorLock) {
473       myMirrorFileElement = null;
474       myPackageStatement = packageStatement;
475     }
476   }
477
478   @Override
479   public PsiFile cacheCopy(final FileContent content) {
480     return this;
481   }
482
483   @Override
484   public void putInfo(@NotNull Map<String, String> info) {
485     PsiFileImpl.putInfo(this, info);
486   }
487
488   @Override
489   public FileASTNode getNode() {
490     return null;
491   }
492
493   @Override
494   public boolean isPhysical() {
495     return myIsPhysical;
496   }
497
498   @SuppressWarnings("UnusedDeclaration")  // used by Kotlin compiler
499   public void setPhysical(boolean isPhysical) {
500     myIsPhysical = isPhysical;
501   }
502 }