/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

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