PageRenderTime 29ms CodeModel.GetById 14ms app.highlight 13ms RepoModel.GetById 0ms app.codeStats 0ms

/core/src/com/bluemarsh/jswat/core/breakpoint/AbstractBreakpoint.java

http://jswat.googlecode.com/
Java | 514 lines | 322 code | 46 blank | 146 comment | 56 complexity | b4a2ca5212ca0b288f9fac9c93a3bb98 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception
  1/*
  2 * The contents of this file are subject to the terms of the Common Development
  3 * and Distribution License (the License). You may not use this file except in
  4 * compliance with the License.
  5 *
  6 * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
  7 * or http://www.netbeans.org/cddl.txt.
  8 *
  9 * When distributing Covered Code, include this CDDL Header Notice in each file
 10 * and include the License file at http://www.netbeans.org/cddl.txt.
 11 * If applicable, add the following below the CDDL Header, with the fields
 12 * enclosed by brackets [] replaced by your own identifying information:
 13 * "Portions Copyrighted [year] [name of copyright owner]"
 14 *
 15 * The Original Software is JSwat. The Initial Developer of the Original
 16 * Software is Nathan L. Fiedler. Portions created by Nathan L. Fiedler
 17 * are Copyright (C) 2001-2010. All Rights Reserved.
 18 *
 19 * Contributor(s): Nathan L. Fiedler.
 20 *
 21 * $Id: AbstractBreakpoint.java 276 2010-11-14 16:04:28Z nathanfiedler $
 22 */
 23package com.bluemarsh.jswat.core.breakpoint;
 24
 25import com.bluemarsh.jswat.core.event.Dispatcher;
 26import com.bluemarsh.jswat.core.event.DispatcherListener;
 27import com.bluemarsh.jswat.core.event.DispatcherProvider;
 28import com.bluemarsh.jswat.core.session.Session;
 29import com.sun.jdi.ThreadReference;
 30import com.sun.jdi.event.Event;
 31import com.sun.jdi.event.LocatableEvent;
 32import com.sun.jdi.request.EventRequest;
 33import java.beans.PropertyChangeListener;
 34import java.beans.PropertyChangeSupport;
 35import java.util.HashMap;
 36import java.util.LinkedList;
 37import java.util.List;
 38import java.util.ListIterator;
 39import java.util.Map;
 40
 41/**
 42 * Class AbstractBreakpoint is an abstract implementation of the Breakpoint
 43 * interface. It implements most of the basic behavior of breakpoints.
 44 *
 45 * @author  Nathan Fiedler
 46 */
 47public abstract class AbstractBreakpoint implements Breakpoint, DispatcherListener {
 48
 49    /** The thread suspension policy requested by the user. Must be one of
 50     * the <code>com.sun.jdi.request.EventRequest</code> suspend constants.
 51     * Defaults to <code>SUSPEND_ALL</code>. */
 52    private int suspendPolicy = EventRequest.SUSPEND_ALL;
 53    /** If true, force the debuggee to suspend, regardless of the suspend
 54     * policy chosen by the user. This is to accomodate monitors that
 55     * require a suspended debuggee in order to perform. */
 56    private boolean forceSuspend;
 57    /** True if this breakpoint is enabled. */
 58    private boolean isEnabled;
 59    /** Breakpoint group that contains us (always non-null). */
 60    private BreakpointGroup breakpointGroup;
 61    /** Number of times this breakpoint has been hit. */
 62    private int hitCount;
 63    /** List of conditions this breakpoint depends on. */
 64    private final List<Condition> conditionList;
 65    /** List of monitors this breakpoint executes when it stops. */
 66    private final List<Monitor> monitorList;
 67    /** Class filter, appropriate for JDI event requests. */
 68    private String classFilter;
 69    /** Thread filter, appropriate for JDI event requests. */
 70    private String threadFilter;
 71    /** True if the breakpoint should be deleted after being hit. */
 72    private boolean deleteWhenHit;
 73    /** Handles property change listeners and sending events. */
 74    protected final PropertyChangeSupport propSupport;
 75    /** Map of the properties set in this breakpoint. */
 76    private Map<String, Object> propertiesMap;
 77    /** List of breakpoint listeners. */
 78    private BreakpointListener listeners;
 79
 80    /**
 81     * Creates a AbstractBreakpoint with the default parameters.
 82     */
 83    public AbstractBreakpoint() {
 84        conditionList = new LinkedList<Condition>();
 85        monitorList = new LinkedList<Monitor>();
 86        isEnabled = true;
 87        propSupport = new PropertyChangeSupport(this);
 88        propertiesMap = new HashMap<String, Object>();
 89    }
 90
 91    @Override
 92    public void addBreakpointListener(BreakpointListener listener) {
 93        if (listener != null) {
 94            synchronized (this) {
 95                listeners = BreakpointEventMulticaster.add(listeners, listener);
 96            }
 97            propSupport.addPropertyChangeListener(listener);
 98        }
 99    }
100
101    @Override
102    public void addCondition(Condition condition) {
103        if (condition == null) {
104            throw new IllegalArgumentException("null condition not permitted");
105        }
106        synchronized (conditionList) {
107            conditionList.add(condition);
108        }
109    }
110
111    @Override
112    public void addMonitor(Monitor monitor) {
113        if (monitor == null) {
114            throw new IllegalArgumentException("null monitor not permitted");
115        }
116        synchronized (monitorList) {
117            monitorList.add(monitor);
118        }
119        // Update the suspend policy of the breakpoint requests.
120        setSuspendPolicy(getSuspendPolicy());
121    }
122
123    @Override
124    public void addPropertyChangeListener(PropertyChangeListener listener) {
125        propSupport.addPropertyChangeListener(listener);
126    }
127
128    /**
129     * Applies the effective suspend policy of this breakpoint to the given
130     * JDI event request. This takes into account any monitors that require
131     * the debuggee to be suspended in order to perform.
132     *
133     * @param  request  event request to apply suspend policy.
134     */
135    protected void applySuspendPolicy(EventRequest request) {
136        request.setSuspendPolicy(forceSuspend ? EventRequest.SUSPEND_ALL
137                : getSuspendPolicy());
138    }
139
140    @Override
141    public ListIterator<Condition> conditions() {
142        return conditionList.listIterator();
143    }
144
145    /**
146     * Delete the event requests created by this breakpoint. Called by the
147     * destroy() method, when the event requests are no longer needed.
148     */
149    protected abstract void deleteRequests();
150
151    @Override
152    public void destroy() {
153        deleteRequests();
154        conditionList.clear();
155        monitorList.clear();
156    }
157
158    @Override
159    public boolean eventOccurred(Event event) {
160        //
161        // This method lives here because the logic of evaluating the event
162        // and processing it is common for all of the breakpoint types. Each
163        // breakpoint type creates its requests and registers the request
164        // with the event dispatcher. When the event for the corresponding
165        // request occurs, this method does the standard processing.
166        //
167        boolean resume = true;
168        if (isEnabled()) {
169            // Only count hits for enabled breakpoints. Each hit must be
170            // counted so the hit count condition evaluation can work.
171            hitCount++;
172            // Check the filters and conditions.
173            resume = shouldResume(event);
174            if (!resume) {
175                resume = performStop(event);
176                // Do nothing else as we may have just been deleted.
177            }
178        }
179        return resume;
180    }
181
182    /**
183     * Notify breakpoint listeners that this breakpoint experienced
184     * an exceptional event.
185     *
186     * @param  exc  exception that occurred.
187     */
188    protected void fireError(Exception exc) {
189        fireEvent(new BreakpointEvent(this, exc));
190    }
191
192    /**
193     * Let the breakpoint listeners know of an event in this breakpoint.
194     *
195     * @param  e  the breakpoint event.
196     */
197    protected void fireEvent(BreakpointEvent e) {
198        BreakpointListener bl;
199        synchronized (this) {
200            bl = listeners;
201        }
202        if (bl != null) {
203            e.getType().fireEvent(e, bl);
204        }
205    }
206
207    @Override
208    public BreakpointGroup getBreakpointGroup() {
209        return breakpointGroup;
210    }
211
212    @Override
213    public String getClassFilter() {
214        return classFilter;
215    }
216
217    @Override
218    public int getHitCount() {
219        return hitCount;
220    }
221
222    @Override
223    public Object getProperty(String name) {
224        return propertiesMap.get(name);
225    }
226
227    @Override
228    public int getSuspendPolicy() {
229        // Return the suspend policy selected by the user, regardless
230        // of the attached monitors and their requirements.
231        return suspendPolicy;
232    }
233
234    @Override
235    public String getThreadFilter() {
236        return threadFilter;
237    }
238
239    @Override
240    public boolean isEnabled() {
241        BreakpointGroup parent = getBreakpointGroup();
242        if (parent != null) {
243            return parent.isEnabled() ? isEnabled : false;
244        } else {
245            return isEnabled;
246        }
247    }
248
249    @Override
250    public abstract boolean isResolved();
251
252    @Override
253    public ListIterator<Monitor> monitors() {
254        return monitorList.listIterator();
255    }
256
257    /**
258     * This breakpoint has caused the debuggee VM to stop. Execute all
259     * monitors associated with this breakpoint.
260     *
261     * @param  e  Event for which we are stopping.
262     * @return  true if VM should resume, false otherwise.
263     */
264    protected boolean performStop(Event e) {
265        BreakpointEvent be = new BreakpointEvent(this,
266                BreakpointEventType.STOPPED, e);
267        fireEvent(be);
268        runMonitors(be);
269        if (deleteWhenHit) {
270            // Let listeners know we should be deleted. Hopefully one of
271            // them (e.g. breakpoint manager) will actually remove us.
272            fireEvent(new BreakpointEvent(this, BreakpointEventType.REMOVED, e));
273        }
274        // Return true if our policy is to not suspend any threads.
275        return suspendPolicy == EventRequest.SUSPEND_NONE;
276    }
277
278    /**
279     * Register this breakpoint as a listener for the given event request,
280     * such that the event dispatcher will invoke this breakpoint when
281     * events related to this request occur. Also sets the suspend policy
282     * and the enabled state based on the properties of this breakpoint.
283     *
284     * @param  request  event request to be registered.
285     */
286    protected void register(EventRequest request) {
287        BreakpointGroup group = getBreakpointGroup();
288        // Without a breakpoint group, we do not exist.
289        if (group != null) {
290            Session session = BreakpointProvider.getSession(group);
291            Dispatcher dispatcher = DispatcherProvider.getDispatcher(session);
292            dispatcher.register(this, request);
293            applySuspendPolicy(request);
294            request.setEnabled(isEnabled());
295        }
296    }
297
298    @Override
299    public void removeBreakpointListener(BreakpointListener listener) {
300        if (listener != null) {
301            synchronized (this) {
302                listeners = BreakpointEventMulticaster.remove(listeners, listener);
303            }
304            propSupport.removePropertyChangeListener(listener);
305        }
306    }
307
308    @Override
309    public void removeCondition(Condition condition) {
310        synchronized (conditionList) {
311            conditionList.remove(condition);
312        }
313    }
314
315    @Override
316    public void removeMonitor(Monitor monitor) {
317        synchronized (monitorList) {
318            monitorList.remove(monitor);
319        }
320        // Update the suspend policy of the breakpoint requests.
321        setSuspendPolicy(getSuspendPolicy());
322    }
323
324    @Override
325    public void removePropertyChangeListener(PropertyChangeListener listener) {
326        propSupport.removePropertyChangeListener(listener);
327    }
328
329    @Override
330    public void reset() {
331        hitCount = 0;
332    }
333
334    /**
335     * Run the monitors associated with this breakpoint and its group.
336     *
337     * @param  event  breakpoint event.
338     */
339    protected void runMonitors(BreakpointEvent event) {
340        // We are not expecting multiple threads to modify this list,
341        // but if it does happen, an exception will be thrown.
342        for (Monitor monitor : monitorList) {
343            try {
344                monitor.perform(event);
345            } catch (Exception e) {
346                fireError(e);
347            }
348        }
349        getBreakpointGroup().runMonitors(event);
350    }
351
352    @Override
353    public void setBreakpointGroup(BreakpointGroup group) {
354        BreakpointGroup old = breakpointGroup;
355        breakpointGroup = group;
356        propSupport.firePropertyChange(PROP_BREAKPOINTGROUP, old, group);
357    }
358
359    @Override
360    public void setClassFilter(String filter) {
361        if (!canFilterClass() && filter != null && filter.length() > 0) {
362            throw new IllegalArgumentException(
363                    "breakpoint does not support class filters");
364        }
365        String old = classFilter;
366        if (filter != null && filter.isEmpty()) {
367            // Property editor doesn't let user delete, so use blank
368            // as the indication to delete the filter.
369            classFilter = null;
370        } else {
371            classFilter = filter;
372        }
373        propSupport.firePropertyChange(PROP_CLASSFILTER, old, classFilter);
374    }
375
376    @Override
377    public void setDeleteWhenHit(boolean delete) {
378        deleteWhenHit = delete;
379    }
380
381    @Override
382    public void setEnabled(boolean enabled) {
383        boolean old = isEnabled;
384        isEnabled = enabled;
385        propSupport.firePropertyChange(PROP_ENABLED, old, enabled);
386    }
387
388    @Override
389    public void setExpireCount(int expireCount) {
390        // Take no action, for backward compatibility.
391    }
392
393    @Override
394    public Object setProperty(String name, Object value) {
395        Object rv = propertiesMap.put(name, value);
396        propSupport.firePropertyChange(name, rv, value);
397        return rv;
398    }
399
400    @Override
401    public void setSkipCount(int skipCount) {
402        // Take no action, for backward compatibility.
403    }
404
405    @Override
406    public void setSuspendPolicy(int policy) {
407        if ((policy != EventRequest.SUSPEND_ALL)
408                && (policy != EventRequest.SUSPEND_EVENT_THREAD)
409                && (policy != EventRequest.SUSPEND_NONE)) {
410            throw new IllegalArgumentException("invalid suspend policy: " + policy);
411        }
412        int old = suspendPolicy;
413        suspendPolicy = policy;
414        // Determine if we require the debuggee to always suspend.
415        forceSuspend = false;
416        for (Monitor monitor : monitorList) {
417            if (monitor.requiresThread()) {
418                // Found a monitor that requires a suspended debuggee.
419                forceSuspend = true;
420                break;
421            }
422        }
423        propSupport.firePropertyChange(PROP_SUSPENDPOLICY, old, policy);
424    }
425
426    @Override
427    public void setThreadFilter(String filter) {
428        if (!canFilterThread() && filter != null && filter.length() > 0) {
429            throw new IllegalArgumentException(
430                    "breakpoint does not support thread filters");
431        }
432        String old = threadFilter;
433        if (filter != null && filter.length() == 0) {
434            // Property editor doesn't let user delete, so use blank
435            // as the indication to delete the filter.
436            threadFilter = null;
437        } else {
438            threadFilter = filter;
439        }
440        propSupport.firePropertyChange(PROP_THREADFILTER, old, threadFilter);
441    }
442
443    /**
444     * Determines if this breakpoint is to halt execution. Technically
445     * execution has already stopped. This method simply indicates
446     * whether the debuggee VM should be resumed or not. This method checks
447     * if a thread filter is in effect and if there is a match, as well as
448     * consulting any registered conditions to ensure they are satisfied.
449     * The conditions of the parent group, and it's parent and so on, are
450     * also considered prior to this method returning.
451     *
452     * @param  event  JDI Event that brought us here.
453     * @return  true if debuggee VM should resume, false otherwise.
454     */
455    protected boolean shouldResume(Event event) {
456        // Check the thread filter to see if there is a match.
457        if (event instanceof LocatableEvent) {
458            String filter = getThreadFilter();
459            if (filter != null && filter.length() > 0) {
460                LocatableEvent le = (LocatableEvent) event;
461                ThreadReference thread = le.thread();
462                if (!filter.equals(thread.name())) {
463                    // Not a match, resume the debuggee.
464                    return true;
465                }
466            }
467        }
468
469        // Check that the conditions are all satisfied.
470        // We start by assuming they are satisfied.
471        boolean satisfied = true;
472        // We are not expecting multiple threads to modify this list,
473        // but if it does happen, an exception will be thrown.
474        for (Condition condition : conditionList) {
475            try {
476                if (!condition.isSatisfied(this, event)) {
477                    satisfied = false;
478                    break;
479                }
480            } catch (Exception e) {
481                fireError(e);
482            }
483        }
484
485        // Check the parent group to see if its conditions are satisfied.
486        // Note the reversal of the boolean, since we are determining if
487        // the debuggee should resume or not.
488        if (satisfied) {
489            return !breakpointGroup.conditionsSatisfied(this, event);
490        } else {
491            return true;
492        }
493    }
494
495    @Override
496    public String toString() {
497        return getDescription();
498    }
499
500    /**
501     * Unregister this breakpoint from the given event request.
502     *
503     * @param  request  event request.
504     */
505    protected void unregister(EventRequest request) {
506        BreakpointGroup group = getBreakpointGroup();
507        // Without a breakpoint group, we do not exist.
508        if (group != null) {
509            Session session = BreakpointProvider.getSession(group);
510            Dispatcher dispatcher = DispatcherProvider.getDispatcher(session);
511            dispatcher.unregister(request);
512        }
513    }
514}