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