/core/src/com/bluemarsh/jswat/core/breakpoint/AbstractBreakpoint.java
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}