001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.debugger.jpda.breakpoints;
043:
044: import com.sun.jdi.IncompatibleThreadStateException;
045: import com.sun.jdi.ReferenceType;
046: import com.sun.jdi.StackFrame;
047: import com.sun.jdi.ThreadReference;
048: import com.sun.jdi.VMDisconnectedException;
049: import com.sun.jdi.Value;
050: import com.sun.jdi.VirtualMachine;
051: import com.sun.jdi.event.Event;
052: import com.sun.jdi.request.EventRequest;
053: import com.sun.jdi.request.EventRequestManager;
054: import com.sun.jdi.request.StepRequest;
055:
056: import java.awt.Color;
057: import java.awt.GridBagLayout;
058: import java.awt.GridBagConstraints;
059: import java.util.ArrayList;
060: import java.util.LinkedList;
061: import java.util.List;
062: import java.beans.PropertyChangeListener;
063: import java.beans.PropertyChangeEvent;
064: import java.util.logging.Logger;
065: import javax.swing.JCheckBox;
066: import javax.swing.JPanel;
067: import javax.swing.JTextArea;
068: import javax.swing.UIManager;
069: import javax.swing.event.ChangeEvent;
070: import javax.swing.event.ChangeListener;
071:
072: import org.netbeans.api.debugger.Breakpoint;
073: import org.netbeans.api.debugger.jpda.InvalidExpressionException;
074: import org.netbeans.api.debugger.jpda.JPDABreakpoint;
075: import org.netbeans.api.debugger.jpda.MethodBreakpoint;
076: import org.netbeans.api.debugger.jpda.Variable;
077: import org.netbeans.api.debugger.jpda.event.JPDABreakpointEvent;
078: import org.netbeans.api.debugger.jpda.JPDADebugger;
079: import org.netbeans.api.debugger.Session;
080: import org.netbeans.api.debugger.DebuggerManager;
081:
082: import org.netbeans.modules.debugger.jpda.JPDADebuggerImpl;
083: import org.netbeans.modules.debugger.jpda.expr.Expression;
084: import org.netbeans.modules.debugger.jpda.expr.ParseException;
085: import org.netbeans.modules.debugger.jpda.models.JPDAThreadImpl;
086: import org.netbeans.modules.debugger.jpda.models.ReturnVariableImpl;
087: import org.netbeans.modules.debugger.jpda.util.Executor;
088: import org.netbeans.modules.debugger.jpda.util.ThreadInfoPanel;
089:
090: import org.openide.DialogDescriptor;
091: import org.openide.ErrorManager;
092: import org.openide.NotifyDescriptor;
093: import org.openide.util.NbBundle;
094: import org.openide.util.RequestProcessor;
095:
096: /**
097: *
098: * @author Jan Jancura
099: */
100: public abstract class BreakpointImpl implements Executor,
101: PropertyChangeListener {
102:
103: private static Logger logger = Logger
104: .getLogger("org.netbeans.modules.debugger.jpda.breakpoints"); // NOI18N
105:
106: private JPDADebuggerImpl debugger;
107: private JPDABreakpoint breakpoint;
108: private BreakpointsReader reader;
109: private final Session session;
110: private Expression compiledCondition;
111: private List<EventRequest> requests = new ArrayList<EventRequest>();
112: private int hitCountFilter = 0;
113:
114: protected BreakpointImpl(JPDABreakpoint p,
115: BreakpointsReader reader, JPDADebuggerImpl debugger,
116: Session session) {
117: this .debugger = debugger;
118: this .reader = reader;
119: breakpoint = p;
120: this .session = session;
121: }
122:
123: /**
124: * Called from XXXBreakpointImpl constructor only.
125: */
126: final void set() {
127: breakpoint.addPropertyChangeListener(this );
128: update();
129: }
130:
131: /**
132: * Called when Fix&Continue is invoked. Reqritten in LineBreakpointImpl.
133: */
134: void fixed() {
135: if (reader != null) {
136: reader.storeCachedClassName(breakpoint, null);
137: }
138: update();
139: }
140:
141: /**
142: * Called from set () and propertyChanged.
143: */
144: final void update() {
145: if ((getVirtualMachine() == null)
146: || (getDebugger().getState() == JPDADebugger.STATE_DISCONNECTED))
147: return;
148: removeAllEventRequests();
149: if (breakpoint.isEnabled() && isEnabled()) {
150: setRequests();
151: }
152: }
153:
154: protected boolean isEnabled() {
155: return true;
156: }
157:
158: protected final void setValidity(Breakpoint.VALIDITY validity,
159: String reason) {
160: if (breakpoint instanceof ChangeListener) {
161: ((ChangeListener) breakpoint)
162: .stateChanged(new ValidityChangeEvent(validity,
163: reason));
164: }
165: }
166:
167: public void propertyChange(PropertyChangeEvent evt) {
168: String propertyName = evt.getPropertyName();
169: if (Breakpoint.PROP_DISPOSED.equals(propertyName)) {
170: remove();
171: } else if (!Breakpoint.PROP_VALIDITY.equals(propertyName)
172: && !Breakpoint.PROP_GROUP_NAME.equals(propertyName)) {
173: if (reader != null) {
174: reader.storeCachedClassName(breakpoint, null);
175: }
176: RequestProcessor.getDefault().post(new Runnable() {
177: public void run() {
178: // Update lazily in RP. We'll access java source parsing and JDI.
179: update();
180: }
181: });
182: }
183: }
184:
185: protected abstract void setRequests();
186:
187: protected void remove() {
188: removeAllEventRequests();
189: breakpoint.removePropertyChangeListener(this );
190: setValidity(Breakpoint.VALIDITY.UNKNOWN, null);
191: }
192:
193: protected JPDABreakpoint getBreakpoint() {
194: return breakpoint;
195: }
196:
197: protected JPDADebuggerImpl getDebugger() {
198: return debugger;
199: }
200:
201: protected VirtualMachine getVirtualMachine() {
202: return getDebugger().getVirtualMachine();
203: }
204:
205: protected EventRequestManager getEventRequestManager() {
206: VirtualMachine vm = getVirtualMachine();
207: if (vm == null) {
208: // Already disconnected
209: throw new VMDisconnectedException();
210: }
211: return vm.eventRequestManager();
212: }
213:
214: protected void addEventRequest(EventRequest r) {
215: addEventRequest(r, false);
216: }
217:
218: synchronized protected void addEventRequest(EventRequest r,
219: boolean ignoreHitCount) {
220: logger.fine("BreakpointImpl addEventRequest: " + r);
221: requests.add(r);
222: getDebugger().getOperator().register(r, this );
223:
224: // PATCH #48174
225: // if this is breakpoint with SUSPEND_NONE we stop EVENT_THREAD to print output line
226: if (getBreakpoint().getSuspend() == JPDABreakpoint.SUSPEND_ALL)
227: r.setSuspendPolicy(JPDABreakpoint.SUSPEND_ALL);
228: else
229: r.setSuspendPolicy(JPDABreakpoint.SUSPEND_EVENT_THREAD);
230: int hitCountFilter = getBreakpoint().getHitCountFilter();
231: if (!ignoreHitCount && hitCountFilter > 0) {
232: r.addCountFilter(hitCountFilter);
233: switch (getBreakpoint().getHitCountFilteringStyle()) {
234: case MULTIPLE:
235: this .hitCountFilter = hitCountFilter;
236: break;
237: case EQUAL:
238: this .hitCountFilter = 0;
239: break;
240: case GREATER:
241: this .hitCountFilter = -1;
242: break;
243: default:
244: throw new IllegalStateException(getBreakpoint()
245: .getHitCountFilteringStyle().name());
246: }
247: } else {
248: this .hitCountFilter = 0;
249: }
250: r.enable();
251: }
252:
253: synchronized private void removeAllEventRequests() {
254: if (requests.size() == 0)
255: return;
256: VirtualMachine vm = getDebugger().getVirtualMachine();
257: if (vm == null)
258: return;
259: int i, k = requests.size();
260: try {
261: for (i = 0; i < k; i++) {
262: EventRequest r = requests.get(i);
263: logger.fine("BreakpointImpl removeEventRequest: " + r);
264: vm.eventRequestManager().deleteEventRequest(r);
265: getDebugger().getOperator().unregister(r);
266: }
267:
268: } catch (VMDisconnectedException e) {
269: } catch (com.sun.jdi.InternalException e) {
270: }
271: requests = new LinkedList<EventRequest>();
272: }
273:
274: synchronized private void removeEventRequest(EventRequest r) {
275: VirtualMachine vm = getDebugger().getVirtualMachine();
276: if (vm == null)
277: return;
278: try {
279: logger.fine("BreakpointImpl removeEventRequest: " + r);
280: vm.eventRequestManager().deleteEventRequest(r);
281: getDebugger().getOperator().unregister(r);
282: } catch (VMDisconnectedException e) {
283: } catch (com.sun.jdi.InternalException e) {
284: }
285: requests.remove(r);
286: }
287:
288: /** Called when a new event request needs to be created, e.g. after hit count
289: * was met and hit count style is "greater than".
290: */
291: protected abstract EventRequest createEventRequest(
292: EventRequest oldRequest);
293:
294: protected boolean perform(Event event, String condition,
295: ThreadReference thread, ReferenceType referenceType,
296: Value value) {
297: //S ystem.out.println("BreakpointImpl.perform");
298: boolean resume;
299:
300: if (hitCountFilter > 0) {
301: event.request().disable();
302: //event.request().addCountFilter(hitCountFilter);
303: // This submits the event with the filter again
304: event.request().enable();
305: }
306: if (hitCountFilter == -1) {
307: event.request().disable();
308: removeEventRequest(event.request());
309: addEventRequest(createEventRequest(event.request()), true);
310: }
311: //PATCH 48174
312: try {
313: getDebugger().setAltCSF(thread.frame(0));
314: } catch (com.sun.jdi.IncompatibleThreadStateException e) {
315: ErrorManager.getDefault().notify(e);
316: } catch (java.lang.IndexOutOfBoundsException e) {
317: // No frame in case of Thread and "Main" class breakpoints, PATCH 56540
318: }
319: Variable variable = null;
320: if (getBreakpoint() instanceof MethodBreakpoint
321: && (((MethodBreakpoint) getBreakpoint())
322: .getBreakpointType() & MethodBreakpoint.TYPE_METHOD_EXIT) != 0) {
323: JPDAThreadImpl jt = (JPDAThreadImpl) getDebugger()
324: .getThread(thread);
325: if (value != null) {
326: ReturnVariableImpl retVariable = new ReturnVariableImpl(
327: getDebugger(), value, "", jt.getMethodName());
328: jt.setReturnVariable(retVariable);
329: variable = retVariable;
330: }
331: }
332: if (variable == null) {
333: variable = debugger.getVariable(value);
334: }
335:
336: if ((condition == null) || condition.equals("")) {
337: JPDABreakpointEvent e = new JPDABreakpointEvent(
338: getBreakpoint(), debugger,
339: JPDABreakpointEvent.CONDITION_NONE, debugger
340: .getThread(thread), referenceType, variable);
341: getDebugger().fireBreakpointEvent(getBreakpoint(), e);
342: resume = getBreakpoint().getSuspend() == JPDABreakpoint.SUSPEND_NONE
343: || e.getResume();
344: logger
345: .fine("BreakpointImpl: perform breakpoint (no condition): "
346: + this + " resume: " + resume);
347: } else {
348: resume = evaluateCondition(condition, thread,
349: referenceType, variable);
350: //PATCH 48174
351: resume = getBreakpoint().getSuspend() == JPDABreakpoint.SUSPEND_NONE
352: || resume;
353: }
354: getDebugger().setAltCSF(null);
355: if (!resume) {
356: resume = checkWhetherResumeToFinishStep(thread);
357: if (!resume) {
358: DebuggerManager.getDebuggerManager().setCurrentSession(
359: session);
360: getDebugger().setStoppedState(thread);
361: }
362: }
363: //S ystem.out.println("BreakpointImpl.perform end");
364: return resume;
365: }
366:
367: private boolean checkWhetherResumeToFinishStep(
368: ThreadReference thread) {
369: List<StepRequest> stepRequests = thread.virtualMachine()
370: .eventRequestManager().stepRequests();
371: if (stepRequests.size() > 0) {
372: int suspendState = breakpoint.getSuspend();
373: if (suspendState == JPDABreakpoint.SUSPEND_ALL
374: || suspendState == JPDABreakpoint.SUSPEND_EVENT_THREAD) {
375:
376: boolean this ThreadHasStep = false;
377: List<StepRequest> activeStepRequests = new ArrayList<StepRequest>(
378: stepRequests);
379: for (int i = 0; i < activeStepRequests.size(); i++) {
380: StepRequest step = activeStepRequests.get(i);
381: ThreadReference stepThread = step.thread();
382: if (!step.isEnabled()) {
383: activeStepRequests.remove(i);
384: continue;
385: }
386: if (step.thread().status() == ThreadReference.THREAD_STATUS_ZOMBIE) {
387: thread.virtualMachine().eventRequestManager()
388: .deleteEventRequest(step);
389: debugger.getOperator().unregister(step);
390: activeStepRequests.remove(i);
391: continue;
392: }
393: if (thread.equals(stepThread)) {
394: this ThreadHasStep = true;
395: }
396: }
397: if (this ThreadHasStep) { // remove this if the debugger should warn you in the same thread as well. See #104101.
398: return false;
399: }
400: if (activeStepRequests.size() > 0
401: && (this ThreadHasStep || suspendState == JPDABreakpoint.SUSPEND_ALL)) {
402: Boolean resumeDecision = debugger
403: .getStepInterruptByBptResumeDecision();
404: if (resumeDecision != null) {
405: return resumeDecision.booleanValue();
406: }
407: final String message;
408: if (this ThreadHasStep) {
409: message = NbBundle.getMessage(
410: BreakpointImpl.class,
411: "MSG_StepThreadInterruptedByBR",
412: breakpoint.toString());
413: } else {
414: message = NbBundle.getMessage(
415: BreakpointImpl.class,
416: "MSG_StepInterruptedByBR", breakpoint
417: .toString(), thread.name(),
418: activeStepRequests.get(0).thread()
419: .name());
420: }
421: final ThreadInfoPanel[] tiPanelRef = new ThreadInfoPanel[] { null };
422: try {
423: javax.swing.SwingUtilities
424: .invokeAndWait(new Runnable() {
425: public void run() {
426: tiPanelRef[0] = ThreadInfoPanel
427: .create(
428: message,
429: NbBundle
430: .getMessage(
431: BreakpointImpl.class,
432: "StepInterruptedByBR_Btn1"),
433: NbBundle
434: .getMessage(
435: BreakpointImpl.class,
436: "StepInterruptedByBR_Btn1_TIP"),
437: NbBundle
438: .getMessage(
439: BreakpointImpl.class,
440: "StepInterruptedByBR_Btn2"),
441: NbBundle
442: .getMessage(
443: BreakpointImpl.class,
444: "StepInterruptedByBR_Btn2_TIP"));
445: }
446: });
447: } catch (InterruptedException iex) {
448: } catch (java.lang.reflect.InvocationTargetException itex) {
449: ErrorManager.getDefault().notify(itex);
450: }
451: if (tiPanelRef[0] == null) {
452: return false;
453: }
454: tiPanelRef[0]
455: .setButtonListener(new ThreadInfoPanel.ButtonListener() {
456: public void buttonPressed(int n) {
457: if (n == 2) {
458: debugger
459: .setStepInterruptByBptResumeDecision(Boolean.TRUE);
460: }
461: debugger.resume();
462: }
463: });
464: debugger
465: .addPropertyChangeListener(new PropertyChangeListener() {
466: public void propertyChange(
467: PropertyChangeEvent pe) {
468: if (pe.getPropertyName().equals(
469: debugger.PROP_STATE)) {
470: if (pe.getNewValue().equals(
471: debugger.STATE_RUNNING)) {
472: debugger
473: .removePropertyChangeListener(this );
474: tiPanelRef[0].dismiss();
475: }
476: }
477: }
478: });
479: return false;
480:
481: /*
482: JCheckBox cb = new JCheckBox(NbBundle.getMessage(BreakpointImpl.class, "RememberDecision"));
483: DialogDescriptor dd = new DialogDescriptor(
484: //message,
485: createDlgPanel(message, cb),
486: new NotifyDescriptor.Confirmation(message, NotifyDescriptor.YES_NO_OPTION).getTitle(),
487: true,
488: NotifyDescriptor.YES_NO_OPTION,
489: null,
490: null);
491: dd.setMessageType(NotifyDescriptor.QUESTION_MESSAGE);
492: // Set the stopped state to show the breakpoint location
493: DebuggerManager.getDebuggerManager().setCurrentSession(session);
494: getDebugger ().setStoppedState (thread);
495: Object option = org.openide.DialogDisplayer.getDefault().notify(dd);
496: boolean yes = option == NotifyDescriptor.YES_OPTION;
497: boolean no = option == NotifyDescriptor.NO_OPTION;
498: if (cb.isSelected() && (yes || no)) {
499: debugger.setStepInterruptByBptResumeDecision(Boolean.valueOf(yes));
500: }
501: if (yes) {
502: // We'll resume...
503: getDebugger ().setRunningState();
504: }
505: if (no) {
506: // The user wants to stop on the breakpoint, remove
507: // the step requests to prevent confusion
508: for (StepRequest step : activeStepRequests) {
509: thread.virtualMachine().eventRequestManager().deleteEventRequest(step);
510: debugger.getOperator().unregister(step);
511: }
512: }
513: return yes;
514: */
515: }
516: }
517: }
518: return false;
519: }
520:
521: /*
522: private static JPanel createDlgPanel(String message, JCheckBox cb) {
523: JPanel panel = new JPanel();
524: panel.setLayout(new GridBagLayout());
525: GridBagConstraints c = new GridBagConstraints();
526: c.anchor = GridBagConstraints.WEST;
527: JTextArea area = new JTextArea(message);
528: Color color = UIManager.getColor("Label.background"); // NOI18N
529: if (color != null) {
530: area.setBackground(color);
531: }
532: //area.setLineWrap(true);
533: //area.setWrapStyleWord(true);
534: area.setEditable(false);
535: area.setTabSize(4); // looks better for module sys messages than 8
536: panel.add(area, c);
537: c = new GridBagConstraints();
538: c.gridx = 0;
539: c.gridy = 1;
540: c.anchor = GridBagConstraints.WEST;
541: c.insets = new java.awt.Insets(12, 0, 0, 0);
542: panel.add(cb, c);
543: return panel;
544: }
545: */
546:
547: private boolean evaluateCondition(String condition,
548: ThreadReference thread, ReferenceType referenceType,
549: Variable variable) {
550: try {
551: try {
552: boolean result;
553: JPDABreakpointEvent ev;
554: synchronized (debugger.LOCK) {
555: StackFrame sf = thread.frame(0);
556: result = evaluateConditionIn(condition, sf, 0);
557: ev = new JPDABreakpointEvent(
558: getBreakpoint(),
559: debugger,
560: result ? JPDABreakpointEvent.CONDITION_TRUE
561: : JPDABreakpointEvent.CONDITION_FALSE,
562: debugger.getThread(thread), referenceType,
563: variable);
564: }
565: getDebugger().fireBreakpointEvent(getBreakpoint(), ev);
566:
567: // condition true => stop here (do not resume)
568: // condition false => resume
569: logger
570: .fine("BreakpointImpl: perform breakpoint (condition = "
571: + result
572: + "): "
573: + this
574: + " resume: "
575: + (!result || ev.getResume()));
576: return !result || ev.getResume();
577: } catch (ParseException ex) {
578: JPDABreakpointEvent ev = new JPDABreakpointEvent(
579: getBreakpoint(), debugger, ex, debugger
580: .getThread(thread), referenceType,
581: variable);
582: getDebugger().fireBreakpointEvent(getBreakpoint(), ev);
583: logger
584: .fine("BreakpointImpl: perform breakpoint (bad condition): "
585: + this + " resume: " + ev.getResume());
586: return ev.getResume();
587: } catch (InvalidExpressionException ex) {
588: JPDABreakpointEvent ev = new JPDABreakpointEvent(
589: getBreakpoint(), debugger, ex, debugger
590: .getThread(thread), referenceType,
591: variable);
592: getDebugger().fireBreakpointEvent(getBreakpoint(), ev);
593: logger
594: .fine("BreakpointImpl: perform breakpoint (invalid condition): "
595: + this + " resume: " + ev.getResume());
596: return ev.getResume();
597: }
598: } catch (IncompatibleThreadStateException ex) {
599: // should not occurre
600: ex.printStackTrace();
601: }
602: // some error occured during evaluation of expression => do not resume
603: return false; // do not resume
604: }
605:
606: /**
607: * Evaluates given condition. Returns value of condition evaluation.
608: * Returns true othervise (bad expression).
609: */
610: private boolean evaluateConditionIn(String condExpr,
611: StackFrame frame, int frameDepth) throws ParseException,
612: InvalidExpressionException {
613: // 1) compile expression
614: if (compiledCondition == null
615: || !compiledCondition.getExpression().equals(condExpr))
616: compiledCondition = Expression.parse(condExpr,
617: Expression.LANGUAGE_JAVA_1_5);
618:
619: // 2) evaluate expression
620: // already synchronized (debugger.LOCK)
621: com.sun.jdi.Value value = getDebugger().evaluateIn(
622: compiledCondition, frame, frameDepth);
623: try {
624: return ((com.sun.jdi.BooleanValue) value).booleanValue();
625: } catch (ClassCastException e) {
626: throw new InvalidExpressionException(e);
627: } catch (NullPointerException npe) {
628: throw new InvalidExpressionException(npe);
629: }
630: }
631:
632: /**
633: * Support method for simple patterns.
634: */
635: static boolean match(String name, String pattern) {
636: if (pattern.startsWith("*"))
637: return name.endsWith(pattern.substring(1));
638: else if (pattern.endsWith("*"))
639: return name.startsWith(pattern.substring(0, pattern
640: .length() - 1));
641: return name.equals(pattern);
642: }
643:
644: private static final class ValidityChangeEvent extends ChangeEvent {
645:
646: private String reason;
647:
648: public ValidityChangeEvent(Breakpoint.VALIDITY validity,
649: String reason) {
650: super (validity);
651: this .reason = reason;
652: }
653:
654: public String toString() {
655: return reason;
656: }
657: }
658: }
|