/*
* Copyright 2000-2013 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.ide.caches.FileContent;
import com.intellij.ide.highlighter.JavaClassFileType;
import com.intellij.ide.highlighter.JavaFileType;
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.fileTypes.FileType;
import com.intellij.openapi.progress.NonCancelableSection;
import com.intellij.openapi.progress.ProgressIndicatorProvider;
import com.intellij.openapi.ui.Queryable;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.impl.JavaPsiImplementationHelper;
import com.intellij.psi.impl.PsiFileEx;
import com.intellij.psi.impl.PsiManagerEx;
import com.intellij.psi.impl.PsiManagerImpl;
import com.intellij.psi.impl.java.stubs.PsiClassStub;
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.search.PsiElementProcessor;
import com.intellij.psi.stubs.*;
import com.intellij.psi.util.PsiUtil;
import com.intellij.reference.SoftReference;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
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 PsiManagerImpl myManager;
private final boolean myIsForDecompiling;
private final FileViewProvider myViewProvider;
private volatile SoftReference<StubTree> myStub;
private volatile TreeElement myMirrorFileElement;
private volatile ClsPackageStatementImpl myPackageStatement = null;
private boolean myIsPhysical = true;
private ClsFileImpl(@NotNull PsiManagerImpl manager, @NotNull FileViewProvider viewProvider, boolean forDecompiling) {
//noinspection ConstantConditions
super(null);
myManager = manager;
myIsForDecompiling = forDecompiling;
myViewProvider = viewProvider;
JavaElementType.CLASS.getIndex(); // initialize Java stubs
}
public ClsFileImpl(PsiManagerImpl manager, FileViewProvider viewProvider) {
this(manager, viewProvider, false);
}
@Override
public PsiManager getManager() {
return myManager;
}
@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() {
if (myIsForDecompiling) return true;
VirtualFile vFile = getVirtualFile();
return vFile.isValid();
}
@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() {
return LanguageLevel.HIGHEST; // library classes should inherit language level from modules where they are referenced
}
@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());
}
@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 JavaPsiImplementationHelper.getInstance(getProject()).getClsFileNavigationElement(this);
}
@Override
public PsiElement getMirror() {
TreeElement mirrorTreeElement = myMirrorFileElement;
if (mirrorTreeElement == null) {
synchronized (myMirrorLock) {
mirrorTreeElement = myMirrorFileElement;
if (mirrorTreeElement == null) {
VirtualFile file = getVirtualFile();
String mirrorText = decompile(getManager(), 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, getSourceLanguageLevel());
mirrorTreeElement = SourceTreeToPsiMap.psiToTreeNotNull(mirror);
// IMPORTANT: do not take lock too early - FileDocumentManager.saveToString() can run write action
NonCancelableSection section = ProgressIndicatorProvider.startNonCancelableSectionIfSupported();
try {
setMirror(mirrorTreeElement);
}
catch (InvalidMirrorException e) {
LOG.error(file.getPath(), e);
}
finally {
section.done();
}
myMirrorFileElement = mirrorTreeElement;
}
}
}
return mirrorTreeElement.getPsi();
}
@NotNull
public LanguageLevel getSourceLanguageLevel() {
final List stubs = getStub().getChildrenStubs();
return stubs.size() > 0 ? ((PsiClassStub<?>)stubs.get(0)).getLanguageLevel() : LanguageLevel.HIGHEST;
}
@Override
public PsiFile getDecompiledPsiFile() {
for (ClsFileDecompiledPsiFileProvider provider : Extensions.getExtensions(ClsFileDecompiledPsiFileProvider.EP_NAME)) {
PsiFile decompiledPsiFile = provider.getDecompiledPsiFile(this);
if (decompiledPsiFile != null) {
return decompiledPsiFile;
}
}
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() {
}
public static String decompile(PsiManager manager, VirtualFile file) {
ClsFileImpl psiFile = null;
final FileViewProvider provider = ((PsiManagerEx)manager).getFileManager().findViewProvider(file);
if (provider != null) {
final PsiFile psi = provider.getPsi(provider.getBaseLanguage());
if (psi instanceof ClsFileImpl) {
psiFile = (ClsFileImpl)psi;
}
}
if (psiFile == null) {
psiFile = new ClsFileImpl((PsiManagerImpl)manager, new ClassFileViewProvider(manager, file), true);
}
final StringBuilder buffer = new StringBuilder();
psiFile.appendMirrorText(0, buffer);
return buffer.toString();
}
@Override
public PsiElement getContext() {
return FileContextUtil.getFileContext(this);
}
@Override
@NotNull
public PsiClassHolderFileStub getStub() {
return (PsiClassHolderFileStub)getStubTree().getRoot();
}
@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;
//noinspection unchecked
((PsiFileStubImpl)stubTree.getRoot()).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()).setPsi(null);
}
}
ClsPackageStatementImpl packageStatement = new ClsPackageStatementImpl(this);
synchronized (myMirrorLock) {
myMirrorFileElement = null;
myPackageStatement = packageStatement;
}
}
@Override
public PsiFile cacheCopy(final FileContent content) {
return this;
}
@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;
}
}