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