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.ThreadGroupReference;
045: import com.sun.jdi.ThreadReference;
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: import java.util.List;
052: import java.util.Map;
053: import java.util.Set;
054: import java.util.WeakHashMap;
055: import org.netbeans.api.debugger.jpda.JPDAThread;
056: import org.netbeans.api.debugger.jpda.JPDAThreadGroup;
057:
058: import org.netbeans.spi.debugger.ContextProvider;
059: import org.netbeans.api.debugger.jpda.JPDADebugger;
060: import org.netbeans.spi.viewmodel.ModelEvent;
061: import org.netbeans.spi.viewmodel.ModelListener;
062: import org.netbeans.spi.viewmodel.TreeModel;
063: import org.netbeans.spi.viewmodel.UnknownTypeException;
064:
065: import org.netbeans.modules.debugger.jpda.JPDADebuggerImpl;
066: import org.openide.util.RequestProcessor;
067:
068: /**
069: * This class represents JPDA Debugger Implementation.
070: *
071: * @author Jan Jancura
072: */
073: public class ThreadsTreeModel implements TreeModel {
074:
075: private static boolean verbose = (System
076: .getProperty("netbeans.debugger.viewrefresh") != null)
077: && (System.getProperty("netbeans.debugger.viewrefresh")
078: .indexOf('t') >= 0);
079:
080: private JPDADebuggerImpl debugger;
081: private Map<Object, ChildrenTree> childrenCache = new WeakHashMap<Object, ChildrenTree>();
082: private Listener listener;
083: private Collection<ModelListener> listeners = new HashSet<ModelListener>();
084:
085: public ThreadsTreeModel(ContextProvider lookupProvider) {
086: debugger = (JPDADebuggerImpl) lookupProvider.lookupFirst(null,
087: JPDADebugger.class);
088: }
089:
090: public Object getRoot() {
091: return ROOT;
092: }
093:
094: public Object[] getChildren(Object o, int from, int to)
095: throws UnknownTypeException {
096: /*
097: if (node.equals (ROOT)) {
098: return debugger.getTopLevelThreadGroups();
099: } else if (node instanceof JPDAThreadGroup) {
100: JPDAThreadGroup tg = (JPDAThreadGroup) node;
101: JPDAThreadGroup[] tgs = tg.getThreadGroups();
102: JPDAThread[] ts = tg.getThreads();
103: int n = tgs.length + ts.length;
104: from = Math.min(n, from);
105: to = Math.min(n, to);
106: Object[] ch = new Object[to - from];
107: if (from < tgs.length) {
108: if (to >= tgs.length) {
109: System.arraycopy(tgs, from, ch, 0, tgs.length - from);
110: } else {
111: System.arraycopy(tgs, from, ch, 0, to - from);
112: }
113: }
114: if (to > tgs.length) {
115: to -= tgs.length;
116: int pos = tgs.length - from;
117: if (from >= tgs.length) {
118: from -= tgs.length;
119: } else {
120: from = 0;
121: }
122: System.arraycopy(ts, from, ch, pos, to - from);
123: }
124: return ch;
125: } else {
126: throw new UnknownTypeException (node);
127: }
128: */
129:
130: Object[] ch;
131: synchronized (childrenCache) {
132: //ch = (List) childrenCache.get(o);
133: ChildrenTree cht = childrenCache.get(o);
134: if (cht != null) {
135: ch = cht.getChildren();
136: } else {
137: ch = null;
138: }
139: }
140: if (ch == null) {
141: ch = computeChildren(o);
142: if (ch == null) {
143: throw new UnknownTypeException(o);
144: } else {
145: synchronized (childrenCache) {
146: ChildrenTree cht = new ChildrenTree(o);
147: cht.setChildren(ch);
148: childrenCache.put(o, cht);
149: }
150: }
151: }
152: int l = ch.length;
153: from = Math.min(l, from);
154: to = Math.min(l, to);
155: if (from == 0 && to == l) {
156: return ch;
157: } else {
158: Object[] ch1 = new Object[to - from];
159: System.arraycopy(ch, from, ch1, 0, to - from);
160: ch = ch1;
161: }
162: return ch;
163: }
164:
165: private Object[] computeChildren(Object node) {
166: /*
167: List ch;
168: try {
169: if (node.equals (ROOT)) {
170: VirtualMachine vm = debugger.getVirtualMachine ();
171: if (vm != null)
172: ch = vm.topLevelThreadGroups ();
173: else
174: ch = Collections.EMPTY_LIST;
175: } else
176: if (node instanceof ThreadGroupReference) {
177: ThreadGroupReference tgr = (ThreadGroupReference) node;
178: ch = new ArrayList (tgr.threadGroups ());
179: ch.addAll(tgr.threads ());
180: } else
181: ch = null;
182: } catch (VMDisconnectedException ex) {
183: ch = Collections.EMPTY_LIST;
184: }
185: return ch;
186: */
187: if (node.equals(ROOT)) {
188:
189: if (verbose) {
190: com.sun.jdi.VirtualMachine vm = debugger
191: .getVirtualMachine();
192: if (vm == null) {
193: System.err
194: .println("\nThreadsTreeModel.computeChildren():\nVM is null!\n");
195: } else {
196: List<ThreadReference> threads = vm.allThreads();
197: System.err
198: .println("\nThreadsTreeModel.computeChildren() ALL Threads:");
199: for (ThreadReference t : threads) {
200: System.err.println(" " + t.name()
201: + " is suspended: " + t.isSuspended()
202: + ", suspend count = "
203: + t.suspendCount());
204: }
205: System.err.println("");
206: }
207: }
208:
209: return debugger.getTopLevelThreadGroups();
210: } else if (node instanceof JPDAThreadGroup) {
211: JPDAThreadGroup tg = (JPDAThreadGroup) node;
212: JPDAThreadGroup[] tgs = tg.getThreadGroups();
213: JPDAThread[] ts = tg.getThreads();
214: int n = tgs.length + ts.length;
215: Object[] ch = new Object[n];
216: System.arraycopy(tgs, 0, ch, 0, tgs.length);
217: System.arraycopy(ts, 0, ch, tgs.length, ts.length);
218: return ch;
219: } else {
220: return new Object[0];
221: }
222: }
223:
224: private void recomputeChildren() {
225: synchronized (childrenCache) {
226: recomputeChildren(getRoot());
227: }
228: }
229:
230: private void recomputeChildren(Object node) {
231: ChildrenTree cht = childrenCache.get(node);
232: if (cht != null) {
233: Set keys = childrenCache.keySet();
234: Object[] oldCh = cht.getChildren();
235: Object[] newCh = computeChildren(node);
236: cht.setChildren(newCh);
237: for (int i = 0; i < newCh.length; i++) {
238: if (keys.contains(newCh[i])) {
239: recomputeChildren(newCh[i]);
240: }
241: }
242: }
243: /*
244: Set nodes = childrenCache.keySet();
245: for (Iterator it = nodes.iterator(); it.hasNext(); ) {
246: Object node = it.next();
247: // potreba brat jako tree - vyhodit stare nody hierarchicky
248: // takto bych se ptal i na stare out-of-date nody!
249: }
250: //List ch = (List) childrenCache.get(o);
251: */
252: }
253:
254: /**
255: * Returns number of children for given node.
256: *
257: * @param node the parent node
258: * @throws UnknownTypeException if this TreeModel implementation is not
259: * able to resolve children for given node type
260: *
261: * @return true if node is leaf
262: */
263: public int getChildrenCount(Object node)
264: throws UnknownTypeException {
265: // Performance, see issue #59058.
266: return Integer.MAX_VALUE;
267: /*
268: Object[] ch;
269: synchronized (childrenCache) {
270: ChildrenTree cht = childrenCache.get(node);
271: if (cht != null) {
272: ch = cht.getChildren();
273: } else {
274: ch = null;
275: }
276: }
277: if (ch == null) {
278: ch = computeChildren(node);
279: if (ch == null) {
280: throw new UnknownTypeException (node);
281: } else {
282: synchronized (childrenCache) {
283: ChildrenTree cht = new ChildrenTree(node);
284: cht.setChildren(ch);
285: childrenCache.put(node, cht);
286: }
287: }
288: }
289: return ch.length;
290: */
291: /*
292: try {
293: List ch;
294: if (node.equals (ROOT)) {
295: VirtualMachine vm = debugger.getVirtualMachine ();
296: if (vm != null)
297: ch = vm.topLevelThreadGroups ();
298: else
299: ch = Collections.EMPTY_LIST;
300: } else
301: if (node instanceof ThreadGroupReference) {
302: ThreadGroupReference tgr = (ThreadGroupReference) node;
303: ch = new ArrayList (tgr.threadGroups ());
304: ch.addAll(tgr.threads ());
305: } else
306: throw new UnknownTypeException (node);
307: synchronized (lastCachedLock) {
308: lastCachedChildrenNode = node;
309: lastCachedChildren = ch;
310: }
311: return ch.size();
312: } catch (VMDisconnectedException ex) {
313: return 0;
314: }
315: */
316: }
317:
318: public boolean isLeaf(Object o) throws UnknownTypeException {
319: if (o instanceof JPDAThread)
320: return true;
321: if (o instanceof JPDAThreadGroup)
322: return false;
323: if (o == ROOT)
324: return false;
325: throw new UnknownTypeException(o);
326: }
327:
328: public void addModelListener(ModelListener l) {
329: synchronized (listeners) {
330: listeners.add(l);
331: if (listener == null) {
332: listener = new Listener(this , debugger);
333: }
334: }
335: }
336:
337: public void removeModelListener(ModelListener l) {
338: synchronized (listeners) {
339: listeners.remove(l);
340: if (listeners.size() == 0) {
341: listener.destroy();
342: listener = null;
343: }
344: }
345: }
346:
347: public void fireTreeChanged() {
348: recomputeChildren();
349: ModelListener[] ls;
350: synchronized (listeners) {
351: ls = listeners.toArray(new ModelListener[0]);
352: }
353: ModelEvent ev = new ModelEvent.TreeChanged(this );
354: for (int i = 0; i < ls.length; i++) {
355: ls[i].modelChanged(ev);
356: }
357: }
358:
359: /**
360: * Listens on JPDADebugger state property and updates all threads hierarchy.
361: */
362: private static class Listener implements PropertyChangeListener {
363:
364: private JPDADebugger debugger;
365: private WeakReference<ThreadsTreeModel> model;
366: // currently waiting / running refresh task
367: // there is at most one
368: private RequestProcessor.Task task;
369:
370: public Listener(ThreadsTreeModel tm, JPDADebugger debugger) {
371: this .debugger = debugger;
372: model = new WeakReference<ThreadsTreeModel>(tm);
373: debugger.addPropertyChangeListener(this );
374: if (debugger.getState() == debugger.STATE_RUNNING) {
375: task = createTask();
376: task.schedule(500);
377: }
378: }
379:
380: private ThreadsTreeModel getModel() {
381: ThreadsTreeModel tm = model.get();
382: if (tm == null) {
383: destroy();
384: }
385: return tm;
386: }
387:
388: void destroy() {
389: debugger.removePropertyChangeListener(this );
390: synchronized (this ) {
391: if (task != null) {
392: // cancel old task
393: task.cancel();
394: if (verbose)
395: System.out.println("TTM cancel old task "
396: + task);
397: task = null;
398: }
399: }
400: }
401:
402: private RequestProcessor.Task createTask() {
403: RequestProcessor.Task task = new RequestProcessor(
404: "Threads Refresh", 1).create(new RefreshTree());
405: if (verbose)
406: System.out.println("TTM create task " + task);
407: return task;
408: }
409:
410: public void propertyChange(PropertyChangeEvent e) {
411: if ((e.getPropertyName() == debugger.PROP_STATE)
412: && (debugger.getState() == debugger.STATE_STOPPED)) {
413: final ThreadsTreeModel tm = getModel();
414: if (tm == null)
415: return;
416: synchronized (this ) {
417: if (task == null) {
418: task = createTask();
419: }
420: task.schedule(500);
421: }
422: } else if ((e.getPropertyName() == debugger.PROP_STATE)
423: && (debugger.getState() == debugger.STATE_RUNNING)) {
424: final ThreadsTreeModel tm = getModel();
425: if (tm == null)
426: return;
427: synchronized (this ) {
428: if (task == null) {
429: task = createTask();
430: }
431: task.schedule(2000);
432: }
433: }
434: }
435:
436: private class RefreshTree implements Runnable {
437: public RefreshTree() {
438: }
439:
440: public void run() {
441: ThreadsTreeModel tm = getModel();
442: if (tm == null)
443: return;
444: if (verbose)
445: System.out.println("TTM do R task " + task);
446: tm.fireTreeChanged();
447: synchronized (Listener.this ) {
448: if (debugger.getState() == debugger.STATE_RUNNING) {
449: if (task != null) {
450: task.schedule(2000);
451: }
452: }
453: }
454: }
455: }
456: }
457:
458: private static class ChildrenTree {
459:
460: private Object node;
461: private Object[] ch;
462:
463: public ChildrenTree(Object node) {
464: this .node = node;
465: }
466:
467: public void setChildren(Object[] ch) {
468: this .ch = ch;
469: }
470:
471: public Object[] getChildren() {
472: return ch;
473: }
474:
475: }
476:
477: }
|