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-2006 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.models;
043:
044: import com.sun.jdi.AbsentInformationException;
045: import com.sun.jdi.IncompatibleThreadStateException;
046: import java.beans.PropertyChangeEvent;
047: import java.beans.PropertyChangeListener;
048: import java.lang.ref.WeakReference;
049: import java.util.Collection;
050: import java.util.HashSet;
051:
052: import org.netbeans.api.debugger.jpda.CallStackFrame;
053: import org.netbeans.api.debugger.jpda.JPDAThread;
054: import org.netbeans.spi.debugger.ContextProvider;
055: import org.netbeans.api.debugger.jpda.JPDADebugger;
056: import org.netbeans.spi.viewmodel.ModelEvent;
057: import org.netbeans.spi.viewmodel.TreeModel;
058: import org.netbeans.spi.viewmodel.ModelListener;
059: import org.netbeans.spi.viewmodel.UnknownTypeException;
060:
061: import org.netbeans.modules.debugger.jpda.JPDADebuggerImpl;
062: import org.openide.util.RequestProcessor;
063: import org.openide.util.WeakListeners;
064:
065: /**
066: * This tree model provides an array of CallStackFrame objects.
067: *
068: * @author Jan Jancura, Martin Entlicher
069: */
070: public class CallStackTreeModel implements TreeModel {
071:
072: private static boolean verbose = (System
073: .getProperty("netbeans.debugger.viewrefresh") != null)
074: && (System.getProperty("netbeans.debugger.viewrefresh")
075: .indexOf('c') >= 0);
076:
077: private JPDADebuggerImpl debugger;
078: private Collection<ModelListener> listeners = new HashSet<ModelListener>();
079: private Listener listener;
080:
081: public CallStackTreeModel(ContextProvider lookupProvider) {
082: debugger = (JPDADebuggerImpl) lookupProvider.lookupFirst(null,
083: JPDADebugger.class);
084: }
085:
086: /**
087: *
088: * @return threads contained in this group of threads
089: */
090: public Object[] getChildren(Object parent, int from, int to)
091: throws UnknownTypeException {
092: if (parent.equals(ROOT) || (parent instanceof JPDAThread)) {
093: // 1) get Thread
094: JPDAThread thread;
095: if (parent.equals(ROOT)) {
096: thread = debugger.getCurrentThread();
097: } else {
098: thread = (JPDAThread) parent;
099: }
100: if (thread == null) {
101: return new String[] { "No current thread" }; // TODO make localizable!!!
102: }
103:
104: // 2) get StackFrames for this Thread
105: try {
106: CallStackFrame[] sfs = thread.getCallStack(from, to);
107: return sfs;
108: } catch (AbsentInformationException aiex) {
109: if (aiex.getCause() instanceof IncompatibleThreadStateException) {
110: return new String[] { "Thread is running" }; // TODO make localizable!!!
111: } else {
112: return new String[] { "No call stack information available." }; // TODO make localizable!!!
113: }
114: }
115: } else
116: throw new UnknownTypeException(parent);
117: }
118:
119: /**
120: * Returns number of children for given node.
121: *
122: * @param node the parent node
123: * @throws UnknownTypeException if this TreeModel implementation is not
124: * able to resolve children for given node type
125: *
126: * @return true if node is leaf
127: */
128: public int getChildrenCount(Object parent)
129: throws UnknownTypeException {
130: if (parent.equals(ROOT) || (parent instanceof JPDAThread)) {
131: // Performance, see issue #59058.
132: return Integer.MAX_VALUE;
133: /*
134: // 1) get Thread
135: JPDAThread thread;
136: if (parent.equals (ROOT)) {
137: thread = debugger.getCurrentThread ();
138: } else {
139: thread = (JPDAThread) parent;
140: }
141: if (thread == null) {
142: return 1; //new String [] {"No current thread"};
143: }
144:
145: return thread.getStackDepth();
146: */
147: } else
148: throw new UnknownTypeException(parent);
149: }
150:
151: /**
152: *
153: * @return threads contained in this group of threads
154: */
155: public Object getRoot() {
156: return ROOT;
157: }
158:
159: public boolean isLeaf(Object node) throws UnknownTypeException {
160: if (node.equals(ROOT))
161: return false;
162: if (node instanceof CallStackFrame)
163: return true;
164: throw new UnknownTypeException(node);
165: }
166:
167: /**
168: *
169: * @param l the listener to add
170: */
171: public void addModelListener(ModelListener l) {
172: synchronized (listeners) {
173: listeners.add(l);
174: if (listener == null) {
175: listener = new Listener(this , debugger);
176: }
177: }
178: }
179:
180: /**
181: *
182: * @param l the listener to remove
183: */
184: public void removeModelListener(ModelListener l) {
185: synchronized (listeners) {
186: listeners.remove(l);
187: if (listeners.size() == 0) {
188: listener.destroy();
189: listener = null;
190: }
191: }
192: }
193:
194: public void fireTreeChanged() {
195: ModelListener[] ls;
196: synchronized (listeners) {
197: ls = listeners.toArray(new ModelListener[0]);
198: }
199: ModelEvent ev = new ModelEvent.TreeChanged(this );
200: for (int i = 0; i < ls.length; i++) {
201: ls[i].modelChanged(ev);
202: }
203: }
204:
205: /**
206: * Listens on JPDADebugger on PROP_STATE
207: */
208: private static class Listener implements PropertyChangeListener {
209:
210: private JPDADebugger debugger;
211: private WeakReference<CallStackTreeModel> model;
212:
213: public Listener(CallStackTreeModel tm, JPDADebugger debugger) {
214: this .debugger = debugger;
215: model = new WeakReference<CallStackTreeModel>(tm);
216: debugger.addPropertyChangeListener(this );
217: JPDAThreadImpl lastCurrentThread = (JPDAThreadImpl) debugger
218: .getCurrentThread();
219: if (lastCurrentThread != null) {
220: lastCurrentThread
221: .addPropertyChangeListener(WeakListeners
222: .propertyChange(this , lastCurrentThread));
223: }
224: }
225:
226: private CallStackTreeModel getModel() {
227: CallStackTreeModel tm = model.get();
228: if (tm == null) {
229: destroy();
230: }
231: return tm;
232: }
233:
234: void destroy() {
235: debugger.removePropertyChangeListener(this );
236: if (task != null) {
237: // cancel old task
238: task.cancel();
239: if (verbose)
240: System.out.println("CSTM cancel old task " + task);
241: task = null;
242: }
243: }
244:
245: // currently waiting / running refresh task
246: // there is at most one
247: private RequestProcessor.Task task;
248:
249: // check also whether the current thread was resumed/suspended
250: // the call stack needs to be refreshed after invokeMethod() which resumes the thread
251: public synchronized void propertyChange(PropertyChangeEvent e) {
252: boolean refresh = false;
253: String propertyName = e.getPropertyName();
254: if (propertyName == debugger.PROP_CURRENT_THREAD) {
255: JPDAThreadImpl lastCurrentThread = (JPDAThreadImpl) debugger
256: .getCurrentThread();
257: if (lastCurrentThread != null) {
258: lastCurrentThread
259: .addPropertyChangeListener(WeakListeners
260: .propertyChange(this ,
261: lastCurrentThread));
262: refresh = true;
263: }
264: }
265: if (propertyName == JPDAThreadImpl.PROP_SUSPENDED
266: && Boolean.TRUE.equals(e.getNewValue())) {
267: if (e.getSource() == debugger.getCurrentThread()) {
268: refresh = true;
269: }
270: }
271: if ((propertyName == debugger.PROP_STATE)
272: && (debugger.getState() == debugger.STATE_STOPPED)) {
273: refresh = true;
274: }
275: if (refresh) {
276: synchronized (this ) {
277: if (task == null) {
278: task = RequestProcessor.getDefault().create(
279: new Refresher());
280: }
281: task.schedule(200);
282: }
283: }
284: }
285:
286: private class Refresher extends Object implements Runnable {
287: public void run() {
288: if (debugger.getState() == debugger.STATE_STOPPED) {
289: CallStackTreeModel tm = getModel();
290: if (tm != null) {
291: tm.fireTreeChanged();
292: }
293: }
294: }
295: }
296: }
297: }
|