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-2007 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.ArrayReference;
046: import com.sun.jdi.InternalException;
047: import com.sun.jdi.InvalidStackFrameException;
048: import com.sun.jdi.LocalVariable;
049: import com.sun.jdi.NativeMethodException;
050: import com.sun.jdi.ObjectReference;
051: import com.sun.jdi.ReferenceType;
052: import com.sun.jdi.StackFrame;
053: import com.sun.jdi.VMDisconnectedException;
054: import com.sun.jdi.Value;
055: import java.beans.Customizer;
056:
057: import java.beans.PropertyChangeEvent;
058: import java.beans.PropertyChangeListener;
059: import java.lang.ref.WeakReference;
060: import java.util.ArrayList;
061: import java.util.List;
062: import java.util.Map;
063: import java.util.Vector;
064: import java.util.WeakHashMap;
065: import org.netbeans.api.debugger.jpda.JPDAClassType;
066: import org.netbeans.spi.debugger.ContextProvider;
067: import org.netbeans.api.debugger.jpda.JPDADebugger;
068: import org.netbeans.api.debugger.jpda.JPDAThread;
069: import org.netbeans.api.debugger.jpda.Variable;
070: import org.netbeans.spi.debugger.jpda.EditorContext.Operation;
071: import org.netbeans.spi.viewmodel.ModelEvent;
072: import org.netbeans.spi.viewmodel.TreeModel;
073: import org.netbeans.spi.viewmodel.ModelListener;
074: import org.netbeans.spi.viewmodel.UnknownTypeException;
075:
076: import org.netbeans.modules.debugger.jpda.JPDADebuggerImpl;
077:
078: import org.openide.util.RequestProcessor;
079: import org.openide.util.WeakListeners;
080:
081: /**
082: * @author Jan Jancura
083: */
084: public class LocalsTreeModel implements TreeModel,
085: PropertyChangeListener {
086:
087: private static boolean verbose = (System
088: .getProperty("netbeans.debugger.viewrefresh") != null)
089: && (System.getProperty("netbeans.debugger.viewrefresh")
090: .indexOf('l') >= 0);
091:
092: /** Nest array elements when array length is bigger then this. */
093: private static final int ARRAY_CHILDREN_NESTED_LENGTH = 100;
094:
095: private JPDADebuggerImpl debugger;
096: private Listener listener;
097: private List<ModelListener> listeners = new ArrayList<ModelListener>();
098: private PropertyChangeListener[] varListeners;
099: //private Map cachedLocals = new WeakHashMap();
100: private Map<Value, ArrayChildrenNode> cachedArrayChildren = new WeakHashMap<Value, ArrayChildrenNode>();
101:
102: public LocalsTreeModel(ContextProvider lookupProvider) {
103: debugger = (JPDADebuggerImpl) lookupProvider.lookupFirst(null,
104: JPDADebugger.class);
105: }
106:
107: public Object getRoot() {
108: return ROOT;
109: }
110:
111: public void propertyChange(PropertyChangeEvent evt) {
112: fireTableValueChangedChanged(evt.getSource(), null);
113: }
114:
115: public Object[] getChildren(Object o, int from, int to)
116: throws UnknownTypeException {
117: Object[] ch = getChildrenImpl(o, from, to);
118: for (int i = 0; i < ch.length; i++) {
119: if (ch[i] instanceof Customizer) {
120: ((Customizer) ch[i]).addPropertyChangeListener(this );
121: }
122: }
123: return ch;
124: }
125:
126: public Object[] getChildrenImpl(Object o, int from, int to)
127: throws UnknownTypeException {
128: try {
129: if (o.equals(ROOT)) {
130: Object[] os = getLocalVariables(from, to);
131: return os;
132: } else if (o instanceof AbstractObjectVariable) { // ThisVariable & ObjectFieldVariable
133: AbstractObjectVariable abstractVariable = (AbstractObjectVariable) o;
134: boolean isArray = abstractVariable.getInnerValue() instanceof ArrayReference;
135: if (isArray) {
136: to = abstractVariable.getFieldsCount();
137: // We need to reset it for arrays, to get the full array
138: }
139: if (isArray
140: && (to - from) > ARRAY_CHILDREN_NESTED_LENGTH) {
141: ArrayChildrenNode achn = cachedArrayChildren
142: .get(abstractVariable.getInnerValue());
143: if (achn == null) {
144: achn = new ArrayChildrenNode(abstractVariable);
145: cachedArrayChildren.put(abstractVariable
146: .getInnerValue(), achn);
147: } else {
148: achn.update(abstractVariable);
149: }
150: return achn.getChildren();
151: } else {
152: return abstractVariable.getFields(from, Math.min(
153: to, abstractVariable.getFieldsCount()));
154: }
155: } else if (o instanceof AbstractVariable) { // FieldVariable
156: return new Object[0]; // No children for no-object variables
157: } else if (o instanceof ArrayChildrenNode) {
158: return ((ArrayChildrenNode) o).getChildren();
159: } else if (o instanceof JPDAClassType) {
160: JPDAClassType clazz = (JPDAClassType) o;
161: List staticFields = clazz.staticFields();
162: Object[] fields = new Object[1 + staticFields.size()];
163: fields[0] = clazz.classObject();
164: System.arraycopy(staticFields.toArray(), 0, fields, 1,
165: staticFields.size());
166: return fields;
167: } else if (o instanceof Operation) {
168: Object[] ret = { null, null }; // Results of last operations, Arguments to current operation
169: CallStackFrameImpl frame = (CallStackFrameImpl) debugger
170: .getCurrentCallStackFrame();
171: if (frame == null) {
172: return new Object[] {};
173: }
174: Operation currentOperation = frame.getThread()
175: .getCurrentOperation();
176: Operation lastOperation = null;
177: if (currentOperation != null) {
178: JPDAThread t = debugger.getCurrentThread();
179: if (t != null) {
180: java.util.List<Operation> lastOperations = t
181: .getLastOperations();
182: if (lastOperations != null
183: && lastOperations.size() > 0) {
184: lastOperation = lastOperations
185: .get(lastOperations.size() - 1);
186: }
187: }
188: }
189: boolean isNotDone = currentOperation != lastOperation;
190: if (isNotDone) {
191: ret[0] = "operationArguments "
192: + currentOperation.getMethodName(); // NOI18N
193: }
194: List<Operation> operations = frame.getThread()
195: .getLastOperations();
196: if (operations != null && operations.size() > 0
197: && operations.get(0).getReturnValue() != null) {
198: ret[1] = "lastOperations"; // NOI18N
199: }
200: if (ret[0] == null && ret[1] == null)
201: return new Object[] {};
202: if (ret[0] == null)
203: return new Object[] { ret[1] };
204: if (ret[1] == null)
205: return new Object[] { ret[0] };
206: return ret;
207: } else if ("lastOperations" == o) { // NOI18N
208: CallStackFrameImpl frame = (CallStackFrameImpl) debugger
209: .getCurrentCallStackFrame();
210: if (frame == null) {
211: return new Object[] {};
212: }
213: List<Operation> operations = frame.getThread()
214: .getLastOperations();
215: List<Variable> lastOperationValues = new ArrayList<Variable>(
216: operations.size());
217: for (int i = 0; i < operations.size(); i++) {
218: Variable ret = operations.get(i).getReturnValue();
219: if (ret != null) {
220: lastOperationValues.add(ret);
221: }
222: }
223: return lastOperationValues.toArray();
224: } else if (o instanceof String
225: && ((String) o).startsWith("operationArguments")) { // NOI18N
226: CallStackFrameImpl frame = (CallStackFrameImpl) debugger
227: .getCurrentCallStackFrame();
228: if (frame == null) {
229: return new Object[] {};
230: }
231: Operation currentOperation = frame.getThread()
232: .getCurrentOperation();
233: if (currentOperation == null) {
234: return new Object[] {};
235: }
236: List<org.netbeans.api.debugger.jpda.LocalVariable> arguments;
237: try {
238: arguments = frame
239: .findOperationArguments(currentOperation);//currentOperation.getArgumentValues();
240: } catch (NativeMethodException nmex) {
241: return new Object[] { "NativeMethodException" };
242: }
243: if (arguments == null) {
244: return new Object[] {};
245: } else {
246: return arguments.toArray();
247: }
248: } else
249: throw new UnknownTypeException(o);
250: } catch (VMDisconnectedException ex) {
251: return new Object[0];
252: }
253: }
254:
255: /**
256: * Returns number of children for given node.
257: *
258: * @param node the parent node
259: * @throws UnknownTypeException if this TreeModel implementation is not
260: * able to resolve children for given node type
261: *
262: * @return true if node is leaf
263: */
264: public int getChildrenCount(Object node)
265: throws UnknownTypeException {
266: try {
267: if (node.equals(ROOT)) {
268: // Performance, see issue #59058.
269: return Integer.MAX_VALUE;
270: /*
271: CallStackFrameImpl frame = (CallStackFrameImpl) debugger.
272: getCurrentCallStackFrame ();
273: if (frame == null)
274: return 1;
275: StackFrame sf = frame.getStackFrame ();
276: if (sf == null)
277: return 1;
278: try {
279: int i = 0;
280: List<Operation> operations = frame.getThread().getLastOperations();
281: ReturnVariableImpl returnVariable;
282: boolean haveLastOperations;
283: if (operations != null && operations.size() > 0 && operations.get(0).getReturnValue() != null) {
284: haveLastOperations = true;
285: returnVariable = null;
286: } else {
287: returnVariable = ((JPDAThreadImpl) frame.getThread()).getReturnVariable();
288: haveLastOperations = false;
289: }
290: if (haveLastOperations || returnVariable != null) {
291: i++;
292: }
293: try {
294: i += sf.visibleVariables ().size ();
295: } catch (AbsentInformationException ex) {
296: i++;
297: }
298: // This or Static
299: i++;//if (sf.thisObject () != null) i++;
300: return i;
301: } catch (NativeMethodException ex) {
302: return 1;//throw new NoInformationException ("native method");
303: } catch (InternalException ex) {
304: return 1;//throw new NoInformationException ("native method");
305: } catch (InvalidStackFrameException ex) {
306: return 1;//throw new NoInformationException ("thread is running");
307: } catch (VMDisconnectedException ex) {
308: }
309: return 0;
310: */
311: } else if (node instanceof AbstractVariable) { // ThisVariable & FieldVariable
312: AbstractVariable abstractVariable = (AbstractVariable) node;
313: if (abstractVariable.getInnerValue() instanceof ArrayReference) {
314: // Performance, see issue #59058.
315: return Integer.MAX_VALUE;
316: //return Math.min (abstractVariable.getFieldsCount (), ARRAY_CHILDREN_NESTED_LENGTH);
317: }
318: // Performance, see issue #59058.
319: return Integer.MAX_VALUE;
320: //return abstractVariable.getFieldsCount ();
321: } else if (node instanceof ArrayChildrenNode) {
322: // Performance, see issue #59058.
323: return Integer.MAX_VALUE;
324: //return ((ArrayChildrenNode) node).getChildren().length;
325: } else if (node instanceof JPDAClassType) {
326: // Performance, see issue #59058.
327: return Integer.MAX_VALUE;
328: //JPDAClassType clazz = (JPDAClassType) node;
329: //return 1 + clazz.staticFields().size();
330: } else if (node instanceof JPDAClassType) {
331: JPDAClassType clazz = (JPDAClassType) node;
332: return 1 + clazz.staticFields().size();
333: } else if (node instanceof Operation) {
334: return Integer.MAX_VALUE;
335: } else if ("lastOperations" == node) { // NOI18N
336: CallStackFrameImpl frame = (CallStackFrameImpl) debugger
337: .getCurrentCallStackFrame();
338: if (frame == null) {
339: return 0;
340: }
341: List<Operation> operations = frame.getThread()
342: .getLastOperations();
343: if (operations != null) {
344: return operations.size();
345: } else {
346: return 0;
347: }
348: } else if (node instanceof String
349: && ((String) node).startsWith("operationArguments")) { // NOI18N
350: // Performance, see issue #59058.
351: return Integer.MAX_VALUE;
352: } else
353: throw new UnknownTypeException(node);
354: } catch (VMDisconnectedException ex) {
355: }
356: return 0;
357: }
358:
359: public boolean isLeaf(Object o) throws UnknownTypeException {
360: if (o.equals(ROOT))
361: return false;
362: if (o instanceof AbstractVariable)
363: return !(((AbstractVariable) o).getInnerValue() instanceof ObjectReference);
364: if (o.toString().startsWith("SubArray")) {
365: return false;
366: }
367: if (o.equals("NoInfo")) // NOI18N
368: return true;
369: if (o instanceof JPDAClassType)
370: return false;
371: if (o instanceof Operation)
372: return false;
373: if (o == "lastOperations")
374: return false;
375: if (o instanceof String
376: && ((String) o).startsWith("operationArguments")) { // NOI18N
377: return false;
378: }
379: throw new UnknownTypeException(o);
380: }
381:
382: public void addModelListener(ModelListener l) {
383: synchronized (listeners) {
384: listeners.add(l);
385: if (listener == null)
386: listener = new Listener(this , debugger);
387: }
388: }
389:
390: public void removeModelListener(ModelListener l) {
391: synchronized (listeners) {
392: listeners.remove(l);
393: if (listeners.size() == 0) {
394: listener.destroy();
395: listener = null;
396: }
397: }
398: }
399:
400: void fireTreeChanged() {
401: List<ModelListener> ls;
402: synchronized (listeners) {
403: ls = new ArrayList<ModelListener>(listeners);
404: }
405: int i, k = ls.size();
406: for (i = 0; i < k; i++)
407: ls.get(i).modelChanged(new ModelEvent.TreeChanged(this ));
408: }
409:
410: private void fireTableValueChangedChanged(Object node,
411: String propertyName) {
412: List<ModelListener> ls;
413: synchronized (listeners) {
414: ls = new ArrayList<ModelListener>(listeners);
415: }
416: int i, k = ls.size();
417: for (i = 0; i < k; i++)
418: ls.get(i).modelChanged(
419: new ModelEvent.TableValueChanged(this , node,
420: propertyName));
421: }
422:
423: private void fireNodeChanged(Object node) {
424: List<ModelListener> ls;
425: synchronized (listeners) {
426: ls = new ArrayList<ModelListener>(listeners);
427: }
428: int i, k = ls.size();
429: for (i = 0; i < k; i++)
430: ls.get(i).modelChanged(
431: new ModelEvent.NodeChanged(this , node));
432: }
433:
434: // private methods .........................................................
435:
436: private Object[] getLocalVariables(int from, int to) {
437: synchronized (debugger.LOCK) {
438: CallStackFrameImpl callStackFrame = (CallStackFrameImpl) debugger
439: .getCurrentCallStackFrame();
440: if (callStackFrame == null)
441: return new String[] { "No current thread" };
442: StackFrame stackFrame = callStackFrame.getStackFrame();
443: if (stackFrame == null)
444: return new String[] { "No current thread" };
445: try {
446: ObjectReference this R = stackFrame.this Object();
447: List<Operation> operations = callStackFrame.getThread()
448: .getLastOperations();
449: ReturnVariableImpl returnVariable;
450: boolean haveLastOperations;
451: if (operations != null && operations.size() > 0
452: && operations.get(0).getReturnValue() != null) {
453: haveLastOperations = true;
454: returnVariable = null;
455: } else {
456: returnVariable = ((JPDAThreadImpl) callStackFrame
457: .getThread()).getReturnVariable();
458: haveLastOperations = false;
459: }
460: //int retValShift = (haveLastOperations || returnVariable != null) ? 1 : 0;
461: int retValShift = (returnVariable != null) ? 1 : 0;
462: Operation currentOperation = callStackFrame.getThread()
463: .getCurrentOperation();
464: //int currArgShift = (currentOperation != null && CallStackFrameImpl.IS_JDK_160_02) ? 1 : 0;
465: int currArgShift = (currentOperation != null) ? 1 : 0;
466: int shift = retValShift + currArgShift;
467: if (this R == null) {
468: ReferenceType classType = stackFrame.location()
469: .declaringType();
470: Object[] avs = null;
471: avs = getLocalVariables(callStackFrame, stackFrame,
472: Math.max(from - shift - 1, 0), Math.max(to
473: - shift - 1, 0));
474: Object[] result = new Object[avs.length + shift + 1];
475: if (from < 1 && retValShift > 0) {
476: result[0] = returnVariable;
477: }
478: if (from < 1 && currArgShift > 0) {
479: //result[retValShift] = "operationArguments " + currentOperation.getMethodName(); // NOI18N
480: result[retValShift] = currentOperation;
481: }
482: if (from < 1 + shift) {
483: //result [0] = new ThisVariable (debugger, classType.classObject(), "");
484: result[shift] = debugger
485: .getClassType(classType);
486: }
487: System.arraycopy(avs, 0, result, 1 + shift,
488: avs.length);
489: return result;
490: } else {
491: Object[] avs = null;
492: avs = getLocalVariables(callStackFrame, stackFrame,
493: Math.max(from - shift - 1, 0), Math.max(to
494: - shift - 1, 0));
495: Object[] result = new Object[avs.length + shift + 1];
496: if (from < 1 && retValShift > 0) {
497: result[0] = returnVariable;
498: }
499: if (from < 1 && currArgShift > 0) {
500: //result[retValShift] = "operationArguments " + currentOperation.getMethodName(); // NOI18N
501: result[retValShift] = currentOperation;
502: }
503: if (from < 1 + shift) {
504: result[shift] = new ThisVariable(debugger,
505: this R, "");
506: }
507: System.arraycopy(avs, 0, result, 1 + shift,
508: avs.length);
509: return result;
510: }
511: } catch (NativeMethodException nmex) {
512: return new String[] { "NativeMethodException" };
513: } catch (InternalException ex) {
514: return new String[] { ex.getMessage() };
515: }
516: } // synchronized
517: }
518:
519: private org.netbeans.api.debugger.jpda.LocalVariable[] getLocalVariables(
520: final CallStackFrameImpl callStackFrame,
521: final StackFrame stackFrame, int from, int to) {
522: org.netbeans.api.debugger.jpda.LocalVariable[] locals;
523: try {
524: locals = callStackFrame.getLocalVariables();
525: } catch (AbsentInformationException aiex) {
526: locals = callStackFrame.getMethodArguments();
527: }
528: if (locals == null) {
529: locals = new org.netbeans.api.debugger.jpda.LocalVariable[] {};
530: }
531: int n = locals.length;
532: to = Math.min(n, to);
533: from = Math.min(n, from);
534: if (from != 0 || to != n) {
535: org.netbeans.api.debugger.jpda.LocalVariable[] subLocals = new org.netbeans.api.debugger.jpda.LocalVariable[to
536: - from];
537: for (int i = from; i < to; i++) {
538: subLocals[i - from] = locals[i];
539: }
540: locals = subLocals;
541: }
542: updateVarListeners(locals);
543: return locals;
544: /*
545: try {
546: String className = stackFrame.location ().declaringType ().name ();
547: List l = stackFrame.visibleVariables ();
548: to = Math.min(l.size(), to);
549: from = Math.min(l.size(), from);
550: int i, k = to - from, j = from;
551: AbstractVariable[] locals = new AbstractVariable [k];
552: for (i = 0; i < k; i++) {
553: LocalVariable lv = (LocalVariable) l.get (j++);
554: locals [i] = getLocal (lv, callStackFrame, className);
555: }
556: return locals;
557: } catch (NativeMethodException ex) {
558: throw new AbsentInformationException ("native method");
559: } catch (InvalidStackFrameException ex) {
560: throw new AbsentInformationException ("thread is running");
561: } catch (VMDisconnectedException ex) {
562: return new AbstractVariable [0];
563: }
564: */
565: }
566:
567: /*
568: private Local getLocal (LocalVariable lv, CallStackFrameImpl frame, String className) {
569: Value v = frame.getStackFrame ().getValue (lv);
570: Local local = (Local) cachedLocals.get(lv);
571: if (local != null) {
572: local.setInnerValue(v);
573: local.setFrame(frame);
574: local.setLocalVariable(lv);
575: } else {
576: if (v instanceof ObjectReference) {
577: local = new ObjectLocalVariable (
578: debugger,
579: v,
580: className,
581: lv,
582: JPDADebuggerImpl.getGenericSignature (lv),
583: frame
584: );
585: } else {
586: local = new Local (debugger, v, className, lv, frame);
587: }
588: cachedLocals.put(lv, local);
589: }
590: return local;
591: }
592: */
593:
594: private void updateVarListeners(
595: org.netbeans.api.debugger.jpda.LocalVariable[] vars) {
596: varListeners = new PropertyChangeListener[vars.length];
597: for (int i = 0; i < vars.length; i++) {
598: final org.netbeans.api.debugger.jpda.LocalVariable var = vars[i];
599: PropertyChangeListener l = new PropertyChangeListener() {
600: public void propertyChange(PropertyChangeEvent evt) {
601: fireNodeChanged(var);
602: }
603: };
604: varListeners[i] = l; // Hold it so that it does not get lost, till the array is updated.
605: ((AbstractVariable) var)
606: .addPropertyChangeListener(WeakListeners
607: .propertyChange(l, var));
608: }
609: }
610:
611: public Variable getVariable(Value v) {
612: if (v instanceof ObjectReference)
613: return new AbstractObjectVariable(debugger,
614: (ObjectReference) v, null);
615: return new AbstractVariable(debugger, v, null);
616: }
617:
618: JPDADebuggerImpl getDebugger() {
619: return debugger;
620: }
621:
622: // innerclasses ............................................................
623:
624: private static class Listener implements PropertyChangeListener {
625:
626: private JPDADebugger debugger;
627: private WeakReference<LocalsTreeModel> model;
628:
629: public Listener(LocalsTreeModel tm, JPDADebugger debugger) {
630: this .debugger = debugger;
631: model = new WeakReference<LocalsTreeModel>(tm);
632: debugger.addPropertyChangeListener(this );
633: }
634:
635: void destroy() {
636: debugger.removePropertyChangeListener(this );
637: if (task != null) {
638: // cancel old task
639: task.cancel();
640: if (verbose)
641: System.out.println("LTM cancel old task " + task);
642: task = null;
643: }
644: }
645:
646: private LocalsTreeModel getModel() {
647: LocalsTreeModel tm = model.get();
648: if (tm == null) {
649: destroy();
650: }
651: return tm;
652: }
653:
654: // currently waiting / running refresh task
655: // there is at most one
656: private RequestProcessor.Task task;
657:
658: public void propertyChange(PropertyChangeEvent e) {
659: if (((e.getPropertyName() == JPDADebugger.PROP_CURRENT_CALL_STACK_FRAME) ||
660: //(e.getPropertyName () == debugger.PROP_CURRENT_THREAD) ||
661: (e.getPropertyName() == JPDADebugger.PROP_STATE))
662: && (debugger.getState() == JPDADebugger.STATE_STOPPED)) {
663: // IF state has been changed to STOPPED or
664: // IF current call stack frame has been changed & state is stoped
665: final LocalsTreeModel ltm = getModel();
666: if (ltm == null)
667: return;
668: if (task != null) {
669: // cancel old task
670: task.cancel();
671: if (verbose)
672: System.out.println("LTM cancel old task "
673: + task);
674: task = null;
675: }
676: task = RequestProcessor.getDefault().post(
677: new Runnable() {
678: public void run() {
679: if (debugger.getState() != JPDADebugger.STATE_STOPPED) {
680: if (verbose)
681: System.out
682: .println("LTM cancel started task "
683: + task);
684: return;
685: }
686: if (verbose)
687: System.out.println("LTM do task "
688: + task);
689: ltm.fireTreeChanged();
690: }
691: }, 500);
692: if (verbose)
693: System.out.println("LTM create task " + task);
694: } else if ((e.getPropertyName() == JPDADebugger.PROP_STATE)
695: && (debugger.getState() != JPDADebugger.STATE_STOPPED)
696: && (task != null)) {
697: // debugger has been resumed
698: // =>> cancel task
699: task.cancel();
700: if (verbose)
701: System.out.println("LTM cancel task " + task);
702: task = null;
703: }
704: }
705: }
706:
707: /**
708: * The hierarchical representation of nested array elements.
709: * Used for arrays longer then {@link #ARRAY_CHILDREN_NESTED_LENGTH}.
710: */
711: private static final class ArrayChildrenNode {
712:
713: private AbstractObjectVariable var;
714: private int from = 0;
715: private int length;
716: private int maxIndexLog;
717:
718: public ArrayChildrenNode(AbstractObjectVariable var) {
719: this (var, 0, var.getFieldsCount(), -1);
720: }
721:
722: private ArrayChildrenNode(AbstractObjectVariable var, int from,
723: int length, int maxIndex) {
724: this .var = var;
725: this .from = from;
726: this .length = length;
727: if (maxIndex < 0) {
728: maxIndex = from + length - 1;
729: }
730: this .maxIndexLog = ArrayFieldVariable.log10(maxIndex);
731: }
732:
733: private static int pow(int a, int b) {
734: if (b == 0)
735: return 1;
736: int p = a;
737: for (int i = 1; i < b; i++) {
738: p *= a;
739: }
740: return p;
741: }
742:
743: public Object[] getChildren() {
744: if (length > ARRAY_CHILDREN_NESTED_LENGTH) {
745: int depth = (int) Math.ceil(Math.log(length)
746: / Math.log(ARRAY_CHILDREN_NESTED_LENGTH) - 1);
747: int n = pow(ARRAY_CHILDREN_NESTED_LENGTH, depth);
748: int numCh = (int) Math.ceil(length / ((double) n));
749:
750: // We have 'numCh' children, each with 'n' sub-children (or possibly less for the last one)
751: Object[] ch = new Object[numCh];
752: for (int i = 0; i < numCh; i++) {
753: int chLength = n;
754: if (i == (numCh - 1)) {
755: chLength = length % n;
756: if (chLength == 0)
757: chLength = n;
758: }
759: ch[i] = new ArrayChildrenNode(var, from + i * n,
760: chLength, from + length - 1);
761: }
762: return ch;
763: } else {
764: return var.getFields(from, from + length);
765: }
766: }
767:
768: public void update(AbstractObjectVariable var) {
769: this .var = var;
770: }
771:
772: /** Overriden equals so that the nodes are not re-created when not necessary. */
773: public boolean equals(Object obj) {
774: if (!(obj instanceof ArrayChildrenNode))
775: return false;
776: ArrayChildrenNode achn = (ArrayChildrenNode) obj;
777: return achn.var.equals(this .var) && achn.from == this .from
778: && achn.length == this .length;
779: }
780:
781: public int hashCode() {
782: return var.hashCode() + from + length;
783: }
784:
785: public String toString() {
786: int num0 = maxIndexLog - ArrayFieldVariable.log10(from);
787: String froms;
788: if (num0 > 0) {
789: froms = ArrayFieldVariable.zeros(2 * num0) + from; // One space is roughly 1/2 of width of a number
790: } else {
791: froms = Integer.toString(from);
792: }
793: int last = from + length - 1;
794: num0 = maxIndexLog - ArrayFieldVariable.log10(last);
795: String lasts;
796: if (num0 > 0) {
797: lasts = ArrayFieldVariable.zeros(2 * num0) + last; // One space is roughly 1/2 of width of a number
798: } else {
799: lasts = Integer.toString(last);
800: }
801: return "SubArray" + froms + "-" + lasts; // NOI18N
802: }
803:
804: }
805: }
|