bind file element even if refused to bind classes
[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       // Can happen for package-info.class, or classes compiled from languages, that support different class naming scheme, like Scala.
229       if (classes.length != 1 || JavaPsiFacade.getInstance(getProject()).getNameHelper().isIdentifier(classes[0].getName())) {
230         PsiClass[] mirrorClasses = ((PsiJavaFile)mirrorFile).getClasses();
231         LOG.assertTrue(classes.length == mirrorClasses.length);
232         if (classes.length == mirrorClasses.length) {
233           for (int i = 0; i < classes.length; i++) {
234             ((ClsElementImpl)classes[i]).setMirror((TreeElement)SourceTreeToPsiMap.psiElementToTree(mirrorClasses[i]));
235           }
236         }
237       }
238     }
239     myMirrorFileElement = element;
240   }
241
242   @NotNull
243   public PsiElement getNavigationElement() {
244     String packageName = getPackageName();
245     PsiClass[] classes = getClasses();
246     if (classes.length == 0) return this;
247     String sourceFileName = ((ClsClassImpl)classes[0]).getSourceFileName();
248     String relativeFilePath = packageName.length() == 0 ? sourceFileName : packageName.replace('.', '/') + '/' + sourceFileName;
249
250     final VirtualFile vFile = getContainingFile().getVirtualFile();
251     ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(getProject()).getFileIndex();
252     final List<OrderEntry> orderEntries = projectFileIndex.getOrderEntriesForFile(vFile);
253     for (OrderEntry orderEntry : orderEntries) {
254       VirtualFile[] files = orderEntry.getFiles(OrderRootType.SOURCES);
255       for (VirtualFile file : files) {
256         VirtualFile source = file.findFileByRelativePath(relativeFilePath);
257         if (source != null) {
258           PsiFile psiSource = getManager().findFile(source);
259           if (psiSource instanceof PsiClassOwner) {
260             return psiSource;
261           }
262         }
263       }
264     }
265     return this;
266   }
267
268   @Override
269   public PsiElement getMirror() {
270     synchronized (MIRROR_LOCK) {
271       if (myMirrorFileElement == null) {
272         FileDocumentManager documentManager = FileDocumentManager.getInstance();
273         final Document document = documentManager.getDocument(getVirtualFile());
274         String text = document.getText();
275         String ext = StdFileTypes.JAVA.getDefaultExtension();
276         PsiClass aClass = getClasses()[0];
277         String fileName = aClass.getName() + "." + ext;
278         PsiManager manager = getManager();
279         PsiFile mirror = PsiFileFactory.getInstance(manager.getProject()).createFileFromText(fileName, text);
280         final ASTNode mirrorTreeElement = SourceTreeToPsiMap.psiElementToTree(mirror);
281
282         //IMPORTANT: do not take lock too early - FileDocumentManager.getInstance().saveToString() can run write action...
283         ProgressManager.getInstance().executeNonCancelableSection(new Runnable() {
284           public void run() {
285             setMirror((TreeElement)mirrorTreeElement);
286             myMirrorFileElement.putUserData(DOCUMENT_IN_MIRROR_KEY, document);
287           }
288         });
289       }
290
291       return myMirrorFileElement.getPsi();
292     }
293   }
294
295   public long getModificationStamp() {
296     return getVirtualFile().getModificationStamp();
297   }
298
299   public void accept(@NotNull PsiElementVisitor visitor) {
300     if (visitor instanceof JavaElementVisitor) {
301       ((JavaElementVisitor)visitor).visitJavaFile(this);
302     } else {
303       visitor.visitFile(this);
304     }
305   }
306
307   @NonNls
308   public String toString() {
309     return "PsiFile:" + getName();
310   }
311
312   @NotNull
313   public PsiFile getOriginalFile() {
314     return this;
315   }
316
317   @NotNull
318   public FileType getFileType() {
319     return StdFileTypes.CLASS;
320   }
321
322   @NotNull
323   public PsiFile[] getPsiRoots() {
324     return new PsiFile[]{this};
325   }
326
327   @NotNull
328   public FileViewProvider getViewProvider() {
329     return myViewProvider;
330   }
331
332   public void subtreeChanged() {
333   }
334
335   public static String decompile(PsiManager manager, VirtualFile file) {
336     final FileViewProvider provider = ((PsiManagerEx)manager).getFileManager().findViewProvider(file);
337     ClsFileImpl psiFile = null;
338     if (provider != null) {
339       final PsiFile psi = provider.getPsi(provider.getBaseLanguage());
340       if (psi instanceof ClsFileImpl) {
341         psiFile = (ClsFileImpl)psi;
342       }
343     }
344
345     if (psiFile == null) {
346       psiFile = new ClsFileImpl((PsiManagerImpl)manager, new ClassFileViewProvider(manager, file), true);
347     }
348
349     StringBuffer buffer = new StringBuffer();
350     psiFile.appendMirrorText(0, buffer);
351     return buffer.toString();
352   }
353
354   @Override
355   public PsiElement getContext() {
356     return FileContextUtil.getFileContext(this);
357   }
358
359   @NotNull
360   public PsiClassHolderFileStub getStub() {
361     return (PsiClassHolderFileStub)getStubTree().getRoot();
362   }
363
364   private final Object lock = new Object();
365
366   @NotNull
367   public StubTree getStubTree() {
368     ApplicationManager.getApplication().assertReadAccessAllowed();
369
370     final StubTree derefd = derefStub();
371     if (derefd != null) return derefd;
372
373     StubTree stubHolder = StubTree.readOrBuild(getProject(), getVirtualFile());
374     if (stubHolder == null) {
375       // Must be corrupted classfile
376       LOG.info("Class file is corrupted: " + getVirtualFile().getPresentableUrl());
377
378       StubTree emptyTree = new StubTree(new PsiJavaFileStubImpl("corrupted.classfiles", true));
379       setStubTree(emptyTree);
380       resetMirror();
381       return emptyTree;
382     }
383
384     synchronized (lock) {
385       final StubTree derefdOnLock = derefStub();
386       if (derefdOnLock != null) return derefdOnLock;
387
388       setStubTree(stubHolder);
389     }
390
391     resetMirror();
392     return stubHolder;
393   }
394
395   private void setStubTree(StubTree tree) {
396     synchronized (lock) {
397       myStub = new SoftReference<StubTree>(tree);
398       ((PsiFileStubImpl)tree.getRoot()).setPsi(this);
399     }
400   }
401
402   private void resetMirror() {
403     synchronized (MIRROR_LOCK) {
404       myMirrorFileElement = null;
405       myPackageStatement = new ClsPackageStatementImpl(this);
406     }
407   }
408
409   @Nullable
410   private StubTree derefStub() {
411     synchronized (lock) {
412       return myStub != null ? myStub.get() : null;
413     }
414   }
415
416   public ASTNode findTreeForStub(final StubTree tree, final StubElement<?> stub) {
417     return null;
418   }
419
420   public boolean isContentsLoaded() {
421     return myStub != null;
422   }
423
424   public void onContentReload() {
425     SoftReference<StubTree> stub = myStub;
426     StubTree stubHolder = stub == null ? null : stub.get();
427     if (stubHolder != null) {
428       ((StubBase<?>)stubHolder.getRoot()).setPsi(null);
429     }
430     myStub = null;
431
432     ApplicationManager.getApplication().assertWriteAccessAllowed();
433
434     synchronized (MIRROR_LOCK) {
435       myMirrorFileElement = null;
436       myPackageStatement = null;
437     }
438   }
439
440   public PsiFile cacheCopy(final FileContent content) {
441     return this;
442   }
443 }