001package org.javasimon.callback.calltree;
002
003import java.io.PrintWriter;
004import java.io.StringWriter;
005import java.util.ArrayList;
006import java.util.Collection;
007import java.util.Collections;
008import java.util.HashMap;
009import java.util.List;
010import java.util.Map;
011
012import org.javasimon.Split;
013import org.javasimon.utils.SimonUtils;
014
015/**
016 * Call tree node is one Simon one for one call level, all splits for this
017 * Simon+Level(+Thread) tuple are kept for later analysis.
018 * Simon name is unique within parent tree node. Said differently a tree node can
019 * not have two children with same name.
020 *
021 * @author gquintana
022 * @since 3.2
023 */
024public class CallTreeNode {
025        /**
026         * Name, used as a key.
027         */
028        private final String name;
029
030        /**
031         * Splits.
032         * Size defaults to 1, because most of the time there are no loops.
033         */
034        private final List<Split> splits = new ArrayList<>(1);
035
036        /**
037         * Child tree nodes.
038         */
039        private Map<String, CallTreeNode> children;
040
041        /**
042         * Parent tree node. {@code null} for root tree node.
043         */
044        private CallTreeNode parent;
045
046        /**
047         * Main constructor.
048         *
049         * @param name Simon name
050         */
051        public CallTreeNode(String name) {
052                this.name = name;
053        }
054
055        /**
056         * Returns Simon name.
057         *
058         * @return Name
059         */
060        public String getName() {
061                return name;
062        }
063
064        /**
065         * Adds a split to the current tree node.
066         * In case of loops, child nodes can have many splits.
067         *
068         * @param split Split
069         */
070        public void addSplit(Split split) {
071                splits.add(split);
072        }
073
074        /**
075         * Returns the number of splits in this node.
076         *
077         * @return Split count
078         */
079        public int getSplitCount() {
080                return splits.size();
081        }
082
083        /**
084         * Returns the total time of splits using {@link org.javasimon.Split#runningFor()}.
085         *
086         * @return total time of splits
087         */
088        public long getTotal() {
089                long total = 0;
090                for (Split split : splits) {
091                        total += split.runningFor();
092                }
093                return total;
094        }
095
096        /**
097         * Returns the part of time spent in this node compared to parent.
098         *
099         * @return Percent time
100         */
101        public Integer getPercent() {
102                Integer percent;
103                if (parent == null) {
104                        percent = null;
105                } else {
106                        percent = (int) (getTotal() * 100L / getParent().getTotal());
107                }
108                return percent;
109        }
110
111        /**
112         * Adds a child to this tree node.
113         *
114         * @param name Child Simon name
115         * @return Created child node
116         */
117        public CallTreeNode addChild(String name) {
118                if (children == null) {
119                        children = new HashMap<>();
120                }
121                CallTreeNode child = new CallTreeNode(name);
122                children.put(name, child);
123                child.parent = this;
124                return child;
125        }
126
127        /**
128         * Returns the child node by Simon name.
129         *
130         * @param name Simon name
131         * @return Child corresponding to given name, or null if any
132         */
133        public CallTreeNode getChild(String name) {
134                return (children == null) ? null : children.get(name);
135        }
136
137        /**
138         * Returns all child nodes.
139         *
140         * @return children
141         */
142        public Collection<CallTreeNode> getChildren() {
143                return children == null ? Collections.<CallTreeNode>emptyList() : children.values();
144        }
145
146        /**
147         * Returns a child node with given name or creates it if it does not exists.
148         *
149         * @param name Simon name
150         * @return Child node
151         */
152        public CallTreeNode getOrAddChild(String name) {
153                CallTreeNode child = getChild(name);
154                if (child == null) {
155                        child = addChild(name);
156                }
157                return child;
158        }
159
160        /**
161         * Returns parent tree node.
162         *
163         * @return Parent tree node
164         */
165        public CallTreeNode getParent() {
166                return parent;
167        }
168
169        /**
170         * Recursively prints this tree node to given print writer.
171         *
172         * @param printWriter Output print writer
173         * @param prefix Line prefix (used internally for indentation)
174         * @param parentTotal Duration of parent node (used to compute duration ratio for child nodes), null for root nodes
175         */
176        private void print(PrintWriter printWriter, String prefix, Long parentTotal) {
177                long total = getTotal();
178                printWriter.print(prefix);
179                printWriter.print(name);
180                printWriter.print(' ');
181                if (parentTotal != null && parentTotal != 0L) {
182                        printWriter.print(total * 100 / parentTotal);
183                        printWriter.print("%, ");
184                }
185                printWriter.print(SimonUtils.presentNanoTime(total));
186                long counter = getSplitCount();
187                if (counter > 1) {
188                        printWriter.print(", ");
189                        printWriter.print(counter);
190                }
191                printWriter.println();
192                for (CallTreeNode child : getChildren()) {
193                        child.print(printWriter, prefix + "\t", total);
194                }
195        }
196
197        /**
198         * Recursively prints this tree node to given print writer.
199         *
200         * @param printWriter Output print writer
201         */
202        public void print(PrintWriter printWriter) {
203                print(printWriter, "", null);
204        }
205
206        /**
207         * Returns a string representing the tree from this tree node, visiting recursively this tree branch.
208         *
209         * @return String
210         */
211        @Override
212        public String toString() {
213                StringWriter stringWriter = new StringWriter();
214                PrintWriter printWriter = new PrintWriter(stringWriter);
215                print(printWriter);
216                return stringWriter.toString();
217        }
218}