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