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