/* * Copyright 2000-2012 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.stubs; import com.intellij.lang.Language; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Attachment; import com.intellij.openapi.diagnostic.RuntimeExceptionWithAttachments; import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.impl.DebugUtil; import com.intellij.psi.impl.source.PsiFileWithStubSupport; import com.intellij.psi.tree.IStubFileElementType; import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.List; /** * @author yole */ public abstract class StubTreeLoader { public static StubTreeLoader getInstance() { return ServiceManager.getService(StubTreeLoader.class); } @Nullable public abstract ObjectStubTree readOrBuild(Project project, final VirtualFile vFile, @Nullable final PsiFile psiFile); @Nullable public abstract ObjectStubTree readFromVFile(Project project, final VirtualFile vFile); public boolean isStubReloadingProhibited() { return false; } public abstract void rebuildStubTree(VirtualFile virtualFile); public abstract boolean canHaveStub(VirtualFile file); protected boolean hasPsiInManyProjects(@NotNull VirtualFile virtualFile) { return false; } @Nullable protected IndexingStampInfo getIndexingStampInfo(@NotNull VirtualFile file) { return null; } @NotNull public RuntimeException stubTreeAndIndexDoNotMatch(@NotNull String _message, @NotNull ObjectStubTree stubTree, @NotNull PsiFileWithStubSupport psiFile) { VirtualFile file = psiFile.getViewProvider().getVirtualFile(); StubTree stubTreeFromIndex = (StubTree)readFromVFile(psiFile.getProject(), file); Document document = FileDocumentManager.getInstance().getDocument(file); IndexingStampInfo indexingStampInfo = getIndexingStampInfo(file); boolean upToDate = indexingStampInfo != null && indexingStampInfo.isUpToDate(document, file, psiFile); String msg = _message + "\nPlease report the problem to JetBrains with the files attached\n"; if (upToDate) { msg += "INDEXED VERSION IS THE CURRENT ONE"; } msg += " file=" + psiFile; msg += ", file.class=" + psiFile.getClass(); msg += ", file.lang=" + psiFile.getLanguage(); msg += ", modStamp=" + psiFile.getModificationStamp(); if (!(psiFile instanceof PsiCompiledElement)) { String text = psiFile.getText(); PsiFile fromText = PsiFileFactory.getInstance(psiFile.getProject()).createFileFromText(psiFile.getName(), psiFile.getFileType(), text); if (fromText.getLanguage().equals(psiFile.getLanguage())) { boolean consistent = DebugUtil.psiToString(psiFile, true).equals(DebugUtil.psiToString(fromText, true)); if (consistent) { msg += "\n tree consistent"; } else { msg += "\n AST INCONSISTENT, perhaps after incremental reparse; " + fromText; } } } msg += "\n stub debugInfo=" + stubTree.getDebugInfo(); msg += "\nlatestIndexedStub=" + stubTreeFromIndex; if (stubTreeFromIndex != null) { msg += "\n same size=" + (stubTree.getPlainList().size() == stubTreeFromIndex.getPlainList().size()); msg += "\n debugInfo=" + stubTreeFromIndex.getDebugInfo(); } FileViewProvider viewProvider = psiFile.getViewProvider(); msg += "\n viewProvider=" + viewProvider; msg += "\n viewProvider stamp: " + viewProvider.getModificationStamp(); msg += "; file stamp: " + file.getModificationStamp(); msg += "; file modCount: " + file.getModificationCount(); msg += "; file length: " + file.getLength(); if (document != null) { msg += "\n doc saved: " + !FileDocumentManager.getInstance().isDocumentUnsaved(document); msg += "; doc stamp: " + document.getModificationStamp(); msg += "; doc size: " + document.getTextLength(); msg += "; committed: " + PsiDocumentManager.getInstance(psiFile.getProject()).isCommitted(document); } msg += "\nin many projects: " + hasPsiInManyProjects(file); msg += "\nindexing info: " + indexingStampInfo; Attachment[] attachments = createAttachments(stubTree, psiFile, file, stubTreeFromIndex); // separate methods and separate exception classes for EA to treat these situations differently return upToDate ? handleUpToDateMismatch(msg, attachments) : new RuntimeExceptionWithAttachments(msg, attachments); } private static UpToDateStubIndexMismatch handleUpToDateMismatch(@NotNull String message, Attachment[] attachments) { return new UpToDateStubIndexMismatch(message, attachments); } @NotNull private static Attachment[] createAttachments(@NotNull ObjectStubTree stubTree, @NotNull PsiFileWithStubSupport psiFile, VirtualFile file, @Nullable StubTree stubTreeFromIndex) { List<Attachment> attachments = ContainerUtil.newArrayList(); attachments.add(new Attachment(file.getPath() + "_file.txt", psiFile instanceof PsiCompiledElement ? "compiled" : psiFile.getText())); attachments.add(new Attachment("stubTree.txt", ((PsiFileStubImpl)stubTree.getRoot()).printTree())); if (stubTreeFromIndex != null) { attachments.add(new Attachment("stubTreeFromIndex.txt", ((PsiFileStubImpl)stubTreeFromIndex.getRoot()).printTree())); } return attachments.toArray(Attachment.EMPTY_ARRAY); } public static String getFileViewProviderMismatchDiagnostics(@NotNull FileViewProvider provider) { Function<PsiFile, String> fileClassName = file -> file.getClass().getSimpleName(); Function<Pair<IStubFileElementType, PsiFile>, String> stubRootToString = pair -> "(" + pair.first.toString() + ", " + pair.first.getLanguage() + " -> " + fileClassName.fun(pair.second) + ")"; List<Pair<IStubFileElementType, PsiFile>> roots = StubTreeBuilder.getStubbedRoots(provider); return "path = " + provider.getVirtualFile().getPath() + ", stubBindingRoot = " + fileClassName.fun(provider.getStubBindingRoot()) + ", languages = [" + StringUtil.join(provider.getLanguages(), Language::getID, ", ") + "], fileTypes = [" + StringUtil.join(provider.getAllFiles(), file -> file.getFileType().getName(), ", ") + "], files = [" + StringUtil.join(provider.getAllFiles(), fileClassName, ", ") + "], roots = [" + StringUtil.join(roots, stubRootToString, ", ") + "]"; } } @SuppressWarnings("ExceptionClassNameDoesntEndWithException") class UpToDateStubIndexMismatch extends RuntimeExceptionWithAttachments { UpToDateStubIndexMismatch(String message, Attachment... attachments) { super(message, attachments); } }