360d33225dd1c6cf784392655d2346ddd9879ee6
[idea/community.git] / java / java-impl / src / com / intellij / psi / impl / compiled / ClsFileImpl.java
1 /*
2  * Copyright 2000-2009 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.lang.ASTNode;
20 import com.intellij.openapi.application.ApplicationManager;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.editor.Document;
23 import com.intellij.openapi.fileEditor.FileDocumentManager;
24 import com.intellij.openapi.fileTypes.FileType;
25 import com.intellij.openapi.fileTypes.StdFileTypes;
26 import com.intellij.openapi.progress.ProgressManager;
27 import com.intellij.openapi.roots.OrderEntry;
28 import com.intellij.openapi.roots.OrderRootType;
29 import com.intellij.openapi.roots.ProjectFileIndex;
30 import com.intellij.openapi.roots.ProjectRootManager;
31 import com.intellij.openapi.util.Key;
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.PsiFileEx;
36 import com.intellij.psi.impl.PsiManagerEx;
37 import com.intellij.psi.impl.PsiManagerImpl;
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.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.util.ArrayUtil;
48 import com.intellij.util.IncorrectOperationException;
49 import org.jetbrains.annotations.NonNls;
50 import org.jetbrains.annotations.NotNull;
51 import org.jetbrains.annotations.Nullable;
52
53 import java.lang.ref.SoftReference;
54 import java.util.List;
55
56 public class ClsFileImpl extends ClsRepositoryPsiElement<PsiClassHolderFileStub> implements PsiJavaFile, PsiFileWithStubSupport, PsiFileEx {
57   private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.compiled.ClsFileImpl");
58
59   static final Object MIRROR_LOCK = new String("Mirror Lock");
60
61   private static final Key<Document> DOCUMENT_IN_MIRROR_KEY = Key.create("DOCUMENT_IN_MIRROR_KEY");
62   private final PsiManagerImpl myManager;
63   private final boolean myIsForDecompiling;
64   private final FileViewProvider myViewProvider;
65   private volatile SoftReference<StubTree> myStub;
66   private TreeElement myMirrorFileElement;
67   private volatile ClsPackageStatementImpl myPackageStatement = null;
68
69   private ClsFileImpl(@NotNull PsiManagerImpl manager, @NotNull FileViewProvider viewProvider, boolean forDecompiling) {
70     super(null);
71     myManager = manager;
72     JavaElementType.CLASS.getIndex(); // Initialize java stubs...
73
74     myIsForDecompiling = forDecompiling;
75     myViewProvider = viewProvider;
76   }
77
78   public ClsFileImpl(PsiManagerImpl manager, FileViewProvider viewProvider) {
79     this(manager, viewProvider, false);
80   }
81
82   public PsiManager getManager() {
83     return myManager;
84   }
85
86   @NotNull
87   public VirtualFile getVirtualFile() {
88     return myViewProvider.getVirtualFile();
89   }
90
91   public boolean processChildren(final PsiElementProcessor<PsiFileSystemItem> processor) {
92     return true;
93   }
94
95   public PsiDirectory getParent() {
96     return getContainingDirectory();
97   }
98
99   public PsiDirectory getContainingDirectory() {
100     VirtualFile parentFile = getVirtualFile().getParent();
101     if (parentFile == null) return null;
102     return getManager().findDirectory(parentFile);
103   }
104
105   public PsiFile getContainingFile() {
106     if (!isValid()) throw new PsiInvalidElementAccessException(this);
107     return this;
108   }
109
110   public boolean isValid() {
111     if (myIsForDecompiling) return true;
112     VirtualFile vFile = getVirtualFile();
113     return vFile.isValid();
114   }
115
116   @NotNull
117   public String getName() {
118     return getVirtualFile().getName();
119   }
120
121   @NotNull
122   public PsiElement[] getChildren() {
123     return getClasses(); // TODO : package statement?
124   }
125
126   @NotNull
127   public PsiClass[] getClasses() {
128     final PsiClassHolderFileStub fileStub = getStub();
129     return fileStub != null ? fileStub.getClasses() : PsiClass.EMPTY_ARRAY;
130   }
131
132   public PsiPackageStatement getPackageStatement() {
133     getStub(); // Make sure myPackageStatement initializes.
134
135     ClsPackageStatementImpl statement = myPackageStatement;
136     if (statement == null) statement = new ClsPackageStatementImpl(this);
137     return statement.getPackageName() != null ? statement : null;
138   }
139
140   @NotNull
141   public String getPackageName() {
142     PsiPackageStatement statement = getPackageStatement();
143     return statement == null ? "" : statement.getPackageName();
144   }
145
146   public void setPackageName(final String packageName) throws IncorrectOperationException {
147     throw new IncorrectOperationException("Cannot set package name for compiled files");
148   }
149
150   public PsiImportList getImportList() {
151     return null;
152   }
153
154   public boolean importClass(PsiClass aClass) {
155     throw new UnsupportedOperationException("Cannot add imports to compiled classes");
156   }
157
158   @NotNull
159   public PsiElement[] getOnDemandImports(boolean includeImplicit, boolean checkIncludes) {
160     return PsiJavaCodeReferenceElement.EMPTY_ARRAY;
161   }
162
163   @NotNull
164   public PsiClass[] getSingleClassImports(boolean checkIncludes) {
165     return PsiClass.EMPTY_ARRAY;
166   }
167
168   @NotNull
169   public String[] getImplicitlyImportedPackages() {
170     return ArrayUtil.EMPTY_STRING_ARRAY;
171   }
172
173   @NotNull
174   public PsiJavaCodeReferenceElement[] getImplicitlyImportedPackageReferences() {
175     return PsiJavaCodeReferenceElement.EMPTY_ARRAY;
176   }
177
178   public PsiJavaCodeReferenceElement findImportReferenceTo(PsiClass aClass) {
179     return null;
180   }
181
182   @NotNull
183   public LanguageLevel getLanguageLevel() {
184     final List stubs = getStub().getChildrenStubs();
185     return stubs.size() > 0 ? ((PsiClassStub<?>)stubs.get(0)).getLanguageLevel() : LanguageLevel.HIGHEST;
186   }
187
188   public PsiElement setName(@NotNull String name) throws IncorrectOperationException {
189     throw new IncorrectOperationException(CAN_NOT_MODIFY_MESSAGE);
190   }
191
192   public void checkSetName(String name) throws IncorrectOperationException {
193     throw new IncorrectOperationException(CAN_NOT_MODIFY_MESSAGE);
194   }
195
196   public boolean isDirectory() {
197     return false;
198   }
199
200   public void appendMirrorText(final int indentLevel, final StringBuffer buffer) {
201     buffer.append(PsiBundle.message("psi.decompiled.text.header"));
202     goNextLine(indentLevel, buffer);
203     goNextLine(indentLevel, buffer);
204     final PsiPackageStatement packageStatement = getPackageStatement();
205     if (packageStatement != null) {
206       ((ClsElementImpl)packageStatement).appendMirrorText(0, buffer);
207       goNextLine(indentLevel, buffer);
208       goNextLine(indentLevel, buffer);
209     }
210
211     final PsiClass[] classes = getClasses();
212     if (classes.length > 0) {
213       PsiClass aClass = classes[0];
214       ((ClsElementImpl)aClass).appendMirrorText(0, buffer);
215     }
216   }
217
218   public void setMirror(@NotNull TreeElement element) {
219     PsiElement mirrorFile = SourceTreeToPsiMap.treeElementToPsi(element);
220     if (mirrorFile instanceof PsiJavaFile) {
221       PsiPackageStatement packageStatementMirror = ((PsiJavaFile)mirrorFile).getPackageStatement();
222       final PsiPackageStatement packageStatement = getPackageStatement();
223       if (packageStatementMirror != null && packageStatement != null) {
224         ((ClsElementImpl)packageStatement).setMirror((TreeElement)SourceTreeToPsiMap.psiElementToTree(packageStatementMirror));
225       }
226
227       PsiClass[] classes = getClasses();
228       if (classes.length == 1) {
229         if (!JavaPsiFacade.getInstance(getProject()).getNameHelper().isIdentifier(classes[0].getName())) {
230           return; // Can happen for package-info.class, or classes compiled from languages, that support different class naming scheme, like Scala.
231         }
232       }
233
234       PsiClass[] mirrorClasses = ((PsiJavaFile)mirrorFile).getClasses();
235       LOG.assertTrue(classes.length == mirrorClasses.length);
236       if (classes.length == mirrorClasses.length) {
237         for (int i = 0; i < classes.length; i++) {
238           ((ClsElementImpl)classes[i]).setMirror((TreeElement)SourceTreeToPsiMap.psiElementToTree(mirrorClasses[i]));
239         }
240       }
241     }
242     myMirrorFileElement = element;
243   }
244
245   @NotNull
246   public PsiElement getNavigationElement() {
247     String packageName = getPackageName();
248     PsiClass[] classes = getClasses();
249     if (classes.length == 0) return this;
250     String sourceFileName = ((ClsClassImpl)classes[0]).getSourceFileName();
251     String relativeFilePath = packageName.length() == 0 ? sourceFileName : packageName.replace('.', '/') + '/' + sourceFileName;
252
253     final VirtualFile vFile = getContainingFile().getVirtualFile();
254     ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(getProject()).getFileIndex();
255     final List<OrderEntry> orderEntries = projectFileIndex.getOrderEntriesForFile(vFile);
256     for (OrderEntry orderEntry : orderEntries) {
257       VirtualFile[] files = orderEntry.getFiles(OrderRootType.SOURCES);
258       for (VirtualFile file : files) {
259         VirtualFile source = file.findFileByRelativePath(relativeFilePath);
260         if (source != null) {
261           PsiFile psiSource = getManager().findFile(source);
262           if (psiSource instanceof PsiClassOwner) {
263             return psiSource;
264           }
265         }
266       }
267     }
268     return this;
269   }
270
271   @Override
272   public PsiElement getMirror() {
273     synchronized (MIRROR_LOCK) {
274       if (myMirrorFileElement == null) {
275         FileDocumentManager documentManager = FileDocumentManager.getInstance();
276         final Document document = documentManager.getDocument(getVirtualFile());
277         String text = document.getText();
278         String ext = StdFileTypes.JAVA.getDefaultExtension();
279         PsiClass aClass = getClasses()[0];
280         String fileName = aClass.getName() + "." + ext;
281         PsiManager manager = getManager();
282         PsiFile mirror = PsiFileFactory.getInstance(manager.getProject()).createFileFromText(fileName, text);
283         final ASTNode mirrorTreeElement = SourceTreeToPsiMap.psiElementToTree(mirror);
284
285         //IMPORTANT: do not take lock too early - FileDocumentManager.getInstance().saveToString() can run write action...
286         ProgressManager.getInstance().executeNonCancelableSection(new Runnable() {
287           public void run() {
288             setMirror((TreeElement)mirrorTreeElement);
289             myMirrorFileElement.putUserData(DOCUMENT_IN_MIRROR_KEY, document);
290           }
291         });
292       }
293
294       return myMirrorFileElement.getPsi();
295     }
296   }
297
298   public long getModificationStamp() {
299     return getVirtualFile().getModificationStamp();
300   }
301
302   public void accept(@NotNull PsiElementVisitor visitor) {
303     if (visitor instanceof JavaElementVisitor) {
304       ((JavaElementVisitor)visitor).visitJavaFile(this);
305     } else {
306       visitor.visitFile(this);
307     }
308   }
309
310   @NonNls
311   public String toString() {
312     return "PsiFile:" + getName();
313   }
314
315   @NotNull
316   public PsiFile getOriginalFile() {
317     return this;
318   }
319
320   @NotNull
321   public FileType getFileType() {
322     return StdFileTypes.CLASS;
323   }
324
325   @NotNull
326   public PsiFile[] getPsiRoots() {
327     return new PsiFile[]{this};
328   }
329
330   @NotNull
331   public FileViewProvider getViewProvider() {
332     return myViewProvider;
333   }
334
335   public void subtreeChanged() {
336   }
337
338   public static String decompile(PsiManager manager, VirtualFile file) {
339     final FileViewProvider provider = ((PsiManagerEx)manager).getFileManager().findViewProvider(file);
340     ClsFileImpl psiFile = null;
341     if (provider != null) {
342       final PsiFile psi = provider.getPsi(provider.getBaseLanguage());
343       if (psi instanceof ClsFileImpl) {
344         psiFile = (ClsFileImpl)psi;
345       }
346     }
347
348     if (psiFile == null) {
349       psiFile = new ClsFileImpl((PsiManagerImpl)manager, new ClassFileViewProvider(manager, file), true);
350     }
351
352     StringBuffer buffer = new StringBuffer();
353     psiFile.appendMirrorText(0, buffer);
354     return buffer.toString();
355   }
356
357   @Override
358   public PsiElement getContext() {
359     return FileContextUtil.getFileContext(this);
360   }
361
362   @NotNull
363   public PsiClassHolderFileStub getStub() {
364     return (PsiClassHolderFileStub)getStubTree().getRoot();
365   }
366
367   private final Object lock = new Object();
368
369   @NotNull
370   public StubTree getStubTree() {
371     ApplicationManager.getApplication().assertReadAccessAllowed();
372
373     final StubTree derefd = derefStub();
374     if (derefd != null) return derefd;
375
376     StubTree stubHolder = StubTree.readOrBuild(getProject(), getVirtualFile());
377     if (stubHolder == null) {
378       // Must be corrupted classfile
379       LOG.info("Class file is corrupted: " + getVirtualFile().getPresentableUrl());
380
381       StubTree emptyTree = new StubTree(new PsiJavaFileStubImpl("corrupted.classfiles", true));
382       setStubTree(emptyTree);
383       resetMirror();
384       return emptyTree;
385     }
386
387     synchronized (lock) {
388       final StubTree derefdOnLock = derefStub();
389       if (derefdOnLock != null) return derefdOnLock;
390
391       setStubTree(stubHolder);
392     }
393
394     resetMirror();
395     return stubHolder;
396   }
397
398   private void setStubTree(StubTree tree) {
399     synchronized (lock) {
400       myStub = new SoftReference<StubTree>(tree);
401       ((PsiFileStubImpl)tree.getRoot()).setPsi(this);
402     }
403   }
404
405   private void resetMirror() {
406     synchronized (MIRROR_LOCK) {
407       myMirrorFileElement = null;
408       myPackageStatement = new ClsPackageStatementImpl(this);
409     }
410   }
411
412   @Nullable
413   private StubTree derefStub() {
414     synchronized (lock) {
415       return myStub != null ? myStub.get() : null;
416     }
417   }
418
419   public ASTNode findTreeForStub(final StubTree tree, final StubElement<?> stub) {
420     return null;
421   }
422
423   public boolean isContentsLoaded() {
424     return myStub != null;
425   }
426
427   public void onContentReload() {
428     SoftReference<StubTree> stub = myStub;
429     StubTree stubHolder = stub == null ? null : stub.get();
430     if (stubHolder != null) {
431       ((StubBase<?>)stubHolder.getRoot()).setPsi(null);
432     }
433     myStub = null;
434
435     ApplicationManager.getApplication().assertWriteAccessAllowed();
436
437     synchronized (MIRROR_LOCK) {
438       myMirrorFileElement = null;
439       myPackageStatement = null;
440     }
441   }
442
443   public PsiFile cacheCopy(final FileContent content) {
444     return this;
445   }
446 }