| /* |
| * 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. |
| */ |
| |
| /* |
| * Class FrameDebuggerTree |
| * @author Jeka |
| */ |
| package com.intellij.debugger.ui.impl; |
| |
| import com.intellij.debugger.DebuggerBundle; |
| import com.intellij.debugger.DebuggerInvocationUtil; |
| import com.intellij.debugger.SourcePosition; |
| import com.intellij.debugger.engine.DebuggerUtils; |
| import com.intellij.debugger.engine.SuspendManager; |
| import com.intellij.debugger.engine.evaluation.EvaluateException; |
| import com.intellij.debugger.engine.evaluation.EvaluationContextImpl; |
| import com.intellij.debugger.engine.evaluation.TextWithImports; |
| import com.intellij.debugger.engine.evaluation.TextWithImportsImpl; |
| import com.intellij.debugger.engine.events.DebuggerCommandImpl; |
| import com.intellij.debugger.engine.jdi.StackFrameProxy; |
| import com.intellij.debugger.impl.DebuggerContextImpl; |
| import com.intellij.debugger.impl.DebuggerSession; |
| import com.intellij.debugger.jdi.*; |
| import com.intellij.debugger.settings.ViewsGeneralSettings; |
| import com.intellij.debugger.ui.impl.watch.*; |
| import com.intellij.lang.java.JavaLanguage; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.fileEditor.FileDocumentManager; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Computable; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.psi.*; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.util.text.CharArrayUtil; |
| import com.intellij.util.ui.tree.TreeModelAdapter; |
| import com.intellij.xdebugger.XDebuggerBundle; |
| import com.intellij.xdebugger.frame.XStackFrame; |
| import com.intellij.xdebugger.settings.XDebuggerSettingsManager; |
| import com.sun.jdi.*; |
| import gnu.trove.TIntObjectHashMap; |
| import gnu.trove.TObjectProcedure; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.event.TreeModelEvent; |
| import javax.swing.tree.TreeModel; |
| import javax.swing.tree.TreePath; |
| import java.util.*; |
| |
| public class FrameVariablesTree extends DebuggerTree { |
| private static final Logger LOG = Logger.getInstance(FrameVariablesTree.class); |
| |
| private boolean myAnyNewLocals; |
| private boolean myAutoWatchMode = false; |
| |
| private final VariablesPanel myVariablesPanel; |
| |
| public FrameVariablesTree(@NotNull Project project) { |
| this(project, null); |
| } |
| |
| public FrameVariablesTree(@NotNull Project project, @Nullable VariablesPanel variablesPanel) { |
| super(project); |
| |
| getEmptyText().setText(XDebuggerBundle.message("debugger.variables.not.available")); |
| myVariablesPanel = variablesPanel; |
| } |
| |
| public boolean isAutoWatchMode() { |
| return myAutoWatchMode; |
| } |
| |
| public void setAutoVariablesMode(final boolean autoWatchMode) { |
| final boolean valueChanged = myAutoWatchMode != autoWatchMode; |
| myAutoWatchMode = autoWatchMode; |
| if (valueChanged) { |
| rebuild(getDebuggerContext()); |
| } |
| } |
| |
| @Override |
| protected void build(DebuggerContextImpl context) { |
| myAnyNewLocals = false; |
| buildWhenPaused(context, new RefreshFrameTreeCommand(context)); |
| } |
| |
| @Override |
| public void restoreNodeState(DebuggerTreeNodeImpl node) { |
| if (myAnyNewLocals) { |
| final NodeDescriptorImpl descriptor = node.getDescriptor(); |
| final boolean isLocalVar = descriptor instanceof LocalVariableDescriptorImpl; |
| descriptor.myIsSelected &= isLocalVar; |
| // override this setting so that tree will scroll to new locals |
| descriptor.myIsVisible = isLocalVar && descriptor.myIsSelected; |
| if (!descriptor.myIsVisible) { |
| descriptor.putUserData(VISIBLE_RECT, null); |
| } |
| } |
| super.restoreNodeState(node); |
| if (myAnyNewLocals && node.getDescriptor().myIsExpanded) { |
| DebuggerTreeNodeImpl root = (DebuggerTreeNodeImpl)getMutableModel().getRoot(); |
| scrollToVisible(root); |
| } |
| } |
| |
| |
| @Override |
| protected DebuggerCommandImpl getBuildNodeCommand(@NotNull DebuggerTreeNodeImpl node) { |
| if (node.getDescriptor() instanceof StackFrameDescriptorImpl) { |
| return new BuildFrameTreeVariablesCommand(node); |
| } |
| return super.getBuildNodeCommand(node); |
| } |
| |
| private class BuildFrameTreeVariablesCommand extends BuildStackFrameCommand { |
| public BuildFrameTreeVariablesCommand(DebuggerTreeNodeImpl stackNode) { |
| super(stackNode); |
| } |
| |
| @Override |
| public void threadAction() { |
| if (myVariablesPanel != null) { |
| StackFrameDescriptorImpl stackDescriptor = (StackFrameDescriptorImpl)getNode().getDescriptor(); |
| XStackFrame xStackFrame = stackDescriptor.getXStackFrame(); |
| myVariablesPanel.stackChanged(xStackFrame); |
| if (xStackFrame != null) { |
| return; |
| } |
| } |
| |
| super.threadAction(); |
| } |
| |
| @Override |
| protected void buildVariables(final StackFrameDescriptorImpl stackDescriptor, final EvaluationContextImpl evaluationContext) throws EvaluateException { |
| final DebuggerContextImpl debuggerContext = getDebuggerContext(); |
| final SourcePosition sourcePosition = debuggerContext.getSourcePosition(); |
| if (sourcePosition == null) { |
| return; |
| } |
| |
| try { |
| if (!XDebuggerSettingsManager.getInstance().getDataViewSettings().isAutoExpressions() && !myAutoWatchMode) { |
| // optimization |
| super.buildVariables(stackDescriptor, evaluationContext); |
| } |
| else { |
| final Map<String, LocalVariableProxyImpl> visibleVariables = getVisibleVariables(stackDescriptor.getFrameProxy()); |
| final EvaluationContextImpl evalContext = debuggerContext.createEvaluationContext(); |
| final Pair<Set<String>, Set<TextWithImports>> usedVars = |
| ApplicationManager.getApplication().runReadAction(new Computable<Pair<Set<String>, Set<TextWithImports>>>() { |
| @Override |
| public Pair<Set<String>, Set<TextWithImports>> compute() { |
| return findReferencedVars(visibleVariables.keySet(), sourcePosition, evalContext); |
| } |
| }); |
| // add locals |
| if (myAutoWatchMode) { |
| for (String var : usedVars.first) { |
| final LocalVariableDescriptorImpl descriptor = myNodeManager.getLocalVariableDescriptor(stackDescriptor, visibleVariables.get(var)); |
| myChildren.add(myNodeManager.createNode(descriptor, evaluationContext)); |
| } |
| } |
| else { |
| super.buildVariables(stackDescriptor, evaluationContext); |
| } |
| // add expressions |
| final EvaluationContextImpl evalContextCopy = evaluationContext.createEvaluationContext(evaluationContext.getThisObject()); |
| evalContextCopy.setAutoLoadClasses(false); |
| for (TextWithImports text : usedVars.second) { |
| myChildren.add(myNodeManager.createNode(myNodeManager.getWatchItemDescriptor(stackDescriptor, text, null), evalContextCopy)); |
| } |
| } |
| } |
| catch (EvaluateException e) { |
| if (e.getCause() instanceof AbsentInformationException) { |
| final StackFrameProxyImpl frame = stackDescriptor.getFrameProxy(); |
| |
| final Collection<Value> argValues = frame.getArgumentValues(); |
| int index = 0; |
| for (Value argValue : argValues) { |
| final ArgumentValueDescriptorImpl descriptor = myNodeManager.getArgumentValueDescriptor(stackDescriptor, index++, argValue, null); |
| final DebuggerTreeNodeImpl variableNode = myNodeManager.createNode(descriptor, evaluationContext); |
| myChildren.add(variableNode); |
| } |
| myChildren.add(myNodeManager.createMessageNode(MessageDescriptor.LOCAL_VARIABLES_INFO_UNAVAILABLE)); |
| // trying to collect values from variable slots |
| final List<DecompiledLocalVariable> decompiled = collectVariablesFromBytecode(frame, argValues.size()); |
| if (!decompiled.isEmpty()) { |
| try { |
| final Map<DecompiledLocalVariable, Value> values = LocalVariablesUtil.fetchValues(frame.getStackFrame(), decompiled); |
| for (DecompiledLocalVariable var : decompiled) { |
| final Value value = values.get(var); |
| final ArgumentValueDescriptorImpl descriptor = myNodeManager.getArgumentValueDescriptor(stackDescriptor, var.getSlot(), value, var.getName()); |
| final DebuggerTreeNodeImpl variableNode = myNodeManager.createNode(descriptor, evaluationContext); |
| myChildren.add(variableNode); |
| } |
| } |
| catch (Exception ex) { |
| LOG.info(ex); |
| } |
| } |
| } |
| else { |
| throw e; |
| } |
| } |
| } |
| } |
| |
| public static List<DecompiledLocalVariable> collectVariablesFromBytecode(final StackFrameProxy frame, int argumentCount) throws EvaluateException { |
| if (!frame.getVirtualMachine().canGetBytecodes()) { |
| return Collections.emptyList(); |
| } |
| try { |
| final Location location = frame.location(); |
| LOG.assertTrue(location != null); |
| final Method method = location.method(); |
| final Location methodLocation = method.location(); |
| if (methodLocation == null || methodLocation.codeIndex() < 0) { |
| // native or abstract method |
| return Collections.emptyList(); |
| } |
| final byte[] bytecodes = method.bytecodes(); |
| if (bytecodes != null && bytecodes.length > 0) { |
| final int firstLocalVariableSlot = argumentCount + (method.isStatic()? 0 : 1); |
| final long instructionIndex = location.codeIndex(); |
| final TIntObjectHashMap<DecompiledLocalVariable> usedVars = new TIntObjectHashMap<DecompiledLocalVariable>(); |
| new InstructionParser(bytecodes, instructionIndex) { |
| @Override |
| protected void localVariableInstructionFound(int opcode, int slot, String typeSignature) { |
| if (slot >= firstLocalVariableSlot) { |
| DecompiledLocalVariable variable = usedVars.get(slot); |
| if (variable == null || !typeSignature.equals(variable.getSignature())) { |
| variable = new DecompiledLocalVariable(slot, "slot_" + slot, typeSignature); |
| usedVars.put(slot, variable); |
| } |
| } |
| } |
| }.parse(); |
| |
| if (usedVars.isEmpty()) { |
| return Collections.emptyList(); |
| } |
| final List<DecompiledLocalVariable> vars = new ArrayList<DecompiledLocalVariable>(usedVars.size()); |
| usedVars.forEachValue(new TObjectProcedure<DecompiledLocalVariable>() { |
| @Override |
| public boolean execute(DecompiledLocalVariable var) { |
| vars.add(var); |
| return true; |
| } |
| }); |
| Collections.sort(vars, DecompiledLocalVariable.COMPARATOR); |
| return vars; |
| } |
| } |
| catch (UnsupportedOperationException ignored) { |
| } |
| catch (Exception e) { |
| LOG.info(e); |
| } |
| return Collections.emptyList(); |
| } |
| |
| public static Map<String, LocalVariableProxyImpl> getVisibleVariables(final StackFrameProxyImpl frame) throws EvaluateException { |
| final Map<String, LocalVariableProxyImpl> vars = new HashMap<String, LocalVariableProxyImpl>(); |
| for (LocalVariableProxyImpl localVariableProxy : frame.visibleVariables()) { |
| vars.put(localVariableProxy.name(), localVariableProxy); |
| } |
| return vars; |
| } |
| |
| private static boolean shouldSkipLine(final PsiFile file, Document doc, int line) { |
| final int start = doc.getLineStartOffset(line); |
| final int end = doc.getLineEndOffset(line); |
| final int _start = CharArrayUtil.shiftForward(doc.getCharsSequence(), start, " \n\t"); |
| if (_start >= end) { |
| return true; |
| } |
| |
| TextRange alreadyChecked = null; |
| for (PsiElement elem = file.findElementAt(_start); elem != null && elem.getTextOffset() <= end && (alreadyChecked == null || !alreadyChecked .contains(elem.getTextRange())); elem = elem.getNextSibling()) { |
| for (PsiElement _elem = elem; _elem.getTextOffset() >= _start; _elem = _elem.getParent()) { |
| alreadyChecked = _elem.getTextRange(); |
| |
| if (_elem instanceof PsiDeclarationStatement) { |
| final PsiElement[] declared = ((PsiDeclarationStatement)_elem).getDeclaredElements(); |
| for (PsiElement declaredElement : declared) { |
| if (declaredElement instanceof PsiVariable) { |
| return false; |
| } |
| } |
| } |
| |
| if (_elem instanceof PsiJavaCodeReferenceElement) { |
| final PsiElement resolved = ((PsiJavaCodeReferenceElement)_elem).resolve(); |
| if (resolved instanceof PsiVariable) { |
| return false; |
| } |
| } |
| } |
| } |
| return true; |
| } |
| |
| public static Pair<Set<String>, Set<TextWithImports>> findReferencedVars(final Set<String> visibleVars, |
| final SourcePosition position, |
| EvaluationContextImpl evalContext) { |
| final int line = position.getLine(); |
| if (line < 0) { |
| return Pair.create(Collections.<String>emptySet(), Collections.<TextWithImports>emptySet()); |
| } |
| final PsiFile positionFile = position.getFile(); |
| if (!positionFile.getLanguage().isKindOf(JavaLanguage.INSTANCE)) { |
| return Pair.create(visibleVars, Collections.<TextWithImports>emptySet()); |
| } |
| |
| final VirtualFile vFile = positionFile.getVirtualFile(); |
| final Document doc = vFile != null? FileDocumentManager.getInstance().getDocument(vFile) : null; |
| if (doc == null || doc.getLineCount() == 0 || line > (doc.getLineCount() - 1)) { |
| return Pair.create(Collections.<String>emptySet(), Collections.<TextWithImports>emptySet()); |
| } |
| |
| final TextRange limit = calculateLimitRange(positionFile, doc, line); |
| |
| int startLine = Math.max(limit.getStartOffset(), line - 1); |
| startLine = Math.min(startLine, limit.getEndOffset()); |
| while (startLine > limit.getStartOffset() && shouldSkipLine(positionFile, doc, startLine)) { |
| startLine--; |
| } |
| final int startOffset = doc.getLineStartOffset(startLine); |
| |
| int endLine = Math.min(line + 2, limit.getEndOffset()); |
| while (endLine < limit.getEndOffset() && shouldSkipLine(positionFile, doc, endLine)) { |
| endLine++; |
| } |
| final int endOffset = doc.getLineEndOffset(endLine); |
| |
| final TextRange lineRange = new TextRange(startOffset, endOffset); |
| if (!lineRange.isEmpty()) { |
| final int offset = CharArrayUtil.shiftForward(doc.getCharsSequence(), doc.getLineStartOffset(line), " \t"); |
| PsiElement element = positionFile.findElementAt(offset); |
| if (element != null) { |
| PsiMethod method = PsiTreeUtil.getNonStrictParentOfType(element, PsiMethod.class); |
| if (method != null) { |
| element = method; |
| } |
| else { |
| PsiField field = PsiTreeUtil.getNonStrictParentOfType(element, PsiField.class); |
| if (field != null) { |
| element = field; |
| } |
| else { |
| final PsiClassInitializer initializer = PsiTreeUtil.getNonStrictParentOfType(element, PsiClassInitializer.class); |
| if (initializer != null) { |
| element = initializer; |
| } |
| } |
| } |
| |
| //noinspection unchecked |
| if (element instanceof PsiCompiledElement) { |
| return Pair.create(visibleVars, Collections.<TextWithImports>emptySet()); |
| } |
| else { |
| final Set<String> vars = new HashSet<String>(); |
| final Set<TextWithImports> expressions = new HashSet<TextWithImports>(); |
| final PsiElementVisitor variablesCollector = new VariablesCollector(visibleVars, adjustRange(element, lineRange), expressions, vars, position, evalContext); |
| element.accept(variablesCollector); |
| |
| return Pair.create(vars, expressions); |
| } |
| } |
| } |
| return Pair.create(Collections.<String>emptySet(), Collections.<TextWithImports>emptySet()); |
| } |
| |
| private static TextRange calculateLimitRange(final PsiFile file, final Document doc, final int line) { |
| final int offset = doc.getLineStartOffset(line); |
| if (offset > 0) { |
| for (PsiElement elem = file.findElementAt(offset); elem != null; elem = elem.getParent()) { |
| if (elem instanceof PsiMethod) { |
| final TextRange elemRange = elem.getTextRange(); |
| return new TextRange(doc.getLineNumber(elemRange.getStartOffset()), doc.getLineNumber(elemRange.getEndOffset())); |
| } |
| } |
| } |
| return new TextRange(0, doc.getLineCount() - 1); |
| } |
| |
| private static TextRange adjustRange(final PsiElement element, final TextRange originalRange) { |
| final Ref<TextRange> rangeRef = new Ref<TextRange>(originalRange); |
| element.accept(new JavaRecursiveElementVisitor() { |
| @Override public void visitExpressionStatement(final PsiExpressionStatement statement) { |
| final TextRange stRange = statement.getTextRange(); |
| if (originalRange.intersects(stRange)) { |
| final TextRange currentRange = rangeRef.get(); |
| final int start = Math.min(currentRange.getStartOffset(), stRange.getStartOffset()); |
| final int end = Math.max(currentRange.getEndOffset(), stRange.getEndOffset()); |
| rangeRef.set(new TextRange(start, end)); |
| } |
| } |
| }); |
| return rangeRef.get(); |
| } |
| |
| private class RefreshFrameTreeCommand extends RefreshDebuggerTreeCommand { |
| public RefreshFrameTreeCommand(DebuggerContextImpl context) { |
| super(context); |
| } |
| |
| @Override |
| public void contextAction() throws Exception { |
| DebuggerTreeNodeImpl rootNode; |
| |
| final DebuggerContextImpl debuggerContext = getDebuggerContext(); |
| final ThreadReferenceProxyImpl currentThread = debuggerContext.getThreadProxy(); |
| if (currentThread == null) { |
| return; |
| } |
| |
| try { |
| StackFrameProxyImpl frame = debuggerContext.getFrameProxy(); |
| if (frame != null) { |
| NodeManagerImpl nodeManager = getNodeFactory(); |
| rootNode = nodeManager.createNode(nodeManager.getStackFrameDescriptor(null, frame), debuggerContext.createEvaluationContext()); |
| } |
| else { |
| rootNode = getNodeFactory().getDefaultNode(); |
| SuspendManager suspendManager = getSuspendContext().getDebugProcess().getSuspendManager(); |
| try { |
| if (suspendManager.isSuspended(currentThread)) { |
| try { |
| if (currentThread.frameCount() == 0) { |
| rootNode.add(MessageDescriptor.THREAD_IS_EMPTY); |
| } |
| else { |
| rootNode.add(MessageDescriptor.DEBUG_INFO_UNAVAILABLE); |
| } |
| } |
| catch (EvaluateException e) { |
| rootNode.add(new MessageDescriptor(e.getMessage())); |
| } |
| } |
| else { |
| rootNode.add(MessageDescriptor.THREAD_IS_RUNNING); |
| } |
| } |
| catch (ObjectCollectedException ignored) { |
| rootNode.add(new MessageDescriptor(DebuggerBundle.message("label.thread.node.thread.collected", currentThread.name()))); |
| } |
| } |
| } |
| catch (Exception ex) { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug(ex); |
| } |
| rootNode = getNodeFactory().getDefaultNode(); |
| rootNode.add(MessageDescriptor.DEBUG_INFO_UNAVAILABLE); |
| } |
| |
| final DebuggerTreeNodeImpl rootNode1 = rootNode; |
| DebuggerInvocationUtil.swingInvokeLater(getProject(), new Runnable() { |
| @Override |
| public void run() { |
| getMutableModel().setRoot(rootNode1); |
| treeChanged(); |
| |
| final TreeModel model = getModel(); |
| model.addTreeModelListener(new TreeModelAdapter() { |
| @Override |
| public void treeStructureChanged(TreeModelEvent e) { |
| final Object[] path = e.getPath(); |
| if (path.length > 0 && path[path.length - 1] == rootNode1) { |
| // wait until rootNode1 (the root just set) becomes the root |
| model.removeTreeModelListener(this); |
| if (ViewsGeneralSettings.getInstance().AUTOSCROLL_TO_NEW_LOCALS) { |
| autoscrollToNewLocals(rootNode1); |
| } |
| else { |
| // should clear this flag, otherwise, if AUTOSCROLL_TO_NEW_LOCALS option turned |
| // to true during the debug process, all these variables will be considered 'new' |
| for (Enumeration children = rootNode1.rawChildren(); children.hasMoreElements();) { |
| final DebuggerTreeNodeImpl child = (DebuggerTreeNodeImpl)children.nextElement(); |
| final NodeDescriptorImpl descriptor = child.getDescriptor(); |
| if (descriptor instanceof LocalVariableDescriptorImpl) { |
| ((LocalVariableDescriptorImpl)descriptor).setNewLocal(false); |
| } |
| } |
| } |
| } |
| } |
| }); |
| } |
| |
| private void autoscrollToNewLocals(DebuggerTreeNodeImpl frameNode) { |
| final DebuggerSession debuggerSession = debuggerContext.getDebuggerSession(); |
| final boolean isSteppingThrough = debuggerSession != null && debuggerSession.isSteppingThrough(debuggerContext.getThreadProxy()); |
| final List<DebuggerTreeNodeImpl> toClear = new ArrayList<DebuggerTreeNodeImpl>(); |
| final List<DebuggerTreeNodeImpl> newLocalsToSelect = new ArrayList<DebuggerTreeNodeImpl>(); |
| |
| for (Enumeration e = frameNode.rawChildren(); e.hasMoreElements();) { |
| final DebuggerTreeNodeImpl child = (DebuggerTreeNodeImpl)e.nextElement(); |
| final NodeDescriptorImpl descriptor = child.getDescriptor(); |
| if (!(descriptor instanceof LocalVariableDescriptorImpl)) { |
| continue; |
| } |
| final LocalVariableDescriptorImpl localVariableDescriptor = (LocalVariableDescriptorImpl)descriptor; |
| if (isSteppingThrough && localVariableDescriptor.isNewLocal()) { |
| myAnyNewLocals = true; |
| newLocalsToSelect.add(child); |
| } |
| else { |
| toClear.add(child); |
| } |
| localVariableDescriptor.setNewLocal(false); |
| } |
| |
| if (!newLocalsToSelect.isEmpty()) { |
| for (DebuggerTreeNodeImpl child : toClear) { |
| removeSelectionPath(new TreePath(child.getPath())); |
| child.getDescriptor().myIsSelected = false; |
| } |
| for (DebuggerTreeNodeImpl child : newLocalsToSelect) { |
| addSelectionPath(new TreePath(child.getPath())); |
| child.getDescriptor().myIsSelected = true; |
| } |
| } |
| } |
| }); |
| } |
| |
| } |
| |
| private static class VariablesCollector extends JavaRecursiveElementVisitor { |
| private final Set<String> myVisibleLocals; |
| private final TextRange myLineRange; |
| private final Set<TextWithImports> myExpressions; |
| private final Set<String> myVars; |
| private final SourcePosition myPosition; |
| private final EvaluationContextImpl myEvalContext; |
| private final boolean myCollectExpressions; |
| |
| public VariablesCollector(final Set<String> visibleLocals, |
| final TextRange lineRange, |
| final Set<TextWithImports> expressions, |
| final Set<String> vars, |
| SourcePosition position, EvaluationContextImpl evalContext) { |
| myVisibleLocals = visibleLocals; |
| myLineRange = lineRange; |
| myExpressions = expressions; |
| myVars = vars; |
| myPosition = position; |
| myEvalContext = evalContext; |
| myCollectExpressions = XDebuggerSettingsManager.getInstance().getDataViewSettings().isAutoExpressions(); |
| } |
| |
| @Override |
| public void visitElement(final PsiElement element) { |
| if (myLineRange.intersects(element.getTextRange())) { |
| super.visitElement(element); |
| } |
| } |
| |
| @Override |
| public void visitMethodCallExpression(final PsiMethodCallExpression expression) { |
| if (myCollectExpressions) { |
| final PsiMethod psiMethod = expression.resolveMethod(); |
| if (psiMethod != null && !DebuggerUtils.hasSideEffectsOrReferencesMissingVars(expression, myVisibleLocals)) { |
| myExpressions.add(new TextWithImportsImpl(expression)); |
| } |
| } |
| super.visitMethodCallExpression(expression); |
| } |
| |
| @Override |
| public void visitReferenceExpression(final PsiReferenceExpression reference) { |
| if (myLineRange.intersects(reference.getTextRange())) { |
| final PsiElement psiElement = reference.resolve(); |
| if (psiElement instanceof PsiVariable) { |
| final PsiVariable var = (PsiVariable)psiElement; |
| if (var instanceof PsiField) { |
| if (myCollectExpressions && !DebuggerUtils.hasSideEffectsOrReferencesMissingVars(reference, myVisibleLocals)) { |
| /* |
| if (var instanceof PsiEnumConstant && reference.getQualifier() == null) { |
| final PsiClass enumClass = ((PsiEnumConstant)var).getContainingClass(); |
| if (enumClass != null) { |
| final PsiExpression expression = JavaPsiFacade.getInstance(var.getProject()).getParserFacade().createExpressionFromText(enumClass.getName() + "." + var.getName(), var); |
| final PsiReference ref = expression.getReference(); |
| if (ref != null) { |
| ref.bindToElement(var); |
| myExpressions.add(new TextWithImportsImpl(expression)); |
| } |
| } |
| } |
| else { |
| myExpressions.add(new TextWithImportsImpl(reference)); |
| } |
| */ |
| final PsiModifierList modifierList = var.getModifierList(); |
| boolean isConstant = (var instanceof PsiEnumConstant) || |
| (modifierList != null && modifierList.hasModifierProperty(PsiModifier.STATIC) && modifierList.hasModifierProperty(PsiModifier.FINAL)); |
| if (!isConstant) { |
| myExpressions.add(new TextWithImportsImpl(reference)); |
| } |
| } |
| } |
| else { |
| if (myVisibleLocals.contains(var.getName())) { |
| myVars.add(var.getName()); |
| } |
| } |
| } |
| } |
| super.visitReferenceExpression(reference); |
| } |
| |
| @Override |
| public void visitArrayAccessExpression(final PsiArrayAccessExpression expression) { |
| if (myCollectExpressions && !DebuggerUtils.hasSideEffectsOrReferencesMissingVars(expression, myVisibleLocals)) { |
| myExpressions.add(new TextWithImportsImpl(expression)); |
| } |
| super.visitArrayAccessExpression(expression); |
| } |
| |
| @Override |
| public void visitParameter(final PsiParameter parameter) { |
| processVariable(parameter); |
| super.visitParameter(parameter); |
| } |
| |
| @Override |
| public void visitLocalVariable(final PsiLocalVariable variable) { |
| processVariable(variable); |
| super.visitLocalVariable(variable); |
| } |
| |
| private void processVariable(final PsiVariable variable) { |
| if (myLineRange.intersects(variable.getTextRange()) && myVisibleLocals.contains(variable.getName())) { |
| myVars.add(variable.getName()); |
| } |
| } |
| |
| @Override |
| public void visitClass(final PsiClass aClass) { |
| // Do not step in to local and anonymous classes... |
| } |
| } |
| } |