| /* |
| * Copyright 2000-2009 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.debugger.actions; |
| |
| import com.intellij.codeInsight.daemon.impl.HighlightInfoType; |
| import com.intellij.debugger.engine.DebugProcess; |
| import com.intellij.debugger.engine.DebugProcessImpl; |
| import com.intellij.debugger.engine.DebuggerUtils; |
| import com.intellij.debugger.engine.events.DebuggerContextCommandImpl; |
| import com.intellij.debugger.impl.DebuggerContextImpl; |
| import com.intellij.debugger.impl.DebuggerSession; |
| import com.intellij.debugger.ui.impl.tree.TreeBuilder; |
| import com.intellij.debugger.ui.impl.watch.DebuggerTree; |
| import com.intellij.debugger.ui.impl.watch.DebuggerTreeNodeImpl; |
| import com.intellij.debugger.ui.impl.watch.NodeDescriptorImpl; |
| import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl; |
| import com.intellij.debugger.ui.tree.ValueDescriptor; |
| import com.intellij.openapi.actionSystem.AnActionEvent; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.colors.EditorColorsManager; |
| import com.intellij.openapi.editor.markup.TextAttributes; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.util.containers.HashMap; |
| import com.intellij.xdebugger.impl.actions.MarkObjectActionHandler; |
| import com.intellij.xdebugger.impl.ui.tree.ValueMarkup; |
| import com.sun.jdi.*; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import java.awt.*; |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| |
| import static com.intellij.openapi.actionSystem.PlatformDataKeys.CONTEXT_COMPONENT; |
| |
| /* |
| * Class SetValueAction |
| * @author Jeka |
| */ |
| public class JavaMarkObjectActionHandler extends MarkObjectActionHandler { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.actions.JavaMarkObjectActionHandler"); |
| public static final long AUTO_MARKUP_REFERRING_OBJECTS_LIMIT = 100L; // todo: some reasonable limit |
| |
| @Override |
| public void perform(@NotNull Project project, AnActionEvent event) { |
| final DebuggerTreeNodeImpl node = DebuggerAction.getSelectedNode(event.getDataContext()); |
| if (node == null) { |
| return; |
| } |
| final NodeDescriptorImpl descriptor = node.getDescriptor(); |
| if (!(descriptor instanceof ValueDescriptorImpl)) { |
| return; |
| } |
| |
| final DebuggerTree tree = node.getTree(); |
| tree.saveState(node); |
| |
| final Component parent = event.getData(CONTEXT_COMPONENT); |
| final ValueDescriptorImpl valueDescriptor = ((ValueDescriptorImpl)descriptor); |
| final DebuggerContextImpl debuggerContext = tree.getDebuggerContext(); |
| final DebugProcessImpl debugProcess = debuggerContext.getDebugProcess(); |
| final ValueMarkup markup = valueDescriptor.getMarkup(debugProcess); |
| debugProcess.getManagerThread().invoke(new DebuggerContextCommandImpl(debuggerContext) { |
| public Priority getPriority() { |
| return Priority.HIGH; |
| } |
| public void threadAction() { |
| boolean sessionRefreshNeeded = true; |
| try { |
| if (markup != null) { |
| valueDescriptor.setMarkup(debugProcess, null); |
| } |
| else { |
| final String defaultText = valueDescriptor.getName(); |
| final Ref<Pair<ValueMarkup,Boolean>> result = new Ref<Pair<ValueMarkup, Boolean>>(null); |
| try { |
| final boolean suggestAdditionalMarkup = canSuggestAdditionalMarkup(debugProcess, valueDescriptor.getValue()); |
| SwingUtilities.invokeAndWait(new Runnable() { |
| public void run() { |
| ObjectMarkupPropertiesDialog dialog = new ObjectMarkupPropertiesDialog(parent, defaultText, suggestAdditionalMarkup); |
| dialog.show(); |
| if (dialog.isOK()) { |
| result.set(Pair.create(dialog.getConfiguredMarkup(), dialog.isMarkAdditionalFields())); |
| } |
| } |
| }); |
| } |
| catch (InterruptedException ignored) { |
| } |
| catch (InvocationTargetException e) { |
| LOG.error(e); |
| } |
| final Pair<ValueMarkup, Boolean> pair = result.get(); |
| if (pair != null) { |
| valueDescriptor.setMarkup(debugProcess, pair.first); |
| if (pair.second) { |
| final Value value = valueDescriptor.getValue(); |
| final Map<ObjectReference, ValueMarkup> additionalMarkup = suggestMarkup((ObjectReference)value); |
| if (!additionalMarkup.isEmpty()) { |
| final Map<ObjectReference, ValueMarkup> map = NodeDescriptorImpl.getMarkupMap(debugProcess); |
| if (map != null) { |
| for (Map.Entry<ObjectReference, ValueMarkup> entry : additionalMarkup.entrySet()) { |
| final ObjectReference key = entry.getKey(); |
| if (!map.containsKey(key)) { |
| map.put(key, entry.getValue()); |
| } |
| } |
| } |
| } |
| } |
| } |
| else { |
| sessionRefreshNeeded = false; |
| } |
| } |
| } |
| finally { |
| final boolean _sessionRefreshNeeded = sessionRefreshNeeded; |
| SwingUtilities.invokeLater(new Runnable() { |
| public void run() { |
| tree.restoreState(node); |
| final TreeBuilder model = tree.getMutableModel(); |
| refreshLabelsRecursively(model.getRoot(), model, valueDescriptor.getValue()); |
| if (_sessionRefreshNeeded) { |
| final DebuggerSession session = debuggerContext.getDebuggerSession(); |
| if (session != null) { |
| session.refresh(true); |
| } |
| } |
| } |
| |
| private void refreshLabelsRecursively(Object node, TreeBuilder model, Value value) { |
| if (node instanceof DebuggerTreeNodeImpl) { |
| final DebuggerTreeNodeImpl _node = (DebuggerTreeNodeImpl)node; |
| final NodeDescriptorImpl descriptor = _node.getDescriptor(); |
| if (descriptor instanceof ValueDescriptor && Comparing.equal(value, ((ValueDescriptor)descriptor).getValue())) { |
| _node.labelChanged(); |
| } |
| } |
| final int childCount = model.getChildCount(node); |
| for (int idx = 0; idx < childCount; idx++) { |
| refreshLabelsRecursively(model.getChild(node, idx), model, value); |
| } |
| } |
| |
| }); |
| } |
| } |
| }); |
| } |
| |
| private static boolean canSuggestAdditionalMarkup(DebugProcessImpl debugProcess, Value value) { |
| if (!debugProcess.getVirtualMachineProxy().canGetInstanceInfo()) { |
| return false; |
| } |
| if (!(value instanceof ObjectReference)) { |
| return false; |
| } |
| final ObjectReference objRef = (ObjectReference)value; |
| if (objRef instanceof ArrayReference || objRef instanceof ClassObjectReference || objRef instanceof ThreadReference || objRef instanceof ThreadGroupReference || objRef instanceof ClassLoaderReference) { |
| return false; |
| } |
| return true; |
| } |
| |
| private static Map<ObjectReference, ValueMarkup> suggestMarkup(ObjectReference objRef) { |
| final Map<ObjectReference, ValueMarkup> result = new HashMap<ObjectReference, ValueMarkup>(); |
| for (ObjectReference ref : getReferringObjects(objRef)) { |
| if (!(ref instanceof ClassObjectReference)) { |
| // consider references from statisc fields only |
| continue; |
| } |
| final ReferenceType refType = ((ClassObjectReference)ref).reflectedType(); |
| if (!refType.isAbstract()) { |
| continue; |
| } |
| for (Field field : refType.visibleFields()) { |
| if (!(field.isStatic() && field.isFinal())) { |
| continue; |
| } |
| if (DebuggerUtils.isPrimitiveType(field.typeName())) { |
| continue; |
| } |
| final Value fieldValue = refType.getValue(field); |
| if (!(fieldValue instanceof ObjectReference)) { |
| continue; |
| } |
| final ValueMarkup markup = result.get((ObjectReference)fieldValue); |
| |
| final String fieldName = field.name(); |
| final Color autoMarkupColor = getAutoMarkupColor(); |
| if (markup == null) { |
| result.put((ObjectReference)fieldValue, new ValueMarkup(fieldName, autoMarkupColor, createMarkupTooltipText(null, refType, fieldName))); |
| } |
| else { |
| final String currentText = markup.getText(); |
| if (!currentText.contains(fieldName)) { |
| final String currentTooltip = markup.getToolTipText(); |
| final String tooltip = createMarkupTooltipText(currentTooltip, refType, fieldName); |
| result.put((ObjectReference)fieldValue, new ValueMarkup(currentText + ", " + fieldName, autoMarkupColor, tooltip)); |
| } |
| } |
| } |
| } |
| return result; |
| } |
| |
| private static List<ObjectReference> getReferringObjects(ObjectReference value) { |
| try { |
| return value.referringObjects(AUTO_MARKUP_REFERRING_OBJECTS_LIMIT); |
| } |
| catch (UnsupportedOperationException e) { |
| LOG.info(e); |
| } |
| return Collections.emptyList(); |
| } |
| |
| private static String createMarkupTooltipText(@Nullable String prefix, ReferenceType refType, String fieldName) { |
| final StringBuilder builder = new StringBuilder(); |
| if (prefix == null) { |
| builder.append("Value referenced from:"); |
| } |
| else { |
| builder.append(prefix); |
| } |
| return builder.append("<br><b>").append(refType.name()).append(".").append(fieldName).append("</b>").toString(); |
| } |
| |
| @Override |
| public boolean isEnabled(@NotNull Project project, AnActionEvent event) { |
| final DebuggerTreeNodeImpl node = DebuggerAction.getSelectedNode(event.getDataContext()); |
| return node != null && node.getDescriptor() instanceof ValueDescriptor; |
| } |
| |
| @Override |
| public boolean isHidden(@NotNull Project project, AnActionEvent event) { |
| return DebuggerAction.getSelectedNode(event.getDataContext()) == null; |
| } |
| |
| @Override |
| public boolean isMarked(@NotNull Project project, @NotNull AnActionEvent event) { |
| final DebuggerTreeNodeImpl node = DebuggerAction.getSelectedNode(event.getDataContext()); |
| if (node == null) return false; |
| |
| final NodeDescriptorImpl descriptor = node.getDescriptor(); |
| if (!(descriptor instanceof ValueDescriptor)) return false; |
| |
| DebugProcess debugProcess = node.getTree().getDebuggerContext().getDebugProcess(); |
| return ((ValueDescriptor)descriptor).getMarkup(debugProcess) != null; |
| } |
| |
| public static Color getAutoMarkupColor() { |
| final EditorColorsManager manager = EditorColorsManager.getInstance(); |
| final TextAttributes textAttributes = manager.getGlobalScheme().getAttributes(HighlightInfoType.STATIC_FIELD.getAttributesKey()); |
| return textAttributes.getForegroundColor(); |
| } |
| } |