blob: 81524ba31402c4922e0f884f3db51e35f0817dc9 [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;
import com.intellij.lang.ASTNode;
import com.intellij.lang.LighterASTNode;
import com.intellij.lang.LighterASTTokenNode;
import com.intellij.lang.impl.PsiBuilderImpl;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.UserDataHolderBase;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.SourceTreeToPsiMap;
import com.intellij.psi.impl.source.tree.CompositeElement;
import com.intellij.psi.impl.source.tree.SharedImplUtil;
import com.intellij.psi.impl.source.tree.TreeElement;
import com.intellij.psi.stubs.ObjectStubSerializer;
import com.intellij.psi.stubs.Stub;
import com.intellij.psi.tree.IElementType;
import com.intellij.util.*;
import com.intellij.util.diff.FlyweightCapableTreeStructure;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.List;
@SuppressWarnings({"HardCodedStringLiteral", "UtilityClassWithoutPrivateConstructor", "UnusedDeclaration", "TestOnlyProblems"})
public class DebugUtil {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.DebugUtil");
public static class LengthBuilder implements Appendable {
private int myLength = 0;
public int getLength() {
return myLength;
}
@Override
public Appendable append(final CharSequence csq) {
myLength += csq.length();
return this;
}
@Override
public Appendable append(final CharSequence csq, final int start, final int end) {
myLength += csq.subSequence(start, end).length();
return this;
}
@Override
public Appendable append(final char c) {
myLength++;
return this;
}
}
public static /*final*/ boolean CHECK = false;
public static final boolean DO_EXPENSIVE_CHECKS;
static {
Application application = ApplicationManager.getApplication();
DO_EXPENSIVE_CHECKS = application != null && application.isUnitTestMode();
}
public static final boolean CHECK_INSIDE_ATOMIC_ACTION_ENABLED = DO_EXPENSIVE_CHECKS;
public static String psiTreeToString(@NotNull final PsiElement element, final boolean skipWhitespaces) {
final ASTNode node = SourceTreeToPsiMap.psiElementToTree(element);
assert node != null : element;
return treeToString(node, skipWhitespaces);
}
public static String treeToString(@NotNull final ASTNode root, final boolean skipWhitespaces) {
final LengthBuilder ruler = new LengthBuilder();
treeToBuffer(ruler, root, 0, skipWhitespaces, false, false, true);
final StringBuilder buffer = new StringBuilder(ruler.getLength());
treeToBuffer(buffer, root, 0, skipWhitespaces, false, false, true);
return buffer.toString();
}
public static String nodeTreeToString(@NotNull final ASTNode root, final boolean skipWhitespaces) {
final LengthBuilder ruler = new LengthBuilder();
treeToBuffer(ruler, root, 0, skipWhitespaces, false, false, false);
final StringBuilder buffer = new StringBuilder(ruler.getLength());
treeToBuffer(buffer, root, 0, skipWhitespaces, false, false, false);
return buffer.toString();
}
public static String treeToString(@NotNull ASTNode root, boolean skipWhitespaces, boolean showRanges) {
final LengthBuilder ruler = new LengthBuilder();
treeToBuffer(ruler, root, 0, skipWhitespaces, showRanges, false, true);
final StringBuilder buffer = new StringBuilder(ruler.getLength());
treeToBuffer(buffer, root, 0, skipWhitespaces, showRanges, false, true);
return buffer.toString();
}
public static String treeToStringWithUserData(TreeElement root, boolean skipWhitespaces) {
final LengthBuilder ruler = new LengthBuilder();
treeToBufferWithUserData(ruler, root, 0, skipWhitespaces);
final StringBuilder buffer = new StringBuilder(ruler.getLength());
treeToBufferWithUserData(buffer, root, 0, skipWhitespaces);
return buffer.toString();
}
public static String treeToStringWithUserData(PsiElement root, boolean skipWhitespaces) {
final LengthBuilder ruler = new LengthBuilder();
treeToBufferWithUserData(ruler, root, 0, skipWhitespaces);
final StringBuilder buffer = new StringBuilder(ruler.getLength());
treeToBufferWithUserData(buffer, root, 0, skipWhitespaces);
return buffer.toString();
}
public static void treeToBuffer(@NotNull final Appendable buffer,
@NotNull final ASTNode root,
final int indent,
final boolean skipWhiteSpaces,
final boolean showRanges,
final boolean showChildrenRanges,
final boolean usePsi) {
treeToBuffer(buffer, root, indent, skipWhiteSpaces, showRanges, showChildrenRanges, usePsi, null);
}
public static void treeToBuffer(@NotNull final Appendable buffer,
@NotNull final ASTNode root,
final int indent,
final boolean skipWhiteSpaces,
final boolean showRanges,
final boolean showChildrenRanges,
final boolean usePsi,
PairConsumer<PsiElement, Consumer<PsiElement>> extra) {
if (skipWhiteSpaces && root.getElementType() == TokenType.WHITE_SPACE) return;
StringUtil.repeatSymbol(buffer, ' ', indent);
try {
PsiElement psiElement = null;
if (root instanceof CompositeElement) {
if (usePsi) {
psiElement = root.getPsi();
if (psiElement != null) {
buffer.append(psiElement.toString());
}
else {
buffer.append(root.getElementType().toString());
}
}
else {
buffer.append(root.toString());
}
}
else {
final String text = fixWhiteSpaces(root.getText());
buffer.append(root.toString()).append("('").append(text).append("')");
}
if (showRanges) buffer.append(root.getTextRange().toString());
buffer.append("\n");
if (root instanceof CompositeElement) {
ASTNode child = root.getFirstChildNode();
if (child == null) {
StringUtil.repeatSymbol(buffer, ' ', indent + 2);
buffer.append("<empty list>\n");
}
else {
while (child != null) {
treeToBuffer(buffer, child, indent + 2, skipWhiteSpaces, showChildrenRanges, showChildrenRanges, usePsi, extra);
child = child.getTreeNext();
}
}
}
if (psiElement != null && extra != null ) {
extra.consume(psiElement, new Consumer<PsiElement>() {
@Override
public void consume(PsiElement element) {
treeToBuffer(buffer, element.getNode(), indent + 2, skipWhiteSpaces, showChildrenRanges, showChildrenRanges, usePsi, null);
}
});
}
}
catch (IOException e) {
LOG.error(e);
}
}
public static String lightTreeToString(@NotNull final FlyweightCapableTreeStructure<LighterASTNode> tree,
final boolean skipWhitespaces) {
final LengthBuilder ruler = new LengthBuilder();
lightTreeToBuffer(tree, tree.getRoot(), ruler, 0, skipWhitespaces);
final StringBuilder buffer = new StringBuilder(ruler.getLength());
lightTreeToBuffer(tree, tree.getRoot(), buffer, 0, skipWhitespaces);
return buffer.toString();
}
public static void lightTreeToBuffer(@NotNull final FlyweightCapableTreeStructure<LighterASTNode> tree,
@NotNull final LighterASTNode node,
@NotNull final Appendable buffer,
final int indent,
final boolean skipWhiteSpaces) {
final IElementType tokenType = node.getTokenType();
if (skipWhiteSpaces && tokenType == TokenType.WHITE_SPACE) return;
final boolean isLeaf = (node instanceof LighterASTTokenNode);
StringUtil.repeatSymbol(buffer, ' ', indent);
try {
if (tokenType == TokenType.ERROR_ELEMENT) {
buffer.append("PsiErrorElement:").append(PsiBuilderImpl.getErrorMessage(node));
}
else if (tokenType == TokenType.WHITE_SPACE) {
buffer.append("PsiWhiteSpace");
}
else {
buffer.append(isLeaf ? "PsiElement" : "Element").append('(').append(tokenType.toString()).append(')');
}
if (isLeaf) {
final String text = ((LighterASTTokenNode)node).getText().toString();
buffer.append("('").append(fixWhiteSpaces(text)).append("')");
}
buffer.append('\n');
if (!isLeaf) {
final Ref<LighterASTNode[]> kids = new Ref<LighterASTNode[]>();
final int numKids = tree.getChildren(tree.prepareForGetChildren(node), kids);
if (numKids == 0) {
StringUtil.repeatSymbol(buffer, ' ', indent + 2);
buffer.append("<empty list>\n");
}
else {
for (int i = 0; i < numKids; i++) {
lightTreeToBuffer(tree, kids.get()[i], buffer, indent + 2, skipWhiteSpaces);
}
}
}
}
catch (IOException e) {
LOG.error(e);
}
}
public static String stubTreeToString(final Stub root) {
final LengthBuilder ruler = new LengthBuilder();
stubTreeToBuffer(root, ruler, 0);
final StringBuilder builder = new StringBuilder(ruler.getLength());
stubTreeToBuffer(root, builder, 0);
return builder.toString();
}
public static void stubTreeToBuffer(final Stub node, final Appendable buffer, final int indent) {
StringUtil.repeatSymbol(buffer, ' ', indent);
try {
final ObjectStubSerializer stubType = node.getStubType();
if (stubType != null) {
buffer.append(stubType.toString()).append(':');
}
buffer.append(node.toString()).append('\n');
@SuppressWarnings({"unchecked"})
final List<? extends Stub> children = node.getChildrenStubs();
for (final Stub child : children) {
stubTreeToBuffer(child, buffer, indent + 2);
}
}
catch (IOException e) {
LOG.error(e);
}
}
private static void treeToBufferWithUserData(Appendable buffer, TreeElement root, int indent, boolean skipWhiteSpaces) {
if (skipWhiteSpaces && root.getElementType() == TokenType.WHITE_SPACE) return;
StringUtil.repeatSymbol(buffer, ' ', indent);
try {
final PsiElement psi = SourceTreeToPsiMap.treeElementToPsi(root);
assert psi != null : root;
if (root instanceof CompositeElement) {
buffer.append(psi.toString());
}
else {
final String text = fixWhiteSpaces(root.getText());
buffer.append(root.toString()).append("('").append(text).append("')");
}
buffer.append(root.getUserDataString());
buffer.append("\n");
if (root instanceof CompositeElement) {
PsiElement[] children = psi.getChildren();
for (PsiElement child : children) {
treeToBufferWithUserData(buffer, (TreeElement)SourceTreeToPsiMap.psiElementToTree(child), indent + 2, skipWhiteSpaces);
}
if (children.length == 0) {
StringUtil.repeatSymbol(buffer, ' ', indent + 2);
buffer.append("<empty list>\n");
}
}
}
catch (IOException e) {
LOG.error(e);
}
}
private static void treeToBufferWithUserData(Appendable buffer, PsiElement root, int indent, boolean skipWhiteSpaces) {
if (skipWhiteSpaces && root instanceof PsiWhiteSpace) return;
StringUtil.repeatSymbol(buffer, ' ', indent);
try {
if (root instanceof CompositeElement) {
buffer.append(root.toString());
}
else {
final String text = fixWhiteSpaces(root.getText());
buffer.append(root.toString()).append("('").append(text).append("')");
}
buffer.append(((UserDataHolderBase)root).getUserDataString());
buffer.append("\n");
PsiElement[] children = root.getChildren();
for (PsiElement child : children) {
treeToBufferWithUserData(buffer, child, indent + 2, skipWhiteSpaces);
}
if (children.length == 0) {
StringUtil.repeatSymbol(buffer, ' ', indent + 2);
buffer.append("<empty list>\n");
}
}
catch (IOException e) {
LOG.error(e);
}
}
public static void doCheckTreeStructure(@Nullable ASTNode anyElement) {
if (anyElement == null) return;
ASTNode root = anyElement;
while (root.getTreeParent() != null) {
root = root.getTreeParent();
}
if (root instanceof CompositeElement) {
checkSubtree((CompositeElement)root);
}
}
private static void checkSubtree(CompositeElement root) {
if (root.rawFirstChild() == null) {
if (root.rawLastChild() != null) {
throw new IncorrectTreeStructureException(root, "firstChild == null, but lastChild != null");
}
}
else {
for (ASTNode child = root.getFirstChildNode(); child != null; child = child.getTreeNext()) {
if (child instanceof CompositeElement) {
checkSubtree((CompositeElement)child);
}
if (child.getTreeParent() != root) {
throw new IncorrectTreeStructureException(child, "child has wrong parent value");
}
if (child == root.getFirstChildNode()) {
if (child.getTreePrev() != null) {
throw new IncorrectTreeStructureException(root, "firstChild.prev != null");
}
}
else {
if (child.getTreePrev() == null) {
throw new IncorrectTreeStructureException(child, "not first child has prev == null");
}
if (child.getTreePrev().getTreeNext() != child) {
throw new IncorrectTreeStructureException(child, "element.prev.next != element");
}
}
if (child.getTreeNext() == null) {
if (root.getLastChildNode() != child) {
throw new IncorrectTreeStructureException(child, "not last child has next == null");
}
}
}
}
}
public static void checkParentChildConsistent(@NotNull ASTNode element) {
ASTNode treeParent = element.getTreeParent();
if (treeParent == null) return;
ASTNode[] elements = treeParent.getChildren(null);
if (ArrayUtil.find(elements, element) == -1) {
throw new IncorrectTreeStructureException(element, "child cannot be found among parents children");
}
//LOG.debug("checked consistence: "+System.identityHashCode(element));
}
public static void checkSameCharTabs(@NotNull ASTNode element1, @NotNull ASTNode element2) {
final CharTable fromCharTab = SharedImplUtil.findCharTableByTree(element1);
final CharTable toCharTab = SharedImplUtil.findCharTableByTree(element2);
LOG.assertTrue(fromCharTab == toCharTab);
}
public static String psiToString(@NotNull PsiElement element, final boolean skipWhitespaces) {
return psiToString(element, skipWhitespaces, false);
}
public static String psiToString(@NotNull final PsiElement root, final boolean skipWhiteSpaces, final boolean showRanges) {
return psiToString(root, skipWhiteSpaces, showRanges, null);
}
public static String psiToString(@NotNull final PsiElement root, final boolean skipWhiteSpaces, final boolean showRanges, PairConsumer<PsiElement, Consumer<PsiElement>> extra) {
final LengthBuilder ruler = new LengthBuilder();
psiToBuffer(ruler, root, skipWhiteSpaces, showRanges, extra);
final StringBuilder buffer = new StringBuilder(ruler.getLength());
psiToBuffer(buffer, root, skipWhiteSpaces, showRanges, extra);
return buffer.toString();
}
private static void psiToBuffer(final Appendable buffer,
final PsiElement root,
final boolean skipWhiteSpaces,
final boolean showRanges,
PairConsumer<PsiElement, Consumer<PsiElement>> extra) {
final ASTNode node = root.getNode();
if (node == null) {
psiToBuffer(buffer, root, 0, skipWhiteSpaces, showRanges, showRanges, extra);
}
else {
treeToBuffer(buffer, node, 0, skipWhiteSpaces, showRanges, showRanges, true, extra);
}
}
public static void psiToBuffer(@NotNull final Appendable buffer,
@NotNull final PsiElement root,
int indent,
boolean skipWhiteSpaces,
boolean showRanges,
boolean showChildrenRanges) {
psiToBuffer(buffer, root, indent, skipWhiteSpaces, showRanges, showChildrenRanges, null);
}
public static void psiToBuffer(@NotNull final Appendable buffer,
@NotNull final PsiElement root,
final int indent,
final boolean skipWhiteSpaces,
boolean showRanges,
final boolean showChildrenRanges,
PairConsumer<PsiElement, Consumer<PsiElement>> extra) {
if (skipWhiteSpaces && root instanceof PsiWhiteSpace) return;
StringUtil.repeatSymbol(buffer, ' ', indent);
try {
buffer.append(root.toString());
PsiElement child = root.getFirstChild();
if (child == null) {
final String text = root.getText();
assert text != null : "text is null for <" + root + ">";
buffer.append("('").append(fixWhiteSpaces(text)).append("')");
}
if (showRanges) buffer.append(root.getTextRange().toString());
buffer.append("\n");
while (child != null) {
psiToBuffer(buffer, child, indent + 2, skipWhiteSpaces, showChildrenRanges, showChildrenRanges, extra);
child = child.getNextSibling();
}
if (extra != null) {
extra.consume(root, new Consumer<PsiElement>() {
@Override
public void consume(PsiElement element) {
psiToBuffer(buffer, element, indent + 2, skipWhiteSpaces, showChildrenRanges, showChildrenRanges, null);
}
});
}
}
catch (IOException e) {
LOG.error(e);
}
}
public static String fixWhiteSpaces(String text) {
text = StringUtil.replace(text, "\n", "\\n");
text = StringUtil.replace(text, "\r", "\\r");
text = StringUtil.replace(text, "\t", "\\t");
return text;
}
public static String currentStackTrace() {
return ExceptionUtil.getThrowableText(new Throwable());
}
public static class IncorrectTreeStructureException extends RuntimeException {
private final ASTNode myElement;
public IncorrectTreeStructureException(ASTNode element, String message) {
super(message);
myElement = element;
}
public ASTNode getElement() {
return myElement;
}
}
private static final ThreadLocal<Object> ourPsiModificationTrace = new ThreadLocal<Object>();
private static final ThreadLocal<Integer> ourPsiModificationDepth = new ThreadLocal<Integer>();
/**
* Marks a start of PSI modification action. Any PSI/AST elements invalidated inside such an action will contain a debug trace
* identifying this transaction, and so will {@link com.intellij.psi.PsiInvalidElementAccessException} thrown when accessing such invalid
* elements. This should help finding out why a specific PSI element has become invalid.
*
* @param trace The debug trace that the invalidated elements should be identified by. May be null, then current stack trace is used.
*/
public static void startPsiModification(@Nullable String trace) {
if (!PsiInvalidElementAccessException.isTrackingInvalidation()) {
return;
}
//noinspection ThrowableResultOfMethodCallIgnored
if (ourPsiModificationTrace.get() == null) {
ourPsiModificationTrace.set(trace != null ? trace : new Throwable());
}
Integer depth = ourPsiModificationDepth.get();
if (depth == null) depth = 0;
ourPsiModificationDepth.set(depth + 1);
}
/**
* Finished PSI modification action.
* @see #startPsiModification(String)
*/
public static void finishPsiModification() {
if (!PsiInvalidElementAccessException.isTrackingInvalidation()) {
return;
}
Integer depth = ourPsiModificationDepth.get();
if (depth == null) {
LOG.warn("Unmatched PSI modification end", new Throwable());
depth = 0;
} else {
depth--;
ourPsiModificationDepth.set(depth);
}
if (depth == 0) {
ourPsiModificationTrace.set(null);
}
}
public static void onInvalidated(@NotNull ASTNode treeElement) {
Object trace = calcInvalidationTrace(treeElement);
if (trace != null) {
PsiInvalidElementAccessException.setInvalidationTrace(treeElement, trace);
}
}
public static void onInvalidated(@NotNull FileViewProvider provider) {
Object trace = calcInvalidationTrace(null);
if (trace != null) {
PsiInvalidElementAccessException.setInvalidationTrace(provider, trace);
}
}
@Nullable
private static Object calcInvalidationTrace(@Nullable ASTNode treeElement) {
if (!PsiInvalidElementAccessException.isTrackingInvalidation()) {
return null;
}
if (PsiInvalidElementAccessException.findInvalidationTrace(treeElement) != null) {
return null;
}
Object trace = ourPsiModificationTrace.get();
if (trace == null) {
trace = new Throwable();
LOG.info("PSI invalidated outside transaction", (Throwable)trace);
}
return trace;
}
public static void revalidateNode(@NotNull ASTNode element) {
PsiInvalidElementAccessException.setInvalidationTrace(element, null);
}
public static void sleep(long millis) {
TimeoutUtil.sleep(millis);
}
public static void checkTreeStructure(ASTNode element) {
if (CHECK){
doCheckTreeStructure(element);
}
}
}