/*
* Copyright 2003-2016 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 jetbrains.mps.idea.core.psi.impl;
import com.intellij.lang.FileASTNode;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.FileViewProvider;
import com.intellij.psi.PsiBinaryFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiFileSystemItem;
import com.intellij.psi.PsiManager;
import com.intellij.psi.SingleRootFileViewProvider;
import com.intellij.psi.search.PsiElementProcessor;
import com.intellij.util.IncorrectOperationException;
import jetbrains.mps.extapi.persistence.FileDataSource;
import jetbrains.mps.fileTypes.MPSFileTypeFactory;
import jetbrains.mps.icons.MPSIcons.Nodes;
import jetbrains.mps.ide.icons.GlobalIconManager;
import jetbrains.mps.ide.icons.IdeIcons;
import jetbrains.mps.ide.project.ProjectHelper;
import jetbrains.mps.ide.vfs.VirtualFileUtils;
import jetbrains.mps.idea.core.projectView.edit.SNodeDeleteProvider;
import jetbrains.mps.nodefs.NodeVirtualFileSystem;
import jetbrains.mps.openapi.navigation.NavigationSupport;
import jetbrains.mps.smodel.ModelAccessHelper;
import jetbrains.mps.smodel.SNodePointer;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.model.SNodeId;
import org.jetbrains.mps.openapi.model.SNodeReference;
import org.jetbrains.mps.openapi.module.SRepository;
import org.jetbrains.mps.openapi.persistence.DataSource;
import javax.swing.Icon;
import java.util.Collections;
/**
* User: fyodor
* Date: 3/5/13
*/
public class MPSPsiRootNode extends MPSPsiNodeBase implements PsiFile, PsiBinaryFile, MPSPsiRealNode {
private final FileViewProvider myViewProvider;
private final SNodeId myNodeId;
private final String myName;
private final MPSPsiModel myModel;
private VirtualFile mySeparateFile;
public MPSPsiRootNode(SNodeId nodeId, @NotNull String name, MPSPsiModel containingModel, PsiManager manager) {
super(manager);
myNodeId = nodeId;
myModel = containingModel;
myName = name;
myViewProvider = new SingleRootFileViewProvider(manager, NodeVirtualFileSystem.getInstance().getFileFor(getProjectRepository(), getSNodeReference()), false);
}
public MPSPsiRootNode(SNodeId nodeId, @NotNull String name, MPSPsiModel containingModel, PsiManager manager, @NotNull VirtualFile virtualFile) {
this(nodeId, name, containingModel, manager);
mySeparateFile = virtualFile;
}
@Override
protected Icon getBaseIcon() {
SRepository repository = ProjectHelper.getProjectRepository(getProject());
return new ModelAccessHelper(repository.getModelAccess()).runReadAction(() -> {
final SNode node = getSNodeReference().resolve(repository);
if (node == null) return IdeIcons.UNKNOWN_ICON;
final GlobalIconManager globalIconManager = ApplicationManager.getApplication().getComponent(GlobalIconManager.class);
return globalIconManager == null ? Nodes.Node : globalIconManager.getIconFor(node);
});
}
@Nullable
@Override
protected Icon getElementIcon(@IconFlags int flags) {
return getBaseIcon();
}
@Override
public boolean isValid() {
if (!myModel.isValid() || mySeparateFile != null && !mySeparateFile.isValid()) return false;
final SRepository repository = getProjectRepository();
final Ref<Boolean> result = new Ref<>(false);
final SNodeReference nodeRef = getSNodeReference();
if (nodeRef == null) return false;
repository.getModelAccess().runReadAction(() -> {
SNode node = nodeRef.resolve(repository);
result.set(node != null);
});
return result.get();
}
@Nullable
@Override
public VirtualFile getVirtualFile() {
// return MPSNodesVirtualFileSystem.getInstance().getFileFor(getSNodeReference());
if (mySeparateFile != null) {
return mySeparateFile;
}
final SRepository repository = getProjectRepository();
return new ModelAccessHelper(repository.getModelAccess()).runReadAction(() -> {
SModel sModel = myModel.getSModelReference().resolve(repository);
DataSource dataSource = sModel.getSource();
if (dataSource instanceof FileDataSource) {
return VirtualFileUtils.getProjectVirtualFile(((FileDataSource) dataSource).getFile());
}
return null;
});
}
@Override
public boolean processChildren(PsiElementProcessor<PsiFileSystemItem> processor) {
return false;
}
@Override
public MPSPsiModel getContainingModel() {
return myModel != null ? myModel : super.getContainingModel();
}
@Nullable
@Override
public PsiDirectory getContainingDirectory() {
return getContainingModel();
}
@Override
public boolean isDirectory() {
return false;
}
@Nullable
@Override
public PsiDirectory getParent() {
if (myViewProvider.getVirtualFile().getFileType() == MPSFileTypeFactory.MPS_ROOT_FILE_TYPE && super.getParent() instanceof MPSPsiModel) {
return ((MPSPsiModel) super.getParent()).getParentDirectory();
}
return (PsiDirectory) super.getParent();
}
@Override
public String toString() {
return "Root: " + myNodeId.toString();
}
@Override
public long getModificationStamp() {
VirtualFile file = getVirtualFile();
return file != null ? file.getModificationStamp() : -1;
}
@NotNull
@Override
public PsiFile getContainingFile() {
// PsiFileImpl does the same
return this;
}
@NotNull
@Override
public PsiFile getOriginalFile() {
return this;
}
@NotNull
@Override
public FileType getFileType() {
return MPSFileTypeFactory.MPS_NODE_FILE_TYPE;
}
@NotNull
@Override
public String getName() {
return myName;
}
@NotNull
@Override
public PsiFile[] getPsiRoots() {
return new PsiFile[]{this};
}
@NotNull
@Override
public FileViewProvider getViewProvider() {
return myViewProvider;
}
@Override
public FileASTNode getNode() {
return null;
}
@Override
public void navigate(final boolean requestFocus) {
final SRepository repository = getProjectRepository();
repository.getModelAccess().runWriteInEDT(() -> {
SModel model = myModel.getSModelReference().resolve(repository);
if (model == null) return;
SNode node = model.getNode(myNodeId);
if (node == null) return;
NavigationSupport.getInstance().openNode(ProjectHelper.fromIdeaProject(getProject()), node, requestFocus, false);
});
}
@Override
public boolean isPhysical() {
// Honestly check that file is physical - per root RootNode will return true
return this.getVirtualFile() != null && !this.getVirtualFile().equals(myModel.getSourceVirtualFile());
}
@Override
public void subtreeChanged() {
//To change body of implemented methods use File | Settings | File Templates.
}
@Override
public void checkSetName(String name) throws IncorrectOperationException {
throw new IncorrectOperationException("Not implemented");
}
@Override
public PsiElement setName(@NonNls @NotNull String name) throws IncorrectOperationException {
throw new IncorrectOperationException("Not implemented");
}
public SNodeId getNodeId() {
return myNodeId;
}
@Override
public SNodeReference getSNodeReference() {
MPSPsiModel psiModel = getContainingModel();
if (psiModel == null) return null;
return new SNodePointer(psiModel.getSModelReference(), myNodeId);
}
// added for idea search scope to work with our virtual files
// see PsiSearchScopeUtil.isInScope
// The main thing is: return some physical real file that lives well with idea's search scopes
@Override
public PsiElement getContext() {
VirtualFile vFile = mySeparateFile != null ?
mySeparateFile :
myModel.getSourceVirtualFile();
// TEMP FIX: Guarding vFile == null
// model.getSourceFile() gives us null in case model is in a jar file, because its
// source.getFile() returns not a virtual file, but a JarEntryFile
// Proper solution, i think: exclude them from indexing by MPSFQNameJavaClassIndex and the like
// Came here from MPSJavaClassFinder
return vFile != null && vFile.isValid() ? PsiManager.getInstance(getProject()).findFile(vFile) : null;
}
@Override
public boolean isWritable() {
return true;
}
@Override
public String getText() {
// implemented to avoid assertion error in PsiDocumentManagerImpl.getDocument(PsiFile)
// document.getTextLength() != file.getTextLength() fails
return myViewProvider.getPsi(getLanguage()).getText();
}
@Override
public void delete() throws IncorrectOperationException {
SNodeDeleteProvider deleteProvider = new SNodeDeleteProvider(
Collections.singletonList(getSNodeReference()),
ProjectHelper.fromIdeaProject(getProject()));
getProjectRepository().getModelAccess().executeUndoTransparentCommand(deleteProvider);
}
}