001: /*
002: $Id: LazyNode.java,v 1.11 2005/03/11 13:14:49 vauclair Exp $
003:
004: Copyright (C) 2002-2005 Sebastien Vauclair
005:
006: This file is part of Extensible Java Profiler.
007:
008: Extensible Java Profiler is free software; you can redistribute it and/or
009: modify it under the terms of the GNU General Public License as published by
010: the Free Software Foundation; either version 2 of the License, or
011: (at your option) any later version.
012:
013: Extensible Java Profiler is distributed in the hope that it will be useful,
014: but WITHOUT ANY WARRANTY; without even the implied warranty of
015: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016: GNU General Public License for more details.
017:
018: You should have received a copy of the GNU General Public License
019: along with Extensible Java Profiler; if not, write to the Free Software
020: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
021: */
022:
023: package ejp.presenter.api.model.tree;
024:
025: import java.awt.Color;
026: import java.util.Enumeration;
027: import java.util.Vector;
028: import java.util.logging.Level;
029:
030: import javax.swing.tree.TreeNode;
031:
032: import ejp.presenter.api.filters.AbstractFilterImpl;
033: import ejp.presenter.api.model.LoadedMethod;
034: import ejp.presenter.api.util.CustomLogger;
035: import ejp.presenter.api.util.ValuesRenderer;
036: import ejp.presenter.parser.IMethodTimeNode;
037:
038: /**
039: * Super class of all presented call tree nodes.
040: *
041: * Implements lazy application of filters.
042: *
043: * @author Sebastien Vauclair
044: * @version <code>$Revision: 1.11 $<br>$Date: 2005/03/11 13:14:49 $</code>
045: */
046: public class LazyNode implements TreeNode {
047: // ///////////////////////////////////////////////////////////////////////////
048: // PROTECTED FIELDS
049: // ///////////////////////////////////////////////////////////////////////////
050:
051: /**
052: * Parent node.
053: *
054: * Might be <code>null</code> for a root node.
055: */
056: protected LazyNode parent;
057:
058: /**
059: * Array of filters to apply.
060: *
061: * Is not <code>null</code> iff the node is a root node.
062: */
063: protected AbstractFilterImpl[] filterImpls;
064:
065: /**
066: * Method time node associated with this node.
067: *
068: * Might not be <code>null</code> in <code>LazyNode</code> class.
069: */
070: protected final IMethodTimeNode methodTimeNode;
071:
072: /**
073: * Current list of children nodes.
074: *
075: * This is a list of <code>LazyNode</code> instances.
076: */
077: protected Vector children;
078:
079: /**
080: * Number of last filter fully applied.
081: *
082: * If equal to <code>-1</code>, no filter was applied.
083: */
084: protected int lastAppliedFilter;
085:
086: /**
087: * Node's text color.
088: *
089: * Default color is black.
090: */
091: protected Color color = Color.black;
092:
093: /**
094: * Node's text.
095: *
096: * If <code>null</code>, the associated method (if any) will be used to get
097: * the node's caption.
098: */
099: protected String text = null;
100:
101: /**
102: * Prefix text.
103: */
104: protected String prefix = "";
105:
106: /**
107: * Suffix text.
108: */
109: protected String suffix = "";
110:
111: /**
112: * Own time + children time.
113: */
114: protected long totalTime = 0;
115:
116: /**
117: * Time spent in own body.
118: */
119: protected long ownTime = 0;
120:
121: /**
122: * Ratio of time spent in sub-tree rooted at this node.
123: *
124: * Default value is <code>-1</code> (N/A).
125: */
126: protected double ratio = -1;
127:
128: /**
129: * Total execution time of profiled program.
130: *
131: * <code>-1</code> means not yet initialized.
132: */
133: protected long profileTime = -1;
134:
135: protected long entryTime = -1;
136:
137: protected long exitTime = -1;
138:
139: // ///////////////////////////////////////////////////////////////////////////
140: // CONSTRUCTORS
141: // ///////////////////////////////////////////////////////////////////////////
142:
143: /**
144: * Creates a new lazy node instance that mirrors one single method time node
145: * instance.
146: *
147: * @param aMethodTimeNode
148: * associated <code>MethodTimeNode</code> value.
149: */
150: protected LazyNode(IMethodTimeNode aMethodTimeNode) {
151: invalidateChildren();
152: methodTimeNode = aMethodTimeNode;
153:
154: if (methodTimeNode != null) {
155: totalTime = aMethodTimeNode.getTotalTime();
156: ownTime = aMethodTimeNode.getTime();
157: entryTime = aMethodTimeNode.getEntryTime();
158: exitTime = aMethodTimeNode.getExitTime();
159: }
160: }
161:
162: /**
163: * Creates a root lazy node mirroring a root method time node.
164: *
165: * @param aMethodTimeNode
166: * associated <code>MethodTimeNode</code> value.
167: * @param aFilterImpls
168: * array of filters to apply on tree.
169: */
170: public LazyNode(IMethodTimeNode aMethodTimeNode,
171: AbstractFilterImpl[] aFilterImpls) {
172: this (aMethodTimeNode);
173: parent = null;
174: filterImpls = aFilterImpls;
175:
176: // apply all filters to this root node (for node renderers)
177: for (int i = 0; i < aFilterImpls.length; i++)
178: aFilterImpls[i].applyToRoot(this );
179: }
180:
181: /**
182: * Creates a new child lazy node instance that mirrors one single method time
183: * node instance.
184: *
185: * @param aMethodTimeNode
186: * associated <code>MethodTimeNode</code> value.
187: * @param aParent
188: * the new node's parent.
189: */
190: public LazyNode(IMethodTimeNode aMethodTimeNode, LazyNode aParent) {
191: this (aMethodTimeNode);
192: parent = aParent;
193: filterImpls = null;
194: }
195:
196: // ///////////////////////////////////////////////////////////////////////////
197: // CHILDREN BUILDING AND PROCESSING (FILTERS APPLICATION)
198: // ///////////////////////////////////////////////////////////////////////////
199:
200: /**
201: * Builds the list of children lazy nodes without any processing.
202: *
203: * The method implemented in this class builds the list of lazy nodes from
204: * associated method time node's children.
205: *
206: * @return a list of <code>LazyNode</code> values.
207: */
208: protected Vector buildChildren() {
209: IMethodTimeNode[] childrenMTN = methodTimeNode.getChildren();
210:
211: // ask it again after MTN children computation
212: ownTime = methodTimeNode.getTime();
213:
214: int nb = childrenMTN.length;
215: Vector result = new Vector(nb);
216: for (int i = 0; i < nb; i++)
217: result
218: .add(new LazyNode(
219: childrenMTN[i] /* method time node */,
220: this /* parent */));
221: return result;
222: }
223:
224: /**
225: * Computes the fully-processed list of children of this node.
226: *
227: * Calls <code>buildChildren()</code> to generate the list of children lazy
228: * nodes before processing it.
229: */
230: protected final void computeChildren() {
231: // get array of filters impl
232: AbstractFilterImpl[] filters = getFilterImpls();
233:
234: // build list of children
235: children = buildChildren();
236:
237: // apply all filters
238: for (int i = 0; i < filters.length; i++) {
239: lastAppliedFilter = i;
240:
241: try {
242: filters[i].applyTo(this , children);
243: } catch (Exception e) {
244: CustomLogger.INSTANCE.log(Level.WARNING,
245: "An exception occured while applying filter "
246: + filters[i], e);
247: throw new RuntimeException(e);
248: }
249: }
250: }
251:
252: /**
253: * Invalidates current list of children nodes.
254: *
255: * This forces it to be computed again next time it is needed.
256: */
257: protected void invalidateChildren() {
258: children = null;
259: lastAppliedFilter = -1;
260: }
261:
262: // ///////////////////////////////////////////////////////////////////////////
263: // HANDLING OF APPLIED FILTERS
264: // ///////////////////////////////////////////////////////////////////////////
265:
266: /**
267: * Gets array of filters to be applied to this node.
268: *
269: * This array depends on the filters already applied on the node's parent.
270: *
271: * @return an <code>AbstractFilterImpl[]</code> value.
272: */
273: protected AbstractFilterImpl[] getFilterImpls() {
274: if (parent == null)
275: return filterImpls; // root node
276: return parent.getAppliedFilters(); // child node
277: }
278:
279: /**
280: * Gets array of filters applied on parent node.
281: *
282: * @return an <code>AbstractFilterImpl[]</code> value.
283: */
284: protected AbstractFilterImpl[] getAppliedFilters() {
285: AbstractFilterImpl[] result = new AbstractFilterImpl[lastAppliedFilter + 1];
286:
287: AbstractFilterImpl[] filters = getFilterImpls();
288: for (int i = 0; i <= lastAppliedFilter; i++)
289: result[i] = filters[i];
290: return result;
291:
292: // no longer returns all filters
293: // return getFilterImpls();
294: }
295:
296: // ///////////////////////////////////////////////////////////////////////////
297: // TreeNode INTERFACE
298: // ///////////////////////////////////////////////////////////////////////////
299:
300: public TreeNode getParent() {
301: return parent;
302: }
303:
304: public TreeNode getChildAt(int aIndex) {
305: if (children == null)
306: computeChildren();
307: return (LazyNode) children.get(aIndex);
308: }
309:
310: public int getChildCount() {
311: if (children == null)
312: computeChildren();
313: return children.size();
314: }
315:
316: public int getIndex(TreeNode aTreeNode) {
317: if (children == null)
318: computeChildren();
319: return children.indexOf(aTreeNode);
320: }
321:
322: public boolean getAllowsChildren() {
323: return !isLeaf();
324: }
325:
326: public boolean isLeaf() {
327: if (children == null)
328: return false; // before computation, all nodes are openable
329: return children.isEmpty();
330: }
331:
332: public Enumeration children() {
333: if (children == null)
334: computeChildren();
335: return children.elements(); // this respects laziness
336: }
337:
338: // ///////////////////////////////////////////////////////////////////////////
339: // CONVENIENCE METHODS
340: // ///////////////////////////////////////////////////////////////////////////
341:
342: /**
343: * Convenience method to compute children only on first access.
344: *
345: * @return the list of children of this node.
346: */
347: public Vector getChildren() {
348: if (children == null)
349: computeChildren();
350: return children;
351: }
352:
353: // ///////////////////////////////////////////////////////////////////////////
354: // WRAPPERS TO MethodTimeNode METHODS
355: // ///////////////////////////////////////////////////////////////////////////
356:
357: /**
358: * Gets the description of the method associated with this node.
359: *
360: * @return a <code>LoadedMethod</code> value.
361: */
362: public LoadedMethod getMethod() {
363: return methodTimeNode.getMethod();
364: }
365:
366: // ///////////////////////////////////////////////////////////////////////////
367: // TREE NAVIGATION
368: // ///////////////////////////////////////////////////////////////////////////
369:
370: /**
371: * Gets the parent lazy node of this node, if any.
372: *
373: * @return <code>null</code> iff this node is a root.
374: */
375: public LazyNode getParentLazyNode() {
376: return parent;
377: }
378:
379: /**
380: * Inserts a lazy child node at a given index.
381: *
382: * This requires application of filters.
383: *
384: * @param aChild
385: * a <code>LazyNode</code> value.
386: * @param aIndex
387: * index of insertion (if <code>-1</code>, insert at the end).
388: */
389: protected void insert(LazyNode aChild, int aIndex) {
390: if (children == null)
391: computeChildren();
392: if (aIndex == -1)
393: children.add(aChild);
394: else
395: children.insertElementAt(aChild, aIndex);
396: }
397:
398: /**
399: * Removes a child lazy node.
400: *
401: * @param aChild
402: * a <code>LazyNode</code> value.
403: * @return <code>true</code> iff the node was present.
404: */
405: public boolean remove(LazyNode aChild) {
406: if (children == null)
407: computeChildren();
408: return children.remove(aChild);
409: }
410:
411: /**
412: * Removes this node from its parent, thus removing it from the tree.
413: *
414: * @return <code>false</code> iff the node was not present in its parent.
415: */
416: public boolean removeFromParent() {
417: return (parent == null ? true : parent.remove(this ));
418: }
419:
420: /**
421: * Changes the parent of this node, by inserting this node at a given index.
422: *
423: * Automatically calls <code>removeFromParent()</code> and inserts into new
424: * parent's children.
425: *
426: * @param aParent
427: * a <code>LazyNode</code> value.
428: * @param aInsertionIndex
429: * index of insertion (if <code>-1</code>, insert at the end).
430: */
431: public void setParent(LazyNode aParent, int aInsertionIndex) {
432: setParent(aParent);
433: aParent.insert(this , aInsertionIndex);
434: filterImpls = null;
435: }
436:
437: /**
438: * Changes the parent of this node.
439: *
440: * Automatically calls <code>removeFromParent()</code>.
441: *
442: * @param aParent
443: * a <code>LazyNode</code> value.
444: */
445: public void setParent(LazyNode aParent) {
446: removeFromParent();
447: parent = aParent;
448: }
449:
450: // ///////////////////////////////////////////////////////////////////////////
451: // TIME
452: // ///////////////////////////////////////////////////////////////////////////
453:
454: /**
455: * Adds <i>own </i> time.
456: *
457: * @param aTime
458: * a <code>long</code> value.
459: */
460: public void addOwnTime(long aTime) {
461: ownTime += aTime;
462: }
463:
464: /**
465: * Adds <i>own </i> time and updates <i>total </i> time.
466: *
467: * @param aTime
468: * a <code>long</code> value.
469: */
470: public void addOwnAndTotalTime(long aTime) {
471: ownTime += aTime;
472: totalTime += aTime;
473: }
474:
475: /**
476: * Gets <i>own </i> time.
477: *
478: * @return a <code>long</code> value.
479: */
480: public long getOwnTime() {
481: return ownTime;
482: }
483:
484: /**
485: * Gets <i>total </i> time.
486: *
487: * @return a <code>long</code> value.
488: */
489: public long getTotalTime() {
490: return totalTime;
491: }
492:
493: /**
494: * Gets <i>profile </i> time, i.e. total execution time of the program.
495: *
496: * @return a <code>long</code> value.
497: */
498: public long getProfileTime() {
499: if (profileTime == -1) {
500: if (parent == null) {
501: profileTime = totalTime;
502: } else {
503: profileTime = parent.getProfileTime();
504: if (profileTime == -1) {
505: profileTime = totalTime;
506: }
507: }
508: }
509:
510: return profileTime;
511: }
512:
513: /**
514: * Returns the ratio of time spent in this node with respect to profile time.
515: *
516: * @return a <code>double</code> value.
517: */
518: public double getRatioOfProfileTime() {
519: long profTime = getProfileTime();
520: if (profTime == 0) {
521: return 0; // N/A
522: }
523: if (profTime == -1) {
524: return 0; // N/A
525: }
526: return ((double) getTotalTime()) / ((double) profTime);
527: }
528:
529: /**
530: * Returns the ratio of time spent in this node with respect to parent's time.
531: *
532: * @return a <code>double</code> value.
533: */
534: public double getRatioOfParentTime() {
535: if (parent == null)
536: return 1;
537:
538: long parentTime = parent.getTotalTime();
539: if (parentTime == 0)
540: return 0; // N/A
541: if (parentTime == -1)
542: return -1; // N/A
543:
544: return ((double) getTotalTime()) / ((double) parentTime);
545: }
546:
547: // ///////////////////////////////////////////////////////////////////////////
548: // COLOR / RATIO
549: // ///////////////////////////////////////////////////////////////////////////
550:
551: /**
552: * Sets this node's color.
553: *
554: * @param aColor
555: * a <code>Color</code> value.
556: */
557: public void setColor(Color aColor) {
558: color = aColor;
559: }
560:
561: /**
562: * Gets current color of the node.
563: *
564: * Called by GUI to know how to display the node.
565: *
566: * @return a <code>Color</code> value.
567: */
568: public Color getColor() {
569: return color;
570: }
571:
572: /**
573: * Sets the value of the ratio associated with this node.
574: *
575: * @param aRatio
576: * a <code>double</code> value.
577: */
578: public void setRatio(double aRatio) {
579: ratio = aRatio;
580: }
581:
582: /**
583: * Gets the value of the ratio associated with this node.
584: *
585: * @return a <code>double</code> value.
586: */
587: public double getRatio() {
588: return ratio;
589: }
590:
591: // ///////////////////////////////////////////////////////////////////////////
592: // DISPLAY METHODS
593: // ///////////////////////////////////////////////////////////////////////////
594:
595: /**
596: * Adds a prefix text.
597: *
598: * @param aText
599: * a <code>String</code> value.
600: */
601: public void addPrefix(String aText) {
602: prefix += aText + " ";
603: }
604:
605: /**
606: * Adds a suffix text.
607: *
608: * @param aText
609: * a <code>String</code> value.
610: */
611: public void addSuffix(String aText) {
612: suffix += " " + aText;
613: }
614:
615: /**
616: * Gets the node's text.
617: *
618: * @return a <code>String</code> value.
619: */
620: public String getText() {
621: if (text != null)
622: return text;
623:
624: LoadedMethod meth = getMethod();
625: if (meth != null)
626: return getMethod().toNodeLabel();
627: return "<root>";
628: }
629:
630: /**
631: * Sets the node's text, thus overriding the associated method's name, if any.
632: *
633: * @param aText
634: * a <code>String</code> value.
635: */
636: public void setText(String aText) {
637: text = aText;
638: }
639:
640: /**
641: * Returns the node's caption (prefix + text + suffix).
642: *
643: * @return a <code>String</code> value.
644: */
645: public String toString() {
646: return prefix + getText() + suffix;
647: }
648:
649: /**
650: * Returns the node's tooltip text.
651: *
652: * @return a <code>String</code> value.
653: */
654: public String getToolTipText() {
655: StringBuffer buffer = new StringBuffer();
656:
657: // body time
658: buffer.append("<font color=blue>Time spent in body</font>: ");
659: buffer
660: .append((children == null ? "? (<i>expand to compute</i>)"
661: : ValuesRenderer.renderTime(getOwnTime(), true)));
662:
663: // source
664: buffer.append("<br><font color=blue>Source</font>: ");
665: LoadedMethod meth = getMethod();
666: buffer.append((meth == null ? "N/A"
667: : meth.ownerClass.sourceFile + ":" + meth.sourceLine));
668:
669: // entry time
670: if (entryTime != -1) {
671: buffer.append("<br><font color=blue>Entry time</font>: ");
672: buffer.append(ValuesRenderer.renderTime(entryTime, true));
673: }
674:
675: // exit time
676: if (exitTime != -1) {
677: buffer.append("<br><font color=blue>Exit time</font>: ");
678: buffer.append(ValuesRenderer.renderTime(exitTime, true));
679: }
680:
681: return buffer.toString();
682: }
683:
684: // ///////////////////////////////////////////////////////////////////////////
685: // MISC
686: // ///////////////////////////////////////////////////////////////////////////
687:
688: /**
689: * Recursively gets the node's depth in the tree.
690: *
691: * @return an <code>int</code> value.
692: */
693: public int getDepth() {
694: return (parent == null ? 0 : parent.getDepth() + 1);
695: }
696: }
|