blob: 20c11a7ccb39289c856efdd775ffa323080a7609 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.psi.impl.compiled;
import com.intellij.diagnostic.PluginException;
import com.intellij.ide.highlighter.JavaClassFileType;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.ide.plugins.PluginManagerCore;
import com.intellij.lang.ASTNode;
import com.intellij.lang.FileASTNode;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.extensions.PluginId;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.DefaultProjectFactory;
import com.intellij.openapi.roots.FileIndexFacade;
import com.intellij.openapi.ui.Queryable;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.ModificationTracker;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.compiled.ClassFileDecompilers;
import com.intellij.psi.impl.JavaPsiImplementationHelper;
import com.intellij.psi.impl.PsiFileEx;
import com.intellij.psi.impl.java.stubs.PsiClassStub;
import com.intellij.psi.impl.java.stubs.PsiJavaFileStub;
import com.intellij.psi.impl.java.stubs.impl.PsiJavaFileStubImpl;
import com.intellij.psi.impl.source.PsiFileImpl;
import com.intellij.psi.impl.source.PsiFileWithStubSupport;
import com.intellij.psi.impl.source.SourceTreeToPsiMap;
import com.intellij.psi.impl.source.resolve.FileContextUtil;
import com.intellij.psi.impl.source.tree.JavaElementType;
import com.intellij.psi.impl.source.tree.TreeElement;
import com.intellij.psi.scope.ElementClassHint;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.search.PsiElementProcessor;
import com.intellij.psi.stubs.*;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiUtil;
import com.intellij.reference.SoftReference;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.cls.ClsFormatException;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.org.objectweb.asm.ClassReader;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.intellij.reference.SoftReference.dereference;
public class ClsFileImpl extends ClsRepositoryPsiElement<PsiClassHolderFileStub>
implements PsiJavaFile, PsiFileWithStubSupport, PsiFileEx, Queryable, PsiClassOwnerEx, PsiCompiledFile {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.compiled.ClsFileImpl");
/** NOTE: you absolutely MUST NOT hold PsiLock under the mirror lock */
private final Object myMirrorLock = new Object();
private final Object myStubLock = new Object();
private final FileViewProvider myViewProvider;
private final boolean myIsForDecompiling;
private volatile SoftReference<StubTree> myStub;
private volatile TreeElement myMirrorFileElement;
private volatile ClsPackageStatementImpl myPackageStatement = null;
private volatile LanguageLevel myLanguageLevel = null;
private boolean myIsPhysical = true;
public ClsFileImpl(@NotNull FileViewProvider viewProvider) {
this(viewProvider, false);
}
/** @deprecated use {@link #ClsFileImpl(FileViewProvider)} (to remove in IDEA 14) */
@SuppressWarnings("unused")
public ClsFileImpl(@NotNull PsiManager manager, @NotNull FileViewProvider viewProvider) {
this(viewProvider, false);
}
private ClsFileImpl(@NotNull FileViewProvider viewProvider, boolean forDecompiling) {
//noinspection ConstantConditions
super(null);
myViewProvider = viewProvider;
myIsForDecompiling = forDecompiling;
JavaElementType.CLASS.getIndex(); // initialize Java stubs
}
@Override
public PsiManager getManager() {
return myViewProvider.getManager();
}
@Override
@NotNull
public VirtualFile getVirtualFile() {
return myViewProvider.getVirtualFile();
}
@Override
public boolean processChildren(final PsiElementProcessor<PsiFileSystemItem> processor) {
return true;
}
@Override
public PsiDirectory getParent() {
return getContainingDirectory();
}
@Override
public PsiDirectory getContainingDirectory() {
VirtualFile parentFile = getVirtualFile().getParent();
if (parentFile == null) return null;
return getManager().findDirectory(parentFile);
}
@Override
public PsiFile getContainingFile() {
if (!isValid()) throw new PsiInvalidElementAccessException(this);
return this;
}
@Override
public boolean isValid() {
return myIsForDecompiling || getVirtualFile().isValid();
}
protected boolean isForDecompiling() {
return myIsForDecompiling;
}
@Override
@NotNull
public String getName() {
return getVirtualFile().getName();
}
@Override
@NotNull
public PsiElement[] getChildren() {
return getClasses(); // TODO : package statement?
}
@Override
@NotNull
public PsiClass[] getClasses() {
return getStub().getClasses();
}
@Override
public PsiPackageStatement getPackageStatement() {
getStub(); // Make sure myPackageStatement initializes.
ClsPackageStatementImpl statement = myPackageStatement;
if (statement == null) statement = new ClsPackageStatementImpl(this);
return statement.getPackageName() != null ? statement : null;
}
@Override
@NotNull
public String getPackageName() {
PsiPackageStatement statement = getPackageStatement();
return statement == null ? "" : statement.getPackageName();
}
@Override
public void setPackageName(final String packageName) throws IncorrectOperationException {
throw new IncorrectOperationException("Cannot set package name for compiled files");
}
@Override
public PsiImportList getImportList() {
return null;
}
@Override
public boolean importClass(PsiClass aClass) {
throw new UnsupportedOperationException("Cannot add imports to compiled classes");
}
@Override
@NotNull
public PsiElement[] getOnDemandImports(boolean includeImplicit, boolean checkIncludes) {
return PsiJavaCodeReferenceElement.EMPTY_ARRAY;
}
@Override
@NotNull
public PsiClass[] getSingleClassImports(boolean checkIncludes) {
return PsiClass.EMPTY_ARRAY;
}
@Override
@NotNull
public String[] getImplicitlyImportedPackages() {
return ArrayUtil.EMPTY_STRING_ARRAY;
}
@Override
public Set<String> getClassNames() {
return Collections.singleton(getVirtualFile().getNameWithoutExtension());
}
@Override
@NotNull
public PsiJavaCodeReferenceElement[] getImplicitlyImportedPackageReferences() {
return PsiJavaCodeReferenceElement.EMPTY_ARRAY;
}
@Override
public PsiJavaCodeReferenceElement findImportReferenceTo(PsiClass aClass) {
return null;
}
@Override
@NotNull
public LanguageLevel getLanguageLevel() {
LanguageLevel level = myLanguageLevel;
if (level == null) {
List classes = ApplicationManager.getApplication().runReadAction(new Computable<List>() {
@Override
public List compute() {
return getStub().getChildrenStubs();
}
});
myLanguageLevel = level = !classes.isEmpty() ? ((PsiClassStub<?>)classes.get(0)).getLanguageLevel() : LanguageLevel.HIGHEST;
}
return level;
}
@Override
public PsiElement setName(@NotNull String name) throws IncorrectOperationException {
throw new IncorrectOperationException(CAN_NOT_MODIFY_MESSAGE);
}
@Override
public void checkSetName(String name) throws IncorrectOperationException {
throw new IncorrectOperationException(CAN_NOT_MODIFY_MESSAGE);
}
@Override
public boolean isDirectory() {
return false;
}
@Override
public void appendMirrorText(final int indentLevel, @NotNull final StringBuilder buffer) {
buffer.append("\n");
buffer.append(" // IntelliJ API Decompiler stub source generated from a class file\n");
buffer.append(" // Implementation of methods is not available\n");
buffer.append("\n");
appendText(getPackageStatement(), 0, buffer, "\n\n");
PsiClass[] classes = getClasses();
if (classes.length > 0) {
appendText(classes[0], 0, buffer);
}
}
@Override
public void setMirror(@NotNull TreeElement element) throws InvalidMirrorException {
PsiElement mirrorElement = SourceTreeToPsiMap.treeToPsiNotNull(element);
if (!(mirrorElement instanceof PsiJavaFile)) {
throw new InvalidMirrorException("Unexpected mirror file: " + mirrorElement);
}
PsiJavaFile mirrorFile = (PsiJavaFile)mirrorElement;
setMirrorIfPresent(getPackageStatement(), mirrorFile.getPackageStatement());
setMirrors(getClasses(), mirrorFile.getClasses());
}
@SuppressWarnings("deprecation")
@Override
@NotNull
public PsiElement getNavigationElement() {
for (ClsCustomNavigationPolicy customNavigationPolicy : Extensions.getExtensions(ClsCustomNavigationPolicy.EP_NAME)) {
if (customNavigationPolicy instanceof ClsCustomNavigationPolicyEx) {
PsiFile navigationElement = ((ClsCustomNavigationPolicyEx)customNavigationPolicy).getFileNavigationElement(this);
if (navigationElement != null) {
return navigationElement;
}
}
}
return CachedValuesManager.getCachedValue(this, new CachedValueProvider<PsiElement>() {
@Nullable
@Override
public Result<PsiElement> compute() {
PsiElement target = JavaPsiImplementationHelper.getInstance(getProject()).getClsFileNavigationElement(ClsFileImpl.this);
ModificationTracker tracker = FileIndexFacade.getInstance(getProject()).getRootModificationTracker();
return Result.create(target, ClsFileImpl.this, target.getContainingFile(), tracker);
}
});
}
@Override
public PsiElement getMirror() {
TreeElement mirrorTreeElement = myMirrorFileElement;
if (mirrorTreeElement == null) {
synchronized (myMirrorLock) {
mirrorTreeElement = myMirrorFileElement;
if (mirrorTreeElement == null) {
final VirtualFile file = getVirtualFile();
CharSequence mirrorText = ClassFileDecompiler.decompileText(file);
String ext = JavaFileType.INSTANCE.getDefaultExtension();
PsiClass[] classes = getClasses();
String fileName = (classes.length > 0 ? classes[0].getName() : file.getNameWithoutExtension()) + "." + ext;
PsiFileFactory factory = PsiFileFactory.getInstance(getManager().getProject());
PsiFile mirror = factory.createFileFromText(fileName, JavaLanguage.INSTANCE, mirrorText, false, false);
mirror.putUserData(PsiUtil.FILE_LANGUAGE_LEVEL_KEY, getLanguageLevel());
mirrorTreeElement = SourceTreeToPsiMap.psiToTreeNotNull(mirror);
// IMPORTANT: do not take lock too early - FileDocumentManager.saveToString() can run write action
final TreeElement finalMirrorTreeElement = mirrorTreeElement;
ProgressManager.getInstance().executeNonCancelableSection(new Runnable() {
@Override
public void run() {
try {
setMirror(finalMirrorTreeElement);
}
catch (InvalidMirrorException e) {
LOG.error(file.getPath(), wrapException(e, file));
}
}
});
myMirrorFileElement = mirrorTreeElement;
}
}
}
return mirrorTreeElement.getPsi();
}
private static Exception wrapException(InvalidMirrorException e, VirtualFile file) {
ClassFileDecompilers.Decompiler decompiler = ClassFileDecompilers.find(file);
if (decompiler instanceof ClassFileDecompilers.Light) {
PluginId pluginId = PluginManagerCore.getPluginByClassName(decompiler.getClass().getName());
if (pluginId != null) {
return new PluginException(e, pluginId);
}
}
return e;
}
@Override
public PsiFile getDecompiledPsiFile() {
return (PsiFile)getMirror();
}
@Override
public long getModificationStamp() {
return getVirtualFile().getModificationStamp();
}
@Override
public void accept(@NotNull PsiElementVisitor visitor) {
if (visitor instanceof JavaElementVisitor) {
((JavaElementVisitor)visitor).visitJavaFile(this);
} else {
visitor.visitFile(this);
}
}
@NonNls
public String toString() {
return "PsiFile:" + getName();
}
@Override
@NotNull
public PsiFile getOriginalFile() {
return this;
}
@Override
@NotNull
public FileType getFileType() {
return JavaClassFileType.INSTANCE;
}
@Override
@NotNull
public PsiFile[] getPsiRoots() {
return new PsiFile[]{this};
}
@Override
@NotNull
public FileViewProvider getViewProvider() {
return myViewProvider;
}
@Override
public void subtreeChanged() {
}
@Override
public PsiElement getContext() {
return FileContextUtil.getFileContext(this);
}
@Override
@NotNull
public PsiClassHolderFileStub<?> getStub() {
return (PsiClassHolderFileStub)getStubTree().getRoot();
}
@Override
public boolean processDeclarations(@NotNull PsiScopeProcessor processor,
@NotNull ResolveState state,
PsiElement lastParent,
@NotNull PsiElement place) {
processor.handleEvent(PsiScopeProcessor.Event.SET_DECLARATION_HOLDER, this);
final ElementClassHint classHint = processor.getHint(ElementClassHint.KEY);
if (classHint == null || classHint.shouldProcess(ElementClassHint.DeclarationKind.CLASS)) {
final PsiClass[] classes = getClasses();
for (PsiClass aClass : classes) {
if (!processor.execute(aClass, state)) return false;
}
}
return true;
}
@Override
@NotNull
public StubTree getStubTree() {
ApplicationManager.getApplication().assertReadAccessAllowed();
StubTree stubTree = dereference(myStub);
if (stubTree != null) return stubTree;
// build newStub out of lock to avoid deadlock
StubTree newStubTree = (StubTree)StubTreeLoader.getInstance().readOrBuild(getProject(), getVirtualFile(), this);
if (newStubTree == null) {
LOG.warn("No stub for class file in index: " + getVirtualFile().getPresentableUrl());
newStubTree = new StubTree(new PsiJavaFileStubImpl("corrupted.classfiles", true));
}
synchronized (myStubLock) {
stubTree = dereference(myStub);
if (stubTree != null) return stubTree;
stubTree = newStubTree;
@SuppressWarnings("unchecked") PsiFileStubImpl<PsiFile> fileStub = (PsiFileStubImpl)stubTree.getRoot();
fileStub.setPsi(this);
myStub = new SoftReference<StubTree>(stubTree);
}
return stubTree;
}
@Override
public ASTNode findTreeForStub(final StubTree tree, final StubElement<?> stub) {
return null;
}
@Override
public boolean isContentsLoaded() {
return myStub != null;
}
@Override
public void onContentReload() {
ApplicationManager.getApplication().assertWriteAccessAllowed();
synchronized (myStubLock) {
StubTree stubTree = dereference(myStub);
myStub = null;
if (stubTree != null) {
//noinspection unchecked
((PsiFileStubImpl)stubTree.getRoot()).clearPsi("cls onContentReload");
}
}
ClsPackageStatementImpl packageStatement = new ClsPackageStatementImpl(this);
synchronized (myMirrorLock) {
myMirrorFileElement = null;
myPackageStatement = packageStatement;
}
myLanguageLevel = null;
}
@Override
public void putInfo(@NotNull Map<String, String> info) {
PsiFileImpl.putInfo(this, info);
}
@Override
public FileASTNode getNode() {
return null;
}
@Override
public boolean isPhysical() {
return myIsPhysical;
}
@SuppressWarnings("UnusedDeclaration") // used by Kotlin compiler
public void setPhysical(boolean isPhysical) {
myIsPhysical = isPhysical;
}
// default decompiler implementation
/** @deprecated use {@link #decompile(VirtualFile)} (to remove in IDEA 14) */
@SuppressWarnings("unused")
public static String decompile(@NotNull PsiManager manager, @NotNull VirtualFile file) {
return decompile(file).toString();
}
@NotNull
public static CharSequence decompile(@NotNull VirtualFile file) {
PsiManager manager = PsiManager.getInstance(DefaultProjectFactory.getInstance().getDefaultProject());
StringBuilder buffer = new StringBuilder();
new ClsFileImpl(new ClassFileViewProvider(manager, file), true).appendMirrorText(0, buffer);
return buffer;
}
@Nullable
public static PsiJavaFileStub buildFileStub(@NotNull VirtualFile file, @NotNull byte[] bytes) throws ClsFormatException {
if (ClassFileViewProvider.isInnerClass(file)) {
return null;
}
try {
PsiJavaFileStubImpl stub = new PsiJavaFileStubImpl("do.not.know.yet", true);
String className = file.getNameWithoutExtension();
StubBuildingVisitor<VirtualFile> visitor = new StubBuildingVisitor<VirtualFile>(file, STRATEGY, stub, 0, className);
try {
new ClassReader(bytes).accept(visitor, ClassReader.SKIP_FRAMES);
}
catch (OutOfOrderInnerClassException e) {
return null;
}
PsiClassStub<?> result = visitor.getResult();
if (result == null) return null;
stub.setPackageName(getPackageName(result));
return stub;
}
catch (Exception e) {
throw new ClsFormatException(file.getPath() + ": " + e.getMessage(), e);
}
}
private static final InnerClassSourceStrategy<VirtualFile> STRATEGY = new InnerClassSourceStrategy<VirtualFile>() {
@Nullable
@Override
public VirtualFile findInnerClass(String innerName, VirtualFile outerClass) {
String baseName = outerClass.getNameWithoutExtension();
VirtualFile dir = outerClass.getParent();
assert dir != null : outerClass;
return dir.findChild(baseName + "$" + innerName + ".class");
}
@Override
public void accept(VirtualFile innerClass, StubBuildingVisitor<VirtualFile> visitor) {
try {
byte[] bytes = innerClass.contentsToByteArray();
new ClassReader(bytes).accept(visitor, ClassReader.SKIP_FRAMES);
}
catch (IOException ignored) { }
}
};
private static String getPackageName(@NotNull PsiClassStub<?> result) {
String fqn = result.getQualifiedName();
String shortName = result.getName();
return fqn == null || Comparing.equal(shortName, fqn) ? "" : fqn.substring(0, fqn.lastIndexOf('.'));
}
}