• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  *******************************************************************************
3  * Copyright (C) 2012 International Business Machines Corporation and          *
4  * others. All Rights Reserved.                                                *
5  *******************************************************************************
6  */
7 
8 package org.unicode.cldr.util;
9 
10 import java.util.Hashtable;
11 import java.util.Iterator;
12 import java.util.Map;
13 import java.util.function.Predicate;
14 
15 /**
16  * Debugging utility.
17  *
18  * A StackTracker tracks which stack frame various objects were created from.
19  * For example, call add() and remove() alongside some cache, and then StackTracker's toString() will
20  * print out the stack frame of all adds() not balanced by remove().
21  *
22  * Objects must be Comparable.
23  *
24  * Example use is in the main() at the bottom. Outputs:
25  *
26  * "{StackTracker:
27  * Held Obj #1/2: the
28  * org.unicode.cldr.util.StackTracker.currentStack(StackTracker.java:92)
29  * org.unicode.cldr.util.StackTracker.add(StackTracker.java:34)
30  * org.unicode.cldr.util.StackTracker.main(StackTracker.java:118)
31  * ...}"
32  *
33  * @author srl
34  */
35 @CLDRTool(alias = "test.stacktracker", description = "Test for StackTracker", hidden = "test")
36 public class StackTracker implements Iterable<Object> {
37     private Hashtable<Object, String> stacks = new Hashtable<>();
38 
39     /**
40      * Add object (i.e. added to cache)
41      *
42      * @param o
43      */
add(Object o)44     public void add(Object o) {
45         String stack = currentStack();
46         stacks.put(o, stack);
47     }
48 
49     /**
50      * remove obj (i.e. removed from cache)
51      *
52      * @param o
53      */
remove(Object o)54     public void remove(Object o) {
55         stacks.remove(o);
56     }
57 
58     /**
59      * internal - convert a stack to string
60      *
61      * @param stackTrace
62      * @param skip
63      *            start at this index (skip the top stuff)
64      * @return
65      */
stackToString(StackTraceElement[] stackTrace, int skip)66     public static String stackToString(StackTraceElement[] stackTrace, int skip) {
67         StringBuffer sb = new StringBuffer();
68         for (int i = skip; i < stackTrace.length; i++) {
69             sb.append(stackTrace[i].toString() + "\n");
70         }
71         return sb.toString();
72     }
73 
74     /**
75      * Get this tracker as a string. Prints any leaked objects, and the stack frame of where they were constructed.
76      */
77     @Override
toString()78     public String toString() {
79         if (stacks.isEmpty()) {
80             return "{StackTracker: empty}";
81         }
82         StringBuffer sb = new StringBuffer();
83 
84         sb.append("{StackTracker:\n");
85         int n = 0;
86         for (Map.Entry<Object, String> e : stacks.entrySet()) {
87             sb.append("Held Obj #" + (++n) + "/" + stacks.size() + ": " + e.getKey() + "\n");
88             sb.append(e.getValue() + "\n");
89         }
90         sb.append("}");
91         return sb.toString();
92     }
93 
94     /**
95      * Purges all held objects.
96      */
clear()97     public void clear() {
98         stacks.clear();
99     }
100 
101     /**
102      * Convenience function, gets the current stack trace.
103      *
104      * @return current stack trace
105      */
currentStack()106     public static String currentStack() {
107         return stackToString(Thread.currentThread().getStackTrace(), 2);
108     }
109 
110     /**
111      * Convenience function, gets the current element
112      *
113      * @param stacks
114      *            to skip - 0 for immediate caller, 1, etc
115      */
currentElement(int skip)116     public static StackTraceElement currentElement(int skip) {
117         return Thread.currentThread().getStackTrace()[3 + skip];
118     }
119 
120     /**
121      * Return the 'calling' element, skipping
122      * @param matchFirst matching predicate
123      * @return first matching stack. If none match, return currentElement(0)
124      * Example: to skip callers in my own class:
125      * currentElement(
126      *  (StackTraceElement s) ->
127      *    !s.getClassName().equals(MyClass.class.getName()));
128      */
firstCallerMatching(Predicate<StackTraceElement> matchFirst)129     public static StackTraceElement firstCallerMatching(Predicate<StackTraceElement> matchFirst) {
130         final StackTraceElement stacks[] = Thread.currentThread().getStackTrace();
131         for (int i=3; i<stacks.length; i++) {
132             if (matchFirst.test(stacks[i])) {
133                 return stacks[i];
134             }
135         }
136         return stacks[3];
137     }
138 
139     /**
140      *
141      * @return true if there are no held objects
142      */
isEmpty()143     public boolean isEmpty() {
144         return stacks.isEmpty();
145     }
146 
147     /**
148      * Iterate over held objects.
149      */
150     @Override
iterator()151     public Iterator<Object> iterator() {
152         return stacks.keySet().iterator();
153     }
154 
155     /**
156      * Example use.
157      *
158      * @param args
159      *            ignored
160      */
main(String args[])161     public static void main(String args[]) {
162         StackTracker tracker = new StackTracker();
163         System.out.println("At first: " + tracker);
164 
165         tracker.add("Now");
166         tracker.add("is");
167         tracker.add("the");
168         tracker.add("time");
169         tracker.add("for");
170         tracker.add("time");
171         tracker.remove("Now");
172         tracker.remove("for");
173         tracker.remove("time");
174 
175         // any leaks?
176         System.out.println("At end: " + tracker);
177     }
178 }
179