/* * Copyright 2000-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 com.intellij.xdebugger.impl.breakpoints; import com.intellij.icons.AllIcons; import com.intellij.openapi.actionSystem.ActionGroup; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.components.ComponentSerializationUtil; import com.intellij.openapi.editor.markup.GutterDraggableObject; import com.intellij.openapi.editor.markup.GutterIconRenderer; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.UserDataHolderBase; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.pom.Navigatable; import com.intellij.ui.ColorUtil; import com.intellij.ui.JBColor; import com.intellij.ui.LayeredIcon; import com.intellij.util.StringBuilderSpinAllocator; import com.intellij.util.ui.JBUI; import com.intellij.util.xmlb.SkipDefaultValuesSerializationFilters; import com.intellij.util.xmlb.XmlSerializer; import com.intellij.xdebugger.XDebugSession; import com.intellij.xdebugger.XDebuggerBundle; import com.intellij.xdebugger.XExpression; import com.intellij.xdebugger.XSourcePosition; import com.intellij.xdebugger.breakpoints.SuspendPolicy; import com.intellij.xdebugger.breakpoints.XBreakpoint; import com.intellij.xdebugger.breakpoints.XBreakpointProperties; import com.intellij.xdebugger.breakpoints.XBreakpointType; import com.intellij.xdebugger.impl.DebuggerSupport; import com.intellij.xdebugger.impl.XDebugSessionImpl; import com.intellij.xdebugger.impl.XDebuggerSupport; import com.intellij.xdebugger.impl.XDebuggerUtilImpl; import com.intellij.xdebugger.impl.actions.EditBreakpointAction; import com.intellij.xml.CommonXmlStrings; import com.intellij.xml.util.XmlStringUtil; import org.jdom.Element; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.util.Collections; import java.util.List; /** * @author nik */ public class XBreakpointBase<Self extends XBreakpoint<P>, P extends XBreakpointProperties, S extends BreakpointState> extends UserDataHolderBase implements XBreakpoint<P>, Comparable<Self> { private static final SkipDefaultValuesSerializationFilters SERIALIZATION_FILTERS = new SkipDefaultValuesSerializationFilters(); @NonNls private static final String BR_NBSP = "<br>" + CommonXmlStrings.NBSP; private final XBreakpointType<Self, P> myType; private final @Nullable P myProperties; protected final S myState; private final XBreakpointManagerImpl myBreakpointManager; private Icon myIcon; private CustomizedBreakpointPresentation myCustomizedPresentation; private boolean myConditionEnabled = true; private XExpression myCondition; private boolean myLogExpressionEnabled = true; private XExpression myLogExpression; private volatile boolean myDisposed; public XBreakpointBase(final XBreakpointType<Self, P> type, XBreakpointManagerImpl breakpointManager, final @Nullable P properties, final S state) { myState = state; myType = type; myProperties = properties; myBreakpointManager = breakpointManager; initExpressions(); } protected XBreakpointBase(final XBreakpointType<Self, P> type, XBreakpointManagerImpl breakpointManager, S breakpointState) { myState = breakpointState; myType = type; myBreakpointManager = breakpointManager; myProperties = type.createProperties(); if (myProperties != null) { ComponentSerializationUtil.loadComponentState(myProperties, myState.getPropertiesElement()); } initExpressions(); } private void initExpressions() { myConditionEnabled = myState.isConditionEnabled(); BreakpointState.Condition condition = myState.getCondition(); myCondition = condition != null ? condition.toXExpression() : null; myLogExpressionEnabled = myState.isLogExpressionEnabled(); BreakpointState.LogExpression expression = myState.getLogExpression(); myLogExpression = expression != null ? expression.toXExpression() : null; } public final Project getProject() { return myBreakpointManager.getProject(); } protected XBreakpointManagerImpl getBreakpointManager() { return myBreakpointManager; } public final void fireBreakpointChanged() { clearIcon(); myBreakpointManager.fireBreakpointChanged(this); } @Override public XSourcePosition getSourcePosition() { return getType().getSourcePosition(this); } @Override public Navigatable getNavigatable() { XSourcePosition position = getSourcePosition(); if (position == null) { return null; } return position.createNavigatable(getProject()); } @Override public boolean isEnabled() { return myState.isEnabled(); } @Override public void setEnabled(final boolean enabled) { if (enabled != isEnabled()) { myState.setEnabled(enabled); fireBreakpointChanged(); } } @Override @NotNull public SuspendPolicy getSuspendPolicy() { return myState.getSuspendPolicy(); } @Override public void setSuspendPolicy(@NotNull SuspendPolicy policy) { if (myState.getSuspendPolicy() != policy) { myState.setSuspendPolicy(policy); fireBreakpointChanged(); } } @Override public boolean isLogMessage() { return myState.isLogMessage(); } @Override public void setLogMessage(final boolean logMessage) { if (logMessage != isLogMessage()) { myState.setLogMessage(logMessage); fireBreakpointChanged(); } } public boolean isConditionEnabled() { return myConditionEnabled; } public void setConditionEnabled(boolean conditionEnabled) { if (myConditionEnabled != conditionEnabled) { myConditionEnabled = conditionEnabled; fireBreakpointChanged(); } } public boolean isLogExpressionEnabled() { return myLogExpressionEnabled; } public void setLogExpressionEnabled(boolean logExpressionEnabled) { if (myLogExpressionEnabled != logExpressionEnabled) { myLogExpressionEnabled = logExpressionEnabled; fireBreakpointChanged(); } } @Override public String getLogExpression() { XExpression expression = getLogExpressionObject(); return expression != null ? expression.getExpression() : null; } @Override public void setLogExpression(@Nullable final String expression) { if (!Comparing.equal(getLogExpression(), expression)) { myLogExpression = XExpressionImpl.fromText(expression); fireBreakpointChanged(); } } public XExpression getLogExpressionObjectInt() { return myLogExpression; } @Nullable @Override public XExpression getLogExpressionObject() { return myLogExpressionEnabled ? myLogExpression : null; } @Override public void setLogExpressionObject(@Nullable XExpression expression) { if (!Comparing.equal(myLogExpression, expression)) { myLogExpression = expression; fireBreakpointChanged(); } } @Override public String getCondition() { XExpression expression = getConditionExpression(); return expression != null ? expression.getExpression() : null; } @Override public void setCondition(@Nullable final String condition) { if (!Comparing.equal(condition, getCondition())) { myCondition = XExpressionImpl.fromText(condition); fireBreakpointChanged(); } } public XExpression getConditionExpressionInt() { return myCondition; } @Nullable @Override public XExpression getConditionExpression() { return myConditionEnabled ? myCondition : null; } @Override public void setConditionExpression(@Nullable XExpression condition) { if (!Comparing.equal(condition, myCondition)) { myCondition = condition; fireBreakpointChanged(); } } @Override public long getTimeStamp() { return myState.getTimeStamp(); } public boolean isValid() { return true; } @Override @Nullable public P getProperties() { return myProperties; } @Override @NotNull public XBreakpointType<Self,P> getType() { return myType; } public S getState() { Element propertiesElement = myProperties != null ? XmlSerializer.serialize(myProperties.getState(), SERIALIZATION_FILTERS) : null; myState.setCondition(BreakpointState.Condition.create(!myConditionEnabled, myCondition)); myState.setLogExpression(BreakpointState.LogExpression.create(!myLogExpressionEnabled, myLogExpression)); myState.setPropertiesElement(propertiesElement); return myState; } public XBreakpointDependencyState getDependencyState() { return myState.getDependencyState(); } public void setDependencyState(XBreakpointDependencyState state) { myState.setDependencyState(state); } @Nullable public String getGroup() { return myState.getGroup(); } public void setGroup(String group) { myState.setGroup(StringUtil.nullize(group)); } public String getUserDescription() { return myState.getDescription(); } public void setUserDescription(String description) { myState.setDescription(StringUtil.nullize(description)); } public final void dispose() { myDisposed = true; doDispose(); } protected void doDispose() { } public boolean isDisposed() { return myDisposed; } @Override public String toString() { return "XBreakpointBase(type=" + myType + ")"; } @Nullable protected GutterDraggableObject createBreakpointDraggableObject() { return null; } protected List<? extends AnAction> getAdditionalPopupMenuActions(XDebugSession session) { return Collections.emptyList(); } @NotNull public String getDescription() { @NonNls StringBuilder builder = StringBuilderSpinAllocator.alloc(); try { builder.append(CommonXmlStrings.HTML_START).append(CommonXmlStrings.BODY_START); builder.append(XBreakpointUtil.getDisplayText(this)); String errorMessage = getErrorMessage(); if (!StringUtil.isEmpty(errorMessage)) { builder.append(BR_NBSP); builder.append("<font color='#").append(ColorUtil.toHex(JBColor.RED)).append("'>"); builder.append(errorMessage); builder.append("</font>"); } if (getSuspendPolicy() == SuspendPolicy.NONE) { builder.append(BR_NBSP).append(XDebuggerBundle.message("xbreakpoint.tooltip.suspend.policy.none")); } else if (getType().isSuspendThreadSupported()) { builder.append(BR_NBSP); //noinspection EnumSwitchStatementWhichMissesCases switch (getSuspendPolicy()) { case ALL: builder.append(XDebuggerBundle.message("xbreakpoint.tooltip.suspend.policy.all")); break; case THREAD: builder.append(XDebuggerBundle.message("xbreakpoint.tooltip.suspend.policy.thread")); break; } } String condition = getCondition(); if (!StringUtil.isEmpty(condition)) { builder.append(BR_NBSP); builder.append(XDebuggerBundle.message("xbreakpoint.tooltip.condition")); builder.append(CommonXmlStrings.NBSP); builder.append(XmlStringUtil.escapeString(condition)); } if (isLogMessage()) { builder.append(BR_NBSP).append(XDebuggerBundle.message("xbreakpoint.tooltip.log.message")); } String logExpression = getLogExpression(); if (!StringUtil.isEmpty(logExpression)) { builder.append(BR_NBSP); builder.append(XDebuggerBundle.message("xbreakpoint.tooltip.log.expression")); builder.append(CommonXmlStrings.NBSP); builder.append(XmlStringUtil.escapeString(logExpression)); } XBreakpoint<?> masterBreakpoint = getBreakpointManager().getDependentBreakpointManager().getMasterBreakpoint(this); if (masterBreakpoint != null) { builder.append(BR_NBSP); String str = XDebuggerBundle.message("xbreakpoint.tooltip.depends.on"); builder.append(str); builder.append(CommonXmlStrings.NBSP); builder.append(XBreakpointUtil.getShortText(masterBreakpoint)); } builder.append(CommonXmlStrings.BODY_END).append(CommonXmlStrings.HTML_END); return builder.toString(); } finally { StringBuilderSpinAllocator.dispose(builder); } } protected void updateIcon() { final Icon icon = calculateSpecialIcon(); setIcon(icon != null ? icon : getType().getEnabledIcon()); } protected void setIcon(Icon icon) { if (!XDebuggerUtilImpl.isEmptyExpression(getConditionExpression())) { LayeredIcon newIcon = new LayeredIcon(2); newIcon.setIcon(icon, 0); newIcon.setIcon(AllIcons.Debugger.Question_badge, 1, 10, 6); myIcon = JBUI.scale(newIcon); } else { myIcon = icon; } } @Nullable protected final Icon calculateSpecialIcon() { XDebugSessionImpl session = getBreakpointManager().getDebuggerManager().getCurrentSession(); if (!isEnabled()) { // disabled icon takes precedence to other to visually distinguish it and provide feedback then it is enabled/disabled // (e.g. in case of mute-mode we would like to differentiate muted but enabled breakpoints from simply disabled ones) if (session == null || !session.areBreakpointsMuted()) { return getType().getDisabledIcon(); } else { return getType().getMutedDisabledIcon(); } } if (session == null) { if (getBreakpointManager().getDependentBreakpointManager().getMasterBreakpoint(this) != null) { return getType().getInactiveDependentIcon(); } } else { if (session.areBreakpointsMuted()) { return getType().getMutedEnabledIcon(); } if (session.isInactiveSlaveBreakpoint(this)) { return getType().getInactiveDependentIcon(); } CustomizedBreakpointPresentation presentation = session.getBreakpointPresentation(this); if (presentation != null) { Icon icon = presentation.getIcon(); if (icon != null) { return icon; } } } if (myCustomizedPresentation != null) { final Icon icon = myCustomizedPresentation.getIcon(); if (icon != null) { return icon; } } return null; } public Icon getIcon() { if (myIcon == null) { updateIcon(); } return myIcon; } @Nullable public String getErrorMessage() { final XDebugSessionImpl currentSession = getBreakpointManager().getDebuggerManager().getCurrentSession(); if (currentSession != null) { CustomizedBreakpointPresentation presentation = currentSession.getBreakpointPresentation(this); if (presentation != null) { final String message = presentation.getErrorMessage(); if (message != null) return message; } } return myCustomizedPresentation != null ? myCustomizedPresentation.getErrorMessage() : null; } CustomizedBreakpointPresentation getCustomizedPresentation() { return myCustomizedPresentation; } public void setCustomizedPresentation(CustomizedBreakpointPresentation presentation) { myCustomizedPresentation = presentation; } @NotNull public GutterIconRenderer createGutterIconRenderer() { return new BreakpointGutterIconRenderer(); } public void clearIcon() { myIcon = null; } @Override public int compareTo(@NotNull Self self) { return myType.getBreakpointComparator().compare((Self)this, self); } protected class BreakpointGutterIconRenderer extends GutterIconRenderer implements DumbAware { @Override @NotNull public Icon getIcon() { return XBreakpointBase.this.getIcon(); } @Override @Nullable public AnAction getClickAction() { if (Registry.is("debugger.click.disable.breakpoints")) { return new ToggleBreakpointGutterIconAction(XBreakpointBase.this); } else { return new RemoveBreakpointGutterIconAction(XBreakpointBase.this); } } @Override @Nullable public AnAction getMiddleButtonClickAction() { if (!Registry.is("debugger.click.disable.breakpoints")) { return new ToggleBreakpointGutterIconAction(XBreakpointBase.this); } else { return new RemoveBreakpointGutterIconAction(XBreakpointBase.this); } } @Nullable @Override public AnAction getRightButtonClickAction() { return new EditBreakpointAction.ContextAction(this, XBreakpointBase.this, DebuggerSupport.getDebuggerSupport(XDebuggerSupport.class)); } @NotNull @Override public Alignment getAlignment() { return Alignment.RIGHT; } @Override @Nullable public ActionGroup getPopupMenuActions() { return null; } @Override @Nullable public String getTooltipText() { return getDescription(); } @Override public GutterDraggableObject getDraggableObject() { return createBreakpointDraggableObject(); } private XBreakpointBase<?,?,?> getBreakpoint() { return XBreakpointBase.this; } @Override public boolean equals(Object obj) { return obj instanceof XLineBreakpointImpl.BreakpointGutterIconRenderer && getBreakpoint() == ((XLineBreakpointImpl.BreakpointGutterIconRenderer)obj).getBreakpoint() && Comparing.equal(getIcon(), ((XLineBreakpointImpl.BreakpointGutterIconRenderer)obj).getIcon()); } @Override public int hashCode() { return getBreakpoint().hashCode(); } } }