001: package org.incava.qualog;
002:
003: import java.io.*;
004: import java.util.*;
005:
006: /**
007: * <p>Writes the logging output, applying filters and decorations. The
008: * <code>Qualog</code> class offers a much cleaner and more thorough interface
009: * than this class.</p>
010: *
011: * @see org.incava.qualog.Qualog
012: */
013: public class QlWriter {
014: public static final int NO_OUTPUT = 0;
015:
016: public static final int QUIET = 1;
017:
018: public static final int VERBOSE = 2;
019:
020: public int fileWidth = 25;
021:
022: public boolean columns = true;
023:
024: public int lineWidth = 5;
025:
026: public int functionWidth = 25;
027:
028: public int classWidth = 35;
029:
030: public boolean showFiles = true;
031:
032: public boolean showClasses = true;
033:
034: public PrintWriter out = new PrintWriter(System.out, true);
035:
036: public List packagesSkipped = new ArrayList(Arrays
037: .asList(new String[] { "org.incava.qualog", }));
038:
039: public List classesSkipped = new ArrayList(Arrays
040: .asList(new String[] { "tr.Ace" }));
041:
042: public List methodsSkipped = new ArrayList(Arrays
043: .asList(new String[] {}));
044:
045: private int outputType = NO_OUTPUT;
046:
047: private Map packageColors = new HashMap();
048:
049: private Map classColors = new HashMap();
050:
051: private Map methodColors = new HashMap();
052:
053: private Map fileColors = new HashMap();
054:
055: private Map levelColors = new HashMap();
056:
057: private StackTraceElement prevStackElement = null;
058:
059: private Thread prevThread = null;
060:
061: private String prevDisplayedClass = null;
062:
063: private String prevDisplayedMethod = null;
064:
065: private QlLevel level = Qualog.LEVEL9;
066:
067: private List filters = new ArrayList();
068:
069: private boolean useColor = true;
070:
071: /**
072: * Adds a filter to be applied for output.
073: *
074: * @see org.incava.qualog.QlFilter
075: */
076: public void addFilter(QlFilter filter) {
077: filters.add(filter);
078: }
079:
080: public void setDisabled(Class cls) {
081: addFilter(new QlClassFilter(cls, null));
082: }
083:
084: public void setClassColor(String className, ANSIColor color) {
085: classColors.put(className, color);
086: }
087:
088: public void setPackageColor(String pkg, ANSIColor color) {
089: }
090:
091: public void setMethodColor(String className, String methodName,
092: ANSIColor color) {
093: methodColors.put(className + "#" + methodName, color);
094: }
095:
096: public void clearClassColor(String className) {
097: classColors.remove(className);
098: }
099:
100: public void setFileColor(String fileName, ANSIColor color) {
101: fileColors.put(fileName, color);
102: }
103:
104: public void set(boolean columns, int fileWidth, int lineWidth,
105: int classWidth, int functionWidth) {
106: this .columns = columns;
107: this .fileWidth = fileWidth;
108: this .lineWidth = lineWidth;
109: this .classWidth = classWidth;
110: this .functionWidth = functionWidth;
111: }
112:
113: /**
114: * Sets the output type and level. Either verbose or quiet can be enabled.
115: */
116: public void setOutput(int type, QlLevel level) {
117: this .outputType = type;
118: this .level = level;
119: }
120:
121: public boolean verbose() {
122: return outputType == VERBOSE;
123: }
124:
125: public void setColumns(boolean cols) {
126: columns = cols;
127: }
128:
129: public void addClassSkipped(Class cls) {
130: addClassSkipped(cls.getName());
131: }
132:
133: public void addClassSkipped(String clsName) {
134: classesSkipped.add(clsName);
135: }
136:
137: /**
138: * Resets parameters to their defaults.
139: */
140: public void clear() {
141: packageColors = new HashMap();
142: classColors = new HashMap();
143: methodColors = new HashMap();
144: fileColors = new HashMap();
145: levelColors = new HashMap();
146: prevStackElement = null;
147: prevThread = null;
148: prevDisplayedClass = null;
149: prevDisplayedMethod = null;
150: level = Qualog.LEVEL9;
151: filters = new ArrayList();
152: }
153:
154: public void reset() {
155: prevThread = Thread.currentThread();
156: prevStackElement = null;
157: }
158:
159: public boolean stack(QlLevel level, ANSIColor[] msgColors,
160: String name, Object obj, ANSIColor fileColor,
161: ANSIColor classColor, ANSIColor methodColor, int numFrames) {
162: if (isLoggable(level)) {
163: String nm = name == null ? "" : name;
164:
165: if (obj == null) {
166: String msg = nm + ": " + "null";
167: return stack(level, msgColors, msg, fileColor,
168: classColor, methodColor, numFrames);
169: } else if (obj instanceof Collection) {
170: Collection c = (Collection) obj;
171: return QlCollection.stack(level, msgColors, nm, c,
172: fileColor, classColor, methodColor, numFrames);
173: } else if (obj instanceof Iterator) {
174: Iterator it = (Iterator) obj;
175: return QlIterator.stack(level, msgColors, nm, it,
176: fileColor, classColor, methodColor, numFrames);
177: } else if (obj instanceof Enumeration) {
178: Enumeration en = (Enumeration) obj;
179: return QlEnumeration.stack(level, msgColors, nm, en,
180: fileColor, classColor, methodColor, numFrames);
181: } else if (obj instanceof Object[]) {
182: Object[] ary = (Object[]) obj;
183: return QlObjectArray.stack(level, msgColors, nm, ary,
184: fileColor, classColor, methodColor, numFrames);
185: } else if (obj instanceof Map) {
186: Map m = (Map) obj;
187: return QlMap.stack(level, msgColors, nm, m, fileColor,
188: classColor, methodColor, numFrames);
189: } else if (obj.getClass().isArray()) {
190: String[] strs = null;
191: if (obj instanceof byte[]) {
192: byte[] ary = (byte[]) obj;
193: strs = new String[ary.length];
194: for (int ai = 0; ai < ary.length; ++ai) {
195: strs[ai] = String.valueOf(ary[ai]);
196: }
197: } else if (obj instanceof char[]) {
198: char[] ary = (char[]) obj;
199: strs = new String[ary.length];
200: for (int ai = 0; ai < ary.length; ++ai) {
201: strs[ai] = String.valueOf(ary[ai]);
202: }
203: } else if (obj instanceof double[]) {
204: double[] ary = (double[]) obj;
205: strs = new String[ary.length];
206: for (int ai = 0; ai < ary.length; ++ai) {
207: strs[ai] = String.valueOf(ary[ai]);
208: }
209: } else if (obj instanceof float[]) {
210: float[] ary = (float[]) obj;
211: strs = new String[ary.length];
212: for (int ai = 0; ai < ary.length; ++ai) {
213: strs[ai] = String.valueOf(ary[ai]);
214: }
215: } else if (obj instanceof int[]) {
216: int[] ary = (int[]) obj;
217: strs = new String[ary.length];
218: for (int ai = 0; ai < ary.length; ++ai) {
219: strs[ai] = String.valueOf(ary[ai]);
220: }
221: } else if (obj instanceof long[]) {
222: long[] ary = (long[]) obj;
223: strs = new String[ary.length];
224: for (int ai = 0; ai < ary.length; ++ai) {
225: strs[ai] = String.valueOf(ary[ai]);
226: }
227: }
228:
229: return QlObjectArray.stack(level, msgColors, nm, strs,
230: fileColor, classColor, methodColor, numFrames);
231: } else {
232: String msg = nm + ": " + objectToString(obj);
233: return stack(level, msgColors, msg, fileColor,
234: classColor, methodColor, numFrames);
235: }
236: } else {
237: return true;
238: }
239: }
240:
241: public boolean isSkipped(StackTraceElement ste) {
242: String className = ste.getClassName();
243: if (classesSkipped.contains(className)
244: || methodsSkipped.contains(ste.getMethodName())) {
245: return true;
246: } else {
247: Iterator pit = packagesSkipped.iterator();
248: while (pit.hasNext()) {
249: String pkgName = (String) pit.next();
250: if (className.startsWith(pkgName)) {
251: return true;
252: }
253: }
254: }
255: return false;
256: }
257:
258: public boolean isLoggable(QlLevel level) {
259: return outputType != NO_OUTPUT && this .level != null
260: && this .level.compareTo(level) >= 0;
261: }
262:
263: /**
264: * Returns the index in the stack where logging (stacks) should be
265: * displayed. Returns -1 if the end of the stack is reached and no logging
266: * should occur.
267: */
268: public synchronized int findStackStart(StackTraceElement[] stack) {
269: for (int fi = 0; fi < stack.length; ++fi) {
270: if (!isSkipped(stack[fi])) {
271: return fi;
272: }
273: }
274:
275: return stack.length;
276: }
277:
278: public synchronized boolean stack(QlLevel lvl,
279: ANSIColor[] msgColor, String msg, ANSIColor fileColor,
280: ANSIColor classColor, ANSIColor methodColor, int numFrames) {
281: if (isLoggable(lvl)) {
282: if (outputType == QUIET) {
283: numFrames = 1;
284: }
285:
286: StackTraceElement[] stack = getStack(numFrames);
287:
288: // when we're switching threads, reset to a null state.
289: if (!Thread.currentThread().equals(prevThread)) {
290: reset();
291: }
292:
293: int fi = findStackStart(stack);
294:
295: for (int framesShown = 0; fi < stack.length
296: && framesShown < numFrames; ++fi, ++framesShown) {
297: StackTraceElement stackElement = stack[fi];
298: String className = stackElement.getClassName();
299: String methodName = stackElement.getMethodName();
300: boolean filtered = false;
301:
302: if (framesShown == 0) {
303: Iterator fit = filters.iterator();
304: while (fit.hasNext()) {
305: QlFilter filter = (QlFilter) fit.next();
306: int lineNum = stackElement.getLineNumber();
307: String fileName = stackElement.getFileName();
308:
309: if (filter.isMatch(fileName, lineNum,
310: className, methodName)) {
311: QlLevel flevel = filter.getLevel();
312: filtered = filtered || flevel != null
313: && level.compareTo(flevel) < 0;
314: }
315: }
316: }
317:
318: if (filtered) {
319: return true;
320: }
321:
322: StringBuffer buf = new StringBuffer();
323:
324: if (outputType == VERBOSE) {
325: if (showFiles) {
326: outputFileName(buf, fileColor, stackElement);
327: }
328: if (showClasses) {
329: outputClassAndMethod(buf, classColor,
330: methodColor, stackElement);
331: }
332: }
333: outputMessage(buf, framesShown, msgColor, msg,
334: stackElement);
335:
336: out.println(buf.toString());
337:
338: // System.err.println("buf: " + buf.toString());
339:
340: prevStackElement = stackElement;
341: }
342: }
343: return true;
344: }
345:
346: void setUseColor(boolean useColor) {
347: this .useColor = useColor;
348: }
349:
350: protected void outputFileName(StringBuffer buf,
351: ANSIColor fileColor, StackTraceElement stackElement) {
352: String fileName = stackElement.getFileName();
353:
354: buf.append("[");
355: if (fileName == null) {
356: fileName = "";
357: }
358:
359: if (prevStackElement != null
360: && prevStackElement.getFileName() != null
361: && prevStackElement.getFileName().equals(fileName)) {
362:
363: int width = columns ? Math
364: .min(fileWidth, fileName.length()) : fileName
365: .length();
366: fileName = repeat(width, ' ');
367: }
368:
369: String lnStr = stackElement.getLineNumber() >= 0 ? String
370: .valueOf(stackElement.getLineNumber()) : "";
371:
372: ANSIColor col = fileColor;
373: if (col == null) {
374: col = (ANSIColor) fileColors.get(fileName);
375: }
376:
377: if (columns) {
378: if (col == null) {
379: appendPadded(buf, fileName, fileWidth);
380: buf.append(' ');
381: buf.append(repeat(lineWidth - lnStr.length(), ' '))
382: .append(lnStr);
383: } else {
384: buf.append(col);
385: buf.append(fileName);
386: buf.append(Qualog.NONE);
387: repeat(buf, fileWidth - fileName.length(), ' ');
388: repeat(buf, 1 + lineWidth - lnStr.length(), ' ');
389: buf.append(col).append(lnStr).append(Qualog.NONE);
390: }
391: } else if (col == null) {
392: appendPadded(buf, fileName + ":" + lnStr, fileWidth);
393: } else {
394: buf.append(col);
395: buf.append(fileName);
396: buf.append(':');
397: buf.append(lnStr);
398: buf.append(Qualog.NONE);
399: repeat(buf, fileWidth - fileName.length() - 1
400: - lnStr.length(), ' ');
401: }
402:
403: buf.append("] ");
404: }
405:
406: protected void outputClassAndMethod(StringBuffer buf,
407: ANSIColor classColor, ANSIColor methodColor,
408: StackTraceElement stackElement) {
409: buf.append("{");
410:
411: String className = stackElement.getClassName();
412:
413: if (classColor == null) {
414: classColor = (ANSIColor) classColors.get(className);
415: }
416:
417: boolean sameClass = prevStackElement != null
418: && prevStackElement.getClassName().equals(className);
419: if (sameClass) {
420: className = repeat(prevDisplayedClass.length(), ' ');
421: classColor = null;
422: } else if (className != null
423: && (className.startsWith("org.") || className
424: .startsWith("com."))) {
425: className = "..."
426: + className
427: .substring(className.indexOf('.', 5) + 1);
428: }
429:
430: int totalWidth = classWidth + 1 + functionWidth;
431:
432: int classPadding = 0;
433: if (className.length() > classWidth) {
434: if (classWidth > 0) {
435: className = className.substring(0, classWidth - 1) + '-';
436: } else {
437: className = "";
438: }
439: } else {
440: classPadding = classWidth - className.length();
441: }
442:
443: if (classColor != null) {
444: buf.append(classColor);
445: }
446: buf.append(className);
447: if (classColor != null) {
448: buf.append(Qualog.NONE);
449: }
450:
451: if (columns) {
452: repeat(buf, classPadding, ' ');
453: }
454:
455: prevDisplayedClass = className;
456:
457: buf.append('#');
458:
459: String methodName = stackElement.getMethodName();
460:
461: if (methodColor == null) {
462: methodColor = (ANSIColor) methodColors.get(methodName);
463: }
464:
465: if (sameClass && prevStackElement != null
466: && prevStackElement.getMethodName().equals(methodName)) {
467: methodName = repeat(prevDisplayedMethod.length(), ' ');
468: methodColor = null;
469: }
470:
471: int methodPadding = 0;
472: if (methodName.length() > functionWidth) {
473: methodName = methodName.substring(0, functionWidth - 1) + '-';
474: } else {
475: methodPadding = functionWidth - methodName.length();
476: }
477:
478: if (methodColor != null) {
479: buf.append(methodColor);
480: }
481: buf.append(methodName);
482: if (methodColor != null) {
483: buf.append(Qualog.NONE);
484: }
485:
486: if (!columns) {
487: repeat(buf, classPadding, ' ');
488: }
489: repeat(buf, methodPadding, ' ');
490:
491: prevDisplayedMethod = methodName;
492:
493: buf.append("} ");
494: }
495:
496: protected void outputMessage(StringBuffer buf, int framesShown,
497: ANSIColor[] msgColor, String msg,
498: StackTraceElement stackElement) {
499: // remove ending EOLN
500: if (framesShown > 0) {
501: msg = "\"\"";
502: } else {
503: while (msg.length() > 0
504: && "\r\n".indexOf(msg.charAt(msg.length() - 1)) != -1) {
505: msg = msg.substring(0, msg.length() - 1);
506: }
507: if (useColor) {
508: boolean hasColor = false;
509: if (msgColor == null
510: || (msgColor.length > 0 && msgColor[0] == null)) {
511: ANSIColor col = null;
512: col = (ANSIColor) methodColors.get(stackElement
513: .getClassName()
514: + "#" + stackElement.getMethodName());
515: if (col == null) {
516: col = (ANSIColor) classColors.get(stackElement
517: .getClassName());
518: if (col == null) {
519: col = (ANSIColor) fileColors
520: .get(stackElement.getFileName());
521: }
522: }
523: if (col != null) {
524: msg = col + msg;
525: hasColor = true;
526: }
527: } else {
528: for (int i = 0; i < msgColor.length; ++i) {
529: if (msgColor[i] != null) {
530: msg = msgColor[i] + msg;
531: hasColor = true;
532: }
533: }
534: }
535:
536: if (hasColor) {
537: msg += Qualog.NONE;
538: }
539: }
540: }
541:
542: buf.append(msg);
543: }
544:
545: protected StackTraceElement[] getStack(int depth) {
546: return (new Exception("")).getStackTrace();
547: }
548:
549: protected String repeat(int len, char ch) {
550: StringBuffer buf = new StringBuffer();
551: for (int i = 0; i < len; ++i) {
552: buf.append(ch);
553: }
554: return buf.toString();
555: }
556:
557: protected StringBuffer repeat(StringBuffer buf, int len, char ch) {
558: for (int i = 0; i < len; ++i) {
559: buf.append(ch);
560: }
561: return buf;
562: }
563:
564: protected void appendPadded(StringBuffer buf, String str,
565: int maxSize) {
566: if (str.length() > maxSize) {
567: buf.append(str.substring(0, maxSize - 1)).append("-");
568: } else {
569: buf.append(str);
570: repeat(buf, maxSize - str.length(), ' ');
571: }
572: }
573:
574: protected String objectToString(Object obj) {
575: String str = null;
576: if (obj == null) {
577: str = "null";
578: } else {
579: Class[] undecorated = new Class[] { String.class,
580: Number.class, Character.class, Boolean.class };
581:
582: Class cls = obj.getClass();
583:
584: for (int ui = 0; ui < undecorated.length; ++ui) {
585: if (undecorated[ui].isAssignableFrom(cls)) {
586: str = obj.toString();
587: break;
588: }
589: }
590:
591: if (str == null) {
592: str = obj.toString() + " (" + obj.getClass() + ") #"
593: + Integer.toHexString(obj.hashCode());
594: }
595: }
596: return str;
597: }
598:
599: }
|