001: package com.protomatter.util;
002:
003: /**
004: * {{{ The Protomatter Software License, Version 1.0
005: * derived from The Apache Software License, Version 1.1
006: *
007: * Copyright (c) 1998-2002 Nate Sammons. All rights reserved.
008: *
009: * Redistribution and use in source and binary forms, with or without
010: * modification, are permitted provided that the following conditions
011: * are met:
012: *
013: * 1. Redistributions of source code must retain the above copyright
014: * notice, this list of conditions and the following disclaimer.
015: *
016: * 2. Redistributions in binary form must reproduce the above copyright
017: * notice, this list of conditions and the following disclaimer in
018: * the documentation and/or other materials provided with the
019: * distribution.
020: *
021: * 3. The end-user documentation included with the redistribution,
022: * if any, must include the following acknowledgment:
023: * "This product includes software developed for the
024: * Protomatter Software Project
025: * (http://protomatter.sourceforge.net/)."
026: * Alternately, this acknowledgment may appear in the software itself,
027: * if and wherever such third-party acknowledgments normally appear.
028: *
029: * 4. The names "Protomatter" and "Protomatter Software Project" must
030: * not be used to endorse or promote products derived from this
031: * software without prior written permission. For written
032: * permission, please contact support@protomatter.com.
033: *
034: * 5. Products derived from this software may not be called "Protomatter",
035: * nor may "Protomatter" appear in their name, without prior written
036: * permission of the Protomatter Software Project
037: * (support@protomatter.com).
038: *
039: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
040: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
041: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
042: * DISCLAIMED. IN NO EVENT SHALL THE PROTOMATTER SOFTWARE PROJECT OR
043: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
044: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
045: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
046: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
047: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
048: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
049: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
050: * SUCH DAMAGE. }}}
051: */
052:
053: import java.io.*;
054: import java.util.*;
055: import java.text.*;
056: import org.apache.oro.text.regex.*;
057:
058: /**
059: * A simple hierarchical namespace utility.
060: * Maintains a static list of names and/or patterns
061: * to match names. Dots (".") provide hierarchy markers.
062: * For instance, if you add the following patterns:<P>
063: *
064: * <dl>
065: * <dd><tt>com.protomatter</tt></dd>
066: * <dd><tt>com.protomatter.syslog.*</tt></dd>
067: * <dd><tt>com.protomatter.util.*</tt></dd>
068: * <dd><tt>com.protomatter.foo.bar.*</tt></dd>
069: * </dl><P>
070: *
071: * Then the following names would "match" the
072: * patterns if the "scanning" feature is turned on:<P>
073: *
074: * <dl>
075: * <dd><tt>com.protomatter</tt></dd>
076: * <dd><tt>com.protomatter.syslog</tt></dd>
077: * <dd><tt>com.protomatter.syslog.xml</tt></dd>
078: * <dd><tt>com.protomatter.util.other.package</tt></dd>
079: * </dl><P>
080: *
081: * If you have "scanning" turned off, then only the
082: * following names would match:<P>
083: *
084: * <dl>
085: * <dd><tt>com.protomatter</tt></dd>
086: * </dl><P>
087: *
088: * And the following names would not "match" the
089: * patterns:<P>
090: *
091: * <dl>
092: * <dd><tt>com.protomatterfoo</tt></dd>
093: * <dd><tt>com.protomatter.foo</tt></dd>
094: * <dd><tt>com.protomatter.jdbc.pool</tt></dd>
095: * </dl><P>
096: *
097: * This class is useful in debugging:<P>
098: *
099: * <UL><TABLE BORDER=1 CELLPADDING=4 CELLSPACING=0>
100: * <TR><TD>
101: * <PRE><B>
102: *
103: * import com.protomatter.util.Debug;
104: * import com.protomatter.util.StackTraceUtil;
105: *
106: * import com.protomatter.syslog.Channel;
107: *
108: * ...
109: *
110: * private final static Debug debug = Debug.forPackage(ThisClass.class);
111: *
112: * private final static Channel channel = Channel.forPackage(ThisClass.class);
113: *
114: * ...
115: *
116: * // three levels... trace, debug and info
117: * if (debug.trace())
118: * channel.debug(this, "Really detailed tracing statements");
119: *
120: * if (debug.debug())
121: * channel.debug(this, "Debugging statements: I'm here -> " + StackTraceUtil.whereAm());
122: *
123: * if (debug.info())
124: * channel.info(this, "Information messages");
125: *
126: * </B></PRE>
127: * </TD></TR></TABLE></UL><P>
128: *
129: * This can prevent expensive operations involved in debugging
130: * to be quickly bypassed in production environments
131: * by changing a configuration file instead of re-compiling.<P>
132: *
133: * All the work is done in the constructor, which calls
134: * the <tt><a href="#match(java.lang.String)">match()</a></tt> method.
135: * Calling the
136: * <tt><a href="#trace()">trace()</a></tt>,
137: * <tt><a href="#debug()">debug()</a></tt> or
138: * <tt><a href="#info()">info()</a></tt>
139: * methods takes almost no time (return values are cached).<P>
140: *
141: * I've tested this operation on a 650MHz PIII Coppermine Sony Vaio
142: * laptop running RedHat Linux 7.2, kernel 2.4.9,
143: * I saw these results for timing "<tt>Debug.forName(name)</tt>":<P>
144: *
145: * <ul><table border=1 cellpadding=4 cellspacing=0>
146: *
147: * <tr>
148: * <th>Virtual Machine</th>
149: * <th>Best Case</th>
150: * <th>Middle Case</th>
151: * <th>Worst Case</th>
152: * </tr>
153: *
154: * <tr>
155: * <td>Classic VM<BR>
156: * (build JDK-1.2.2_012, green threads, nojit)</td>
157: * <td valign=top>0.018627ms</td>
158: * <td valign=top>0.076707ms</td>
159: * <td valign=top>0.625373ms</td>
160: * </tr>
161: *
162: * <tr>
163: * <td>Java(TM) 2 Runtime Environment,<BR>
164: * Standard Edition (build 1.3.1_03-b03)</td>
165: * <td valign=top>0.001347ms</td>
166: * <td valign=top>0.008267ms</td>
167: * <td valign=top>0.08128ms</td>
168: * </tr>
169: *
170: * <tr>
171: * <td>Java HotSpot(TM) Client VM<BR>
172: * (build 1.4.0-b92, mixed mode)</td>
173: * <td valign=top>0.00184ms</td>
174: * <td valign=top>0.008067ms</td>
175: * <td valign=top>0.07928ms</td>
176: * </tr>
177: *
178: * <tr>
179: * <td>JRockit Virtual Machine<BR>
180: * (build 3.1.4-dax.appeal.se-20020319-1000)<BR>
181: * Native Threads, Generational Concurrent Garbage Collector</td>
182: * <td valign=top>0.00092ms</td>
183: * <td valign=top>0.006453ms</td>
184: * <td valign=top>0.065147ms</td>
185: * </tr>
186: *
187: * <tr>
188: * <td>Classic VM<BR>
189: * (build 1.3.1, J2RE 1.3.1 IBM build cxia32131-20020410<BR>
190: * (JIT enabled: jitc))</td>
191: * <td valign=top>0.00204ms</td>
192: * <td valign=top>0.00488ms</td>
193: * <td valign=top>0.039253ms</td>
194: * </tr>
195: *
196: * </table></ul><P>
197: *
198: * "Best Case" above is a direct match, where the name
199: * being matched is the same as one of the patterns.
200: * "Middle Case" is a search only one or two checks
201: * away from success, like matching "<tt>foo.bar.baz</tt>"
202: * against "<tt>foo.bar.*</tt>". "Worst Case" is a match
203: * that's 13 checks away. All cases had around 125 patterns
204: * in the search group. Your mileage may vary.<P>
205: *
206: * If you call <tt>setScan(false)</tt>, then all matches
207: * will be "Best Case" but only exact matches will
208: * return <tt>true</tt>.<P>
209: *
210: * If <tt>disable()</tt> has been called, this method
211: * will return <tt>false</tt> immediately. If there
212: * are no patterns set, it has the same effect.<P>
213: *
214: * @see com.protomatter.syslog.Channel
215: * @see com.protomatter.syslog.Syslog
216: * @see com.protomatter.util.StackTraceUtil
217: * @see com.protomatter.syslog.xml.SyslogXML#configure(org.jdom.Element)
218: */
219: public class Debug implements java.io.Serializable {
220: /**
221: * The set of patterns and/or names to match with for the "trace" severity.
222: */
223: private static Set tracePatternSet = new HashSet();
224:
225: /**
226: * The set of patterns and/or names to match with for the "debug" severity.
227: */
228: private static Set debugPatternSet = new HashSet();
229:
230: /**
231: * The set of patterns and/or names to match with for the "info" severity.
232: */
233: private static Set infoPatternSet = new HashSet();
234:
235: /**
236: * The "." character.
237: */
238: private final static char DOT = '.';
239:
240: /**
241: * The String ".*".
242: */
243: private final static String DOT_STAR = ".*";
244:
245: /**
246: * Disables everything. <tt>true</tt> = disabled,
247: * <tt>false</tt> = enabled (default).
248: */
249: private static boolean allOff = false;
250:
251: /**
252: * Scan "up" the naming hierarchy for
253: * matches. <tt>true</tt> = yes,
254: * <tt>false</tt> = no (default).
255: */
256: private static boolean scan = false;
257:
258: /**
259: * Is this instance "on" for "trace"?.
260: */
261: private boolean traceOn = false;
262:
263: /**
264: * Is this instance "on" for "debug"?.
265: */
266: private boolean debugOn = false;
267:
268: /**
269: * Is this instance "on" for "info"?.
270: */
271: private boolean infoOn = false;
272:
273: /**
274: * The name of this instance.
275: */
276: private String name = null;
277:
278: /**
279: * The last time configuration info was updated.
280: */
281: private static long configLastUpdateTime = 0;
282:
283: /**
284: * The last time this instance was configured. This
285: * is used to determine if we should call init() again
286: * before returning from the debug() info() or trace()
287: * methods.
288: */
289: private long lastUpdateTime = 0;
290:
291: /**
292: * The name of the "trace" severity, for the <tt>match()</tt> method.
293: */
294: private static final int TRACE = 0;
295:
296: /**
297: * The name of the "debug" severity, for the <tt>match()</tt> method.
298: */
299: private static final int DEBUG = 1;
300:
301: /**
302: * The name of the "info" severity, for the <tt>match()</tt> method.
303: */
304: private static final int INFO = 2;
305:
306: /**
307: * Protected constructor. Calls <tt>init()</tt>.
308: */
309: private Debug(String name) {
310: super ();
311:
312: this .name = name;
313: init();
314: }
315:
316: /**
317: * Initialize this <tt>Debug</tt> instance. Each time
318: * this method is called, the name of this instance
319: * is matched against the various patterns for
320: * each severity level. This method is called
321: * by the constructor.
322: */
323: public final void init() {
324: this .traceOn = match(name, TRACE);
325: this .debugOn = match(name, DEBUG);
326: this .infoOn = match(name, INFO);
327: this .lastUpdateTime = System.currentTimeMillis();
328: }
329:
330: /**
331: * Get a debug object with name of the
332: * given class.
333: * Pass in the class <tt>com.protomatter.syslog.Channel</tt>
334: * would use the name "<tt>com.protomatter.syslog.Channel</tt>".
335: * A new instance of this class
336: * is created (and initialized) each time this method is called.
337: */
338: public final static Debug forClass(Class someClass) {
339: return new Debug(someClass.getName());
340: }
341:
342: /**
343: * Get a debug object with name of the
344: * given class's package.
345: * Pass in the class <tt>com.protomatter.syslog.Channel</tt>
346: * would use the name "<tt>com.protomatter.syslog</tt>".
347: * If the class has no package, the name of the
348: * class is used. A new instance of this class
349: * is created (and initialized) each time this method is called.
350: */
351: public final static Debug forPackage(Class someClass) {
352: String packageName = someClass.getName();
353: int pos = packageName.lastIndexOf(DOT);
354: if (pos < 0)
355: return new Debug(packageName);
356: return new Debug(packageName.substring(0, pos));
357: }
358:
359: /**
360: * Get a debug object with the given name. A new instance of this class
361: * is created (and initialized) each time this method is called.
362: */
363: public final static Debug forName(String name) {
364: return new Debug(name);
365: }
366:
367: /**
368: * Disable all checks. This effectively means
369: * that the <tt>on()</tt> and <tt>match()</tt> methods
370: * on all instances of this class will return
371: * <tt>false</tt> until the <tt>enable()</tt>
372: * method is called.
373: */
374: public final static void disable() {
375: allOff = true;
376: }
377:
378: /**
379: * Enable checks. This is the default state.
380: */
381: public final static void enable() {
382: allOff = false;
383: }
384:
385: /**
386: * Determine if checks are enabled or disabled.
387: */
388: public final static boolean isEnabled() {
389: return (!allOff);
390: }
391:
392: /**
393: * Does our name match anything in the list of names and patterns.
394: * This value is cached when this object is instantiated, but is
395: * updated if the configuration is changed after this instance
396: * is created.
397: */
398: public final boolean trace() {
399: if ((this .lastUpdateTime < Debug.configLastUpdateTime)
400: && (!allOff))
401: init();
402: return (allOff) ? false : this .traceOn;
403: }
404:
405: /**
406: * Does our name match anything in the list of names and patterns.
407: * This value is cached when this object is instantiated, but is
408: * updated if the configuration is changed after this instance
409: * is created.
410: */
411: public final boolean debug() {
412: if ((this .lastUpdateTime < Debug.configLastUpdateTime)
413: && (!allOff))
414: init();
415: return (allOff) ? false : this .debugOn;
416: }
417:
418: /**
419: * Does our name match anything in the list of names and patterns.
420: * This value is cached when this object is instantiated, but is
421: * updated if the configuration is changed after this instance
422: * is created.
423: */
424: public final boolean info() {
425: if ((this .lastUpdateTime < Debug.configLastUpdateTime)
426: && (!allOff))
427: init();
428: return (allOff) ? false : this .infoOn;
429: }
430:
431: /**
432: * Does the given name match anything in the list of names and patterns.<P>
433: *
434: * @see #disable()
435: * @see #enable()
436: * @see #setScan(boolean)
437: */
438: private final static boolean match(String name, int severity) {
439: if (name == null)
440: return false;
441:
442: // is everything turned off?
443: if (allOff)
444: return false;
445:
446: Set patternSet = null;
447: if (TRACE == severity)
448: patternSet = tracePatternSet;
449: else if (DEBUG == severity)
450: patternSet = debugPatternSet;
451: else if (INFO == severity)
452: patternSet = infoPatternSet;
453:
454: // no patterns so no matches
455: if (patternSet.isEmpty())
456: return false;
457:
458: // scan up the namespace for matches?
459: if (scan) {
460: if (patternSet.contains(name))
461: return true;
462:
463: int index = 1;
464: while (index > 0) {
465: if (patternSet.contains(name + DOT_STAR)) {
466: return true;
467: }
468: index = name.lastIndexOf(DOT);
469: if (index < 0)
470: return false;
471: name = name.substring(0, index);
472: }
473: return false;
474: }
475:
476: // find an exact match
477: return patternSet.contains(name);
478: }
479:
480: /**
481: * Set if we scan for matches. Default is false.
482: * If set, we scan "up" the name hierarchy,
483: * which can be a little time consuming. It
484: * may make sense to enable, and then
485: * only allow for exact matches on names.
486: */
487: public final static void setScan(boolean setting) {
488: scan = setting;
489: configLastUpdateTime = System.currentTimeMillis();
490: }
491:
492: /**
493: * Should we scan for matches? Default is false.
494: * If set, we scan "up" the name hierarchy,
495: * which can be a little time consuming. It
496: * may make sense to enable, and then
497: * only allow for exact matches on names.
498: */
499: public final static boolean getScan() {
500: return scan;
501: }
502:
503: /**
504: * Get the names and/or patterns to match at the "trace" severity.
505: */
506: public final static Iterator getTraceNames() {
507: return tracePatternSet.iterator();
508: }
509:
510: /**
511: * Get the names and/or patterns to match at the "debug" severity.
512: */
513: public final static Iterator getDebugNames() {
514: return debugPatternSet.iterator();
515: }
516:
517: /**
518: * Get the names and/or patterns to match at the "info" severity.
519: */
520: public final static Iterator getInfoNames() {
521: return infoPatternSet.iterator();
522: }
523:
524: /**
525: * Add a name or pattern to the matching set at the "trace" level.
526: */
527: public final static void addTraceName(String name) {
528: tracePatternSet.add(name);
529: configLastUpdateTime = System.currentTimeMillis();
530: }
531:
532: /**
533: * Add a name or pattern to the matching set at the "debug" level.
534: */
535: public final static void addDebugName(String name) {
536: debugPatternSet.add(name);
537: configLastUpdateTime = System.currentTimeMillis();
538: }
539:
540: /**
541: * Add a name or pattern to the matching set at the "info" level.
542: */
543: public final static void addInfoName(String name) {
544: infoPatternSet.add(name);
545: configLastUpdateTime = System.currentTimeMillis();
546: }
547:
548: /**
549: * Clear the sets of patterns and names.
550: */
551: public final static void clear() {
552: tracePatternSet = new HashSet();
553: debugPatternSet = new HashSet();
554: infoPatternSet = new HashSet();
555: configLastUpdateTime = System.currentTimeMillis();
556: }
557:
558: /**
559: * Get the name this instance uses.
560: */
561: public final String getName() {
562: return this .name;
563: }
564:
565: /**
566: * Return a human-readable representation of this object.
567: */
568: public final String toString() {
569: if (this .lastUpdateTime < Debug.configLastUpdateTime)
570: init();
571: StringBuffer b = new StringBuffer();
572: b.append("Debug[");
573: b.append(name);
574: b.append(" ");
575: if (!traceOn && !debugOn && !infoOn) {
576: b.append("OFF");
577: } else if (traceOn && debugOn && infoOn) {
578: b.append("ON");
579: } else {
580: b.append("trace");
581: b.append((this .traceOn ? "=ON " : "=OFF "));
582:
583: b.append("debug");
584: b.append((this .debugOn ? "=ON " : "=OFF "));
585:
586: b.append("info");
587: b.append((this .infoOn ? "=ON" : "=OFF"));
588: }
589:
590: b.append("]");
591:
592: return b.toString();
593: }
594:
595: /**
596: * Performance testing rig.
597: */
598: public static void main(String args[]) {
599: try {
600: if (args.length != 4) {
601: System.out
602: .println("Usage: java com.protomatter.util.Debug \\");
603: System.out
604: .println(" scan? numCalls patterns.txt tests.txt");
605: System.out.println("");
606: System.out
607: .println(" scan: true/false: passed to Debug.setScan()");
608: System.out
609: .println(" numCalls: number of Debug.forName() calls to make");
610: System.out
611: .println(" patterns.txt: a text file containing strings");
612: System.out
613: .println(" to pass to Debug.addInfoName()");
614: System.out
615: .println(" tests.txt: a text file containing strings");
616: System.out
617: .println(" to pass to Debug.forName() repeatedly.");
618: System.out.println("");
619: System.exit(0);
620: }
621:
622: Debug.setScan("true".equalsIgnoreCase(args[0]));
623: int numCalls = Integer.parseInt(args[1]);
624:
625: BufferedReader names = new BufferedReader(new FileReader(
626: new File(args[2])));
627: BufferedReader tests = new BufferedReader(new FileReader(
628: new File(args[3])));
629:
630: System.out
631: .println("Debug.forName() and Debug.info() performance test");
632: System.out.println("");
633:
634: Debug.setScan(true);
635: System.out.println("JVM Information:");
636: System.out.println(" VM Name: "
637: + System.getProperty("java.vm.name"));
638: System.out.println(" VM Version: "
639: + System.getProperty("java.vm.version"));
640: System.out.println(" Runtime name: "
641: + System.getProperty("java.runtime.name"));
642: System.out.println(" Runtime version: "
643: + System.getProperty("java.runtime.version"));
644: System.out.println("");
645: System.out.println("OS Information:");
646: System.out.println(" " + System.getProperty("os.name")
647: + " " + System.getProperty("os.version"));
648: System.out.println("");
649: System.out.println("Scan: " + Debug.getScan());
650:
651: System.out.println("Adding names:");
652: String line = null;
653: int num = 0;
654: while ((line = names.readLine()) != null) {
655: //System.out.println(" " + line.trim());
656: Debug.addInfoName(line.trim());
657: ++num;
658: }
659: System.out.println(" added " + num + " names/patterns");
660:
661: System.out.println("");
662: System.out.println("Testing names:");
663: DecimalFormat tf = new DecimalFormat("###,###,###,###");
664: DecimalFormat format = new DecimalFormat("###.#######");
665: Debug debug = null;
666: Debug debug1 = null;
667: while ((line = tests.readLine()) != null) {
668: line = line.trim();
669: System.out.println(" instance = Debug.forName(\""
670: + line + "\");");
671: long time = System.currentTimeMillis();
672: for (int i = 0; i < numCalls; i++) {
673: debug = Debug.forName(line);
674: }
675: if (debug1 == null)
676: debug1 = debug;
677: time = System.currentTimeMillis() - time;
678: System.out.println(" " + debug);
679: System.out.println(" " + tf.format(numCalls)
680: + " calls in " + tf.format(time) + "ms");
681: double average = ((double) time / (double) numCalls);
682: double persecond = ((double) 1 / average) * 1000;
683: System.out.println(" Average = "
684: + format.format(average) + "ms");
685: System.out.println(" Per-second = "
686: + tf.format(persecond));
687: System.out.println("");
688: }
689: System.out.println("");
690: System.out.println("");
691: numCalls = numCalls * 1000;
692: System.out.println(tf.format(numCalls)
693: + " instance.info() calls");
694: long time = System.currentTimeMillis();
695: boolean on = false;
696: for (int i = 0; i < numCalls; i++) {
697: on = debug1.info();
698: }
699: time = System.currentTimeMillis() - time;
700: System.out.println(" " + debug1);
701: System.out.println(" " + tf.format(numCalls)
702: + " calls in " + tf.format(time) + "ms");
703: double average = ((double) time / (double) numCalls);
704: double persecond = ((double) 1 / average) * 1000;
705: System.out.println(" Average = "
706: + format.format(average) + "ms");
707: System.out.println(" Per-second = "
708: + tf.format(persecond));
709: System.out.println("");
710: } catch (Exception x) {
711: x.printStackTrace();
712: }
713: }
714: }
|