001    /* 
002     * Copyright (c) Holger Pfaff - http://pfaff.ws
003     *
004     * This software maybe used for any purpose provided the
005     * above copyright notice is retained. It is supplied as is.
006     * No warranty expressed or implied - Use at your own risk.
007     */
008    
009    import java.io.PrintWriter;
010    import java.io.StringWriter;
011    import java.text.DateFormat;
012    import java.text.SimpleDateFormat;
013    import java.util.Date;
014    import java.util.Vector;
015    import java.util.TimeZone;
016    import java.util.Enumeration;
017    import java.util.StringTokenizer;
018    
019    /**
020      * (#)Debug.java
021      * @author   Holger Pfaff
022      * @version  3.2 19-Mar-2004<br><br> 
023      *
024      * Standalone class with static methods to aid with debugging.
025      * Debugging output can be controlled with different methods:<br><br>
026      * 
027      * 1) By specifying the class with should be debugged<br>
028      * 2) By specifying an optional "level" with should be debugged<br>
029      * 3) By specifying the "warning" flag<br>
030      * 
031      * The format can be controlled via some flags<br><br>
032      * 
033      * 1) Thread   (p): Shows the name of the current Thread<br>
034      * 2) Location (l): Shows the location within the source file<br>
035      * 3) Time     (t): Shows the current time including millisec.<br>
036      * 4) Delta    (d): Shows the time delta from the last debugging output<br>
037      * 5) Warning  (w): Shows warning messages (see above)<br>
038      * 6) Method   (m): Shows the name of the current method<br>
039      * 7) Class    (c): Shows the class name<br><br>
040      * 
041      * Location printing only works correct if there is no JIT Compiler active.
042      *
043      */
044     
045    public class Debug extends Object {
046    
047      /**
048       * Stream to write to
049       */
050      private static PrintWriter _writer = new PrintWriter(System.err);
051      
052      /**
053       * format to use for timestamps
054       */
055      private static DateFormat   _datef = new SimpleDateFormat("HH:mm:ss.SSS ");
056    
057      /**
058       * this is where we get our time from ...
059       */
060      private static Date          _date = new Date();
061     
062      /**
063       * show thread id ?  (p)
064       */
065      private static boolean _thread = false;
066    
067      /**
068       * show warnings ?   (w)
069       */
070      private static boolean _warn   = false;
071    
072      /**
073       * show location in source file ?  (l)
074       */
075      private static boolean _loc    = false;
076    
077      /**
078       * show methodname ?   (m)
079       */
080      private static boolean _meth   = false;
081    
082      /**
083       * show an absolute timestamp ?   (t)
084       */
085      private static boolean _time   = false;
086    
087      /**
088       * show a delta timestamp ?  (d)
089       */
090      private static boolean _delta  = false;
091    
092      /**
093       * show class name ?  (c)
094       */
095      private static boolean _cname  = false;
096      
097      /**
098       * store levels to debug
099       */
100      private static Vector  _level  = new Vector();
101    
102      /**
103       * store classes to debug
104       */
105      private static Vector  _class  = new Vector();
106      
107      /**
108       * empty string array - used internally for error conditions
109       */
110      private static String[] _nloc  = new String[0];
111        
112      /**
113       * Configure debugging from command line arg.
114       * Format shoud be: <flags>:<level>,...:<class>,...   
115       * e.g.   plt:*:Main 
116       * shows thread name, locaton & time for any level (*) and class Main
117       * 
118       * @param   a       the command line arg
119       */
120      public static void setArgs(String a) {
121        StringTokenizer t = new StringTokenizer(a, ":");
122        if(t.hasMoreTokens()) setFlags(t.nextToken(), true);
123        if(t.hasMoreTokens()) setLevel(t.nextToken(), true);
124        if(t.hasMoreTokens()) setClass(t.nextToken(), true);
125      }
126      
127      /**
128       * Set debugging flags
129       * 
130       * @param   f       flaf to set
131       * @param   b       set or re-set ?
132       */
133      public static void setFlags(String f, boolean b) {
134        Compiler.disable();
135        for(int i = 0; i < f.length(); ++i) {
136          switch(f.charAt(i)) {
137            case 'p': _thread = b; break;
138            case 'w': _warn   = b; break;
139            case 'm': _meth   = b; break;
140            case 'l': _loc    = b; break;
141            case 't': _time   = b; break;
142            case 'c': _cname  = b; break;
143            case 'd': _delta  = b; _datef.setTimeZone(TimeZone.getTimeZone("GMT")); break;
144            case '*': setFlags("pwltmc", b); break;
145          }
146        }
147      }
148      
149      /**
150       * Set/unset debugging levels,classes
151       * 
152       * @param   v       vector to use -  _levels, _classes
153       * @param   s       level or class to set/unset
154       * @param   a       set or unset?
155       */
156      private static void set(Vector v, String s, boolean a) {
157        if(s.indexOf(",") > -1) {
158          for(Enumeration e = new StringTokenizer(s, ",") ; e.hasMoreElements() ;) {
159            set(v, (String) e.nextElement(), a);
160          }
161        } else {
162          if(a) {
163            if(v.contains(s) == false) v.addElement(s);
164          } else {
165            v.removeElement(s);
166          }
167        }
168      }
169      
170      /**
171       * Set/unset debugging levels
172       * 
173       * @param   l       level to set/unset
174       * @param   a       set or unset?
175       */
176      public static void setLevel(String l, boolean a) {
177        set(_level, l, a);
178      }
179      
180      /**
181       * Set/unset debugging classes
182       * 
183       * @param   c       class to set/unset
184       * @param   a       set or unset?
185       */
186      public static void setClass(String c, boolean a) {
187        set(_class, c, a);
188      }
189      
190      /**
191       * Set/unset debugging classes
192       * 
193       * @param   c       class to set/unset
194       * @param   a       set or unset?
195       */
196      public static void setClass(Class c, boolean a) {
197        set(_class, c.getName(), a);
198      }
199      
200      /**
201       * Return classname of Object. o ist eiter:<br>
202       * - a Class object<br>
203       * - a String containing the class name<br>
204       * - or the Object itself<br>
205       * 
206       * @param   o       object to return classname of
207       * @return classname of Object as string
208       */
209      public static String getClassName(Object o) {
210        if(o instanceof Class) {
211          return ((Class) o).getName();
212        } else if(o instanceof String) {
213          return o.toString();
214        } else {
215          return o.getClass().getName();
216        }
217      }
218      
219      /**
220       * Report an error
221       *
222       * @param   m       usually the error message or Exception
223       */
224      public static void error(Object m) {
225        println("###ERROR: ", m);;
226      }
227              
228      /**
229       * Report a warning
230       *
231       * @param   m       usually the warning message or Exception
232       */
233      public static void warn(Object m) {
234        if(_warn) println("###WARN: ", m);
235      }
236    
237      /**
238       * Debug object o if object o is to debug - no additional message
239       *
240       * @param   o       Object to debug 
241       */
242      public static void debug(Object o) {
243        debug(o, "<>");
244      }
245      
246      /**
247       * Debug object o if object o is to debug on all levels - add message m
248       *
249       * @param   o       Object to debug
250       *
251       * @param   m       the message
252       */
253      public static void debug(Object o, Object m) {
254        debug("*", o, m);
255      }
256      
257      /**
258       * Debug object o if object o and level l is to debug - add message m
259       *
260       * @param   l       level
261       * @param   o       Object to debug
262       * @param   m       the message
263       */
264      public static void debug(Object l, Object o, Object m) {
265        if(isLevel(l) && isClass(o)) println(cname(o), m);
266      }
267    
268      /**
269       * Check if level l was requested to debug.
270       *
271       * @param   l       level
272       */
273      public static boolean isLevel(Object l) {
274        if(_level.contains("*")) return true;
275        if(l == null) return false;
276        return _level.contains(l);
277      }
278      
279      /**
280       * Check if class o was requested to debug.
281       *
282       * @param   o       class
283       */
284      public static boolean isClass(Object o) {
285        if(_class.contains("*")) return true;
286        if(o == null) return false;
287        if(o instanceof String) return _class.contains(o.toString());
288        if(o instanceof Class == false) o = o.getClass();
289        for(Class c = (Class) o; c != null; c = c.getSuperclass()) {
290          if(_class.contains(c.getName())) return true;
291        }
292        return false;
293      }
294      
295      
296      /**
297       * form a debug string
298       *
299       * @param   p       prefix (e.g. timestamp)
300       * @param   m       the message
301       *
302       * @return a nice formatted string
303       */
304      public static String toString(String p, Object m) {
305        String[] l = _meth || _loc ? stackline(0) : _nloc;
306        return time() + thread() + stackline2meth(l) + p + msg(m) + stackline2loc(l);
307      }
308      
309      /**
310       * print a debug string
311       *
312       * @param   p       prefix (e.g. timestamp)
313       * @param   m       the message
314       */
315      public static void println(String p, Object m) {
316        _writer.println(toString(p, m));
317        _writer.flush();
318      }
319      
320      /**
321       * put out a timestamp if requested
322       */
323      public static String time() {
324        if(_time || _delta) {
325          Date d = _date;
326          _date = new Date();
327          d.setTime(_delta ? _date.getTime() - d.getTime() : _date.getTime());
328          return _datef.format(d);
329        } else {
330          return "";
331        }
332      }
333      
334      /**
335       * put out a classname if requested
336       *
337       * @param   o       object to put out classname for
338       */
339      public static String cname(Object o) {
340        return _cname ? "(" + o.getClass().getName() + ") " : "";
341      }
342    
343      /**
344       * put out a threadname if requested
345       */
346      public static String thread() {
347        return _thread ? "[" + Thread.currentThread().getName() + "] " : "";
348      }
349      
350      /**
351       * format the error msg
352       *
353       * @param   o       object to format
354       */
355      public static String msg(Object o){
356        return o == null ? "<null>" : (o instanceof Exception ? msg((Exception) o) : o.toString());
357      }
358      
359      /**
360       * format the error msg
361       *
362       * @param   x       Exception to format
363       */
364      public static String msg(Exception x) {
365        String m = x.getMessage();
366        String n = x.getClass().getName();
367        n = n.substring(n.lastIndexOf('.') + 1);
368        if(m == null || m.length() == 0) {
369          return n;
370        } else {
371          return n + ": " + m;
372        }
373      }
374            
375      /**
376       * return stack trace as string
377       *
378       * @param   x       Exception to format
379       */
380      public static String stack(Exception X) {
381        try {
382          StringWriter b = new StringWriter();
383          PrintWriter  w = new PrintWriter(b);
384          X.printStackTrace(w);
385                      w.close();
386                      b.close();
387                      return b.toString().replace('\r', ' ');
388        } catch(Exception x) {
389          return "";
390        }
391            }
392      
393      /**
394       * return specific stackline - 0 is where exception occured
395       *
396       * @param   0       position in stack to return
397       */
398      public static String[] stackline(int s){
399        StringTokenizer t = new StringTokenizer(stack(new Exception()), "\n\r");
400        for(int i = 0; t.hasMoreTokens();) {
401                      String l = t.nextToken().trim();       // get next line & remove blanks
402          if(l.startsWith("java") || l.startsWith("at Debug")) {
403            continue;                            // remove first line & self
404          } 
405          if(i++ == s) {                         // reached desired depth ?
406            StringTokenizer st = new StringTokenizer(l, " ()");
407            String[] sl = new String[st.countTokens()];
408            for(int j = 0; j < sl.length; ++j) sl[j] = st.nextToken();
409            return sl;
410          }
411        }
412                    return _nloc;                            // return empty array on failure
413      }
414    
415      /**
416       * extract methodname from stackline
417       */
418      public static String stackline2meth(String[] l) { 
419        return _meth && l.length > 1 ? "{" + l[1] + "} " : "";
420      }
421      
422      /**
423       * extract location in source from stackline
424       */
425      public static String stackline2loc(String[] l) {
426        return _loc && l.length > 2 ? " (at " + l[2] + ")" : "";
427      }
428    }