001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2007, Geotools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or (at your option) any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: */
016: package org.geotools.util.logging;
017:
018: import java.text.MessageFormat;
019: import java.util.MissingResourceException;
020: import java.util.ResourceBundle;
021: import java.util.logging.Filter;
022: import java.util.logging.Level;
023: import java.util.logging.Logger;
024: import java.util.logging.Handler;
025: import java.util.logging.LogRecord;
026: import java.util.regex.Pattern;
027:
028: /**
029: * An adapter that redirect all Java logging events to an other logging framework. This
030: * class redefines the {@link #severe(String) severe}, {@link #warning(String) warning},
031: * {@link #info(String) info}, {@link #config(String) config}, {@link #fine(String) fine},
032: * {@link #finer(String) finer} and {@link #finest(String) finest} methods as <em>abstract</em>
033: * ones. Subclasses should implement those methods in order to map Java logging levels to
034: * the backend logging framework.
035: * <p>
036: * All {@link #log(Level,String) log} methods are overriden in order to redirect to one of the
037: * above-cited methods. Note that this is the opposite approach than the Java logging framework
038: * one, which implemented everything on top of {@link Logger#log(LogRecord)}. This adapter is
039: * defined in terms of {@link #severe(String) severe} … {@link #finest(String) finest}
040: * methods instead because external frameworks like
041: * <a href="http://commons.apache.org/logging/">Commons-logging</a>
042: * don't work with {@link LogRecord}, and sometime provides nothing else than convenience methods
043: * equivalent to {@link #severe(String) severe} … {@link #finest(String) finest}.
044: * <p>
045: * <b>Restrictions</b><br>
046: * Because the configuration is expected to be fully controled by the external logging
047: * framework, every configuration methods inherited from {@link Logger} are disabled:
048: * <p>
049: * <ul>
050: * <li>{@link #addHandler}
051: * since the handling is performed by the external framework.</li>
052: *
053: * <li>{@link #setUseParentHandlers}
054: * since this adapter never delegates to the parent handlers. This is consistent with the
055: * previous item and avoid mixing loggings from the external framework with Java loggings.</li>
056: *
057: * <li>{@link #setParent}
058: * since this adapter should not inherits any configuration from a parent logger using the
059: * Java logging framework.</li>
060: *
061: * <li>{@link #setFilter}
062: * for keeping this {@code LoggerAdapter} simple.</li>
063: * </ul>
064: * <p>
065: * Since {@code LoggerAdapter}s do not hold any configuration by themself, it is not strictly
066: * necessary to {@linkplain java.util.logging.LogManager#addLogger add them to the log manager}.
067: * The adapters can be created, garbage-collected and recreated again while preserving their
068: * behavior since their configuration is entirely contained in the external logging framework.
069: * <p>
070: * <b>Localization</b><br>
071: * This logger is always created without resource bundles. Localizations must be performed through
072: * explicit calls to {@code logrb} or {@link #log(LogRecord)} methods. This is suffisient for
073: * GeoTools needs, which performs all localizations through the later. Note that those methods
074: * will be slower in this {@code LoggerAdapter} than the default {@link Logger} because this
075: * adapter localizes and formats records immediately instead of letting the {@linkplain Handler}
076: * performs this work only if needed.
077: * <p>
078: * <b>Logging levels</b><br>
079: * If a log record {@linkplain Level level} is not one of the predefined ones, then this class
080: * maps to the first level below the specified one. For example if a log record has some level
081: * between {@link Level#FINE FINE} and {@link Level#FINER FINER}, then the {@link #finer finer}
082: * method will be invoked. See {@link #isLoggable} for implementation tips taking advantage of
083: * this rule.
084: *
085: * @since 2.4
086: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/metadata/src/main/java/org/geotools/util/logging/LoggerAdapter.java $
087: * @version $Id: LoggerAdapter.java 28037 2007-11-24 14:19:24Z aaime $
088: * @author Martin Desruisseaux
089: *
090: * @see Logging
091: */
092: public abstract class LoggerAdapter extends Logger {
093: /**
094: * The pattern to use for detecting {@link MessageFormat}.
095: */
096: private static final Pattern MESSAGE_FORMAT = Pattern
097: .compile("\\{\\d+\\}");
098:
099: /**
100: * Creates a new logger.
101: *
102: * @param name The logger name.
103: */
104: protected LoggerAdapter(final String name) {
105: super (name, null);
106: /*
107: * Must invokes the super-class method, because LoggerAdapter overrides it as a no-op.
108: */
109: super .setUseParentHandlers(false);
110: /*
111: * Sets the level to ALL as a matter of principle, but we will never check the level
112: * anyway (we will let the external logging framework do its own check). Note that we
113: * must invoke the method in the super-class because we want to set the java logging
114: * level, not the external framework level.
115: */
116: super .setLevel(Level.ALL);
117: }
118:
119: /**
120: * Sets the level for this logger. Subclasses must redirect the call to the external
121: * logging framework, or do nothing if the level can not be changed programmatically.
122: */
123: public abstract void setLevel(Level level);
124:
125: /**
126: * Returns the level for this logger. Subclasses shall get this level from the
127: * external logging framework.
128: */
129: public abstract Level getLevel();
130:
131: /**
132: * Returns the level for {@link #entering}, {@link #exiting} and {@link #throwing} methods.
133: * The default implementation returns {@link Level#FINER}, which is consistent with the
134: * value used in the Java logging framework. Subclasses should override this method if
135: * a different debug level is wanted.
136: */
137: protected Level getDebugLevel() {
138: return Level.FINER;
139: }
140:
141: /**
142: * Returns {@code true} if the specified level is loggable.
143: * <p>
144: * <b>Implementation tip</b><br>
145: * Given that {@link Level#intValue} for all predefined levels are documented in the {@link Level}
146: * specification and are multiple of 100, given that integer divisions are rounded toward zero and
147: * given rule documented in this class javadoc, then logging levels can be efficiently mapped to
148: * predefined levels using {@code switch} statements as below. This statement has good chances to
149: * be compiled to the {@code tableswitch} bytecode rather than {@code lookupswitch} (see
150: * <a href="http://java.sun.com/docs/books/jvms/second_edition/html/Compiling.doc.html#14942">Compiling
151: * Switches</a> in <cite>The Java Virtual Machine Specification</cite>).
152: *
153: * <blockquote><pre>
154: * @SuppressWarnings("fallthrough")
155: * public boolean isLoggable(Level level) {
156: * final int n = level.intValue();
157: * switch (n / 100) {
158: * default: {
159: * // MAX_VALUE is a special value for Level.OFF. Otherwise and
160: * // if positive, fallthrough since we are greater than SEVERE.
161: * switch (n) {
162: * case Integer.MIN_VALUE: return true; // Level.ALL
163: * case Integer.MAX_VALUE: return false; // Level.OFF
164: * default: if (n < 0) return false;
165: * }
166: * }
167: * case 10: return isSevereEnabled();
168: * case 9: return isWarningEnabled();
169: * case 8: return isInfoEnabled();
170: * case 7: return isConfigEnabled();
171: * case 6: // fallthrough
172: * case 5: return isFineEnabled();
173: * case 4: return isFinerEnabled();
174: * case 3: return isFinestEnabled();
175: * case 2: // fallthrough
176: * case 1: // fallthrough
177: * case 0: return false;
178: * }
179: * }
180: * </pre></blockquote>
181: */
182: public abstract boolean isLoggable(Level level);
183:
184: /**
185: * Logs a {@link Level#SEVERE SEVERE} message.
186: */
187: public abstract void severe(String message);
188:
189: /**
190: * Logs a {@link Level#WARNING WARNING} message.
191: */
192: public abstract void warning(String message);
193:
194: /**
195: * Logs an {@link Level#INFO INFO} message.
196: */
197: public abstract void info(String message);
198:
199: /**
200: * Logs an {@link Level#CONFIG CONFIG} message.
201: */
202: public abstract void config(String message);
203:
204: /**
205: * Logs a {@link Level#FINE FINE} message.
206: */
207: public abstract void fine(String message);
208:
209: /**
210: * Logs a {@link Level#FINER FINER} message.
211: */
212: public abstract void finer(String message);
213:
214: /**
215: * Logs a {@link Level#FINEST FINEST} message.
216: */
217: public abstract void finest(String message);
218:
219: /**
220: * Logs a method entry to the {@linkplain #getDebugLevel debug level}. Compared to the
221: * default {@link Logger}, this implementation bypass the level check in order to let
222: * the backing logging framework do its own check.
223: */
224: public void entering(final String sourceClass,
225: final String sourceMethod) {
226: logp(getDebugLevel(), sourceClass, sourceMethod, "ENTRY");
227: }
228:
229: /**
230: * Logs a method entry to the {@linkplain #getDebugLevel debug level} with one parameter.
231: * Compared to the default {@link Logger}, this implementation bypass the level check in
232: * order to let the backing logging framework do its own check.
233: */
234: public void entering(String sourceClass, String sourceMethod,
235: Object param) {
236: logp(getDebugLevel(), sourceClass, sourceMethod, "ENTRY {0}",
237: param);
238: }
239:
240: /**
241: * Logs a method entry to the {@linkplain #getDebugLevel debug level} with many parameters.
242: * Compared to the default {@link Logger}, this implementation bypass the level check in
243: * order to let the backing logging framework do its own check.
244: */
245: public void entering(final String sourceClass,
246: final String sourceMethod, final Object[] params) {
247: final String message;
248: if (params == null) {
249: message = "ENTRY";
250: } else
251: switch (params.length) {
252: case 0:
253: message = "ENTRY";
254: break;
255: case 1:
256: message = "ENTRY {0}";
257: break;
258: case 2:
259: message = "ENTRY {0} {1}";
260: break;
261: default: {
262: final StringBuffer builder = new StringBuffer("ENTRY");
263: for (int i = 0; i < params.length; i++) {
264: builder.append(" {").append(i).append('}');
265: }
266: message = builder.toString();
267: break;
268: }
269: }
270: logp(getDebugLevel(), sourceClass, sourceMethod, message,
271: params);
272: }
273:
274: /**
275: * Logs a method return to the {@linkplain #getDebugLevel debug level}. Compared to the
276: * default {@link Logger}, this implementation bypass the level check in order to let
277: * the backing logging framework do its own check.
278: */
279: public void exiting(final String sourceClass,
280: final String sourceMethod) {
281: logp(getDebugLevel(), sourceClass, sourceMethod, "RETURN");
282: }
283:
284: /**
285: * Logs a method return to the {@linkplain #getDebugLevel debug level}. Compared to the
286: * default {@link Logger}, this implementation bypass the level check in order to let
287: * the backing logging framework do its own check.
288: */
289: public void exiting(String sourceClass, String sourceMethod,
290: Object result) {
291: logp(getDebugLevel(), sourceClass, sourceMethod, "RETURN {0}",
292: result);
293: }
294:
295: /**
296: * Logs a method failure to the {@linkplain #getDebugLevel debug level}. Compared to the
297: * default {@link Logger}, this implementation bypass the level check in order to let
298: * the backing logging framework do its own check.
299: */
300: public void throwing(String sourceClass, String sourceMethod,
301: Throwable thrown) {
302: logp(getDebugLevel(), sourceClass, sourceMethod, "THROW",
303: thrown);
304: }
305:
306: /**
307: * Logs a record. The default implementation delegates to
308: * {@link #logrb(Level,String,String,String,String,Object[]) logrb}.
309: */
310: public void log(final LogRecord record) {
311: /*
312: * The filter should always be null since we overrode the 'setFilter' method as a no-op.
313: * But we check it anyway as matter of principle just in case some subclass overrides the
314: * 'getFilter()' method. This is the only method where we can do this check cheaply. Note
315: * that this is NOT the check for logging level; Filters are for user-specified criterions.
316: */
317: final Filter filter = getFilter();
318: if (filter != null && !filter.isLoggable(record)) {
319: return;
320: }
321: Level level = record.getLevel();
322: String sourceClass = record.getSourceClassName();
323: String sourceMethod = record.getSourceMethodName();
324: String bundleName = record.getResourceBundleName();
325: String message = record.getMessage();
326: Object[] params = record.getParameters();
327: Throwable thrown = record.getThrown();
328: ResourceBundle bundle = record.getResourceBundle();
329: boolean localized = false;
330: if (bundle != null)
331: try {
332: message = bundle.getString(message);
333: localized = true; // Sets only if the above succeed.
334: } catch (MissingResourceException e) {
335: // The default Formatter.messageFormat implementation ignores this exception
336: // and uses the bundle key as the message, so we mimic its behavior here.
337: }
338: final boolean useThrown = (thrown != null)
339: && (params == null || params.length == 0);
340: if (localized) {
341: // The message is already localized.
342: if (useThrown) {
343: logp(level, sourceClass, sourceMethod, message, thrown);
344: } else {
345: logp(level, sourceClass, sourceMethod, message, params);
346: }
347: } else {
348: // The message needs to be localized. The bundle was null but maybe bundleName is not.
349: // Futhermore subclass may have overriden the 'logrb' methods.
350: if (useThrown) {
351: logrb(level, sourceClass, sourceMethod, bundleName,
352: message, thrown);
353: } else {
354: logrb(level, sourceClass, sourceMethod, bundleName,
355: message, params);
356: }
357: }
358: }
359:
360: /**
361: * Logs a record at the specified level. The default implementation delegates to one of the
362: * {@link #severe(String) severe}, {@link #warning(String) warning}, {@link #info(String) info},
363: * {@link #config(String) config}, {@link #fine(String) fine}, {@link #finer(String) finer} or
364: * {@link #finest(String) finest} methods according the supplied level.
365: */
366: public void log(final Level level, final String message) {
367: final int n = level.intValue();
368: switch (n / 100) {
369: default: {
370: if (n < 0 || n == Integer.MAX_VALUE)
371: break;
372: // MAX_VALUE is a special value for Level.OFF. Otherwise and
373: // if positive, fallthrough since we are greater than SEVERE.
374: }
375: case 10:
376: severe(message);
377: break;
378: case 9:
379: warning(message);
380: break;
381: case 8:
382: info(message);
383: break;
384: case 7:
385: config(message);
386: break;
387: case 6:
388: case 5:
389: fine(message);
390: break;
391: case 4:
392: finer(message);
393: break;
394: case 3:
395: finest(message);
396: break;
397: case 2: /* Logging OFF */
398: case 1: /* Logging OFF */
399: case 0: /* Logging OFF */
400: break;
401: }
402: }
403:
404: /**
405: * Logs a record at the specified level. The default implementation discards the exception
406: * and delegates to <code>{@linkplain #log(Level,String) log}(level, message)</code>.
407: */
408: public void log(final Level level, final String message,
409: final Throwable thrown) {
410: log(level, message);
411: }
412:
413: /**
414: * Logs a record at the specified level. The defaut implementation delegates to
415: * <code>{@linkplain #log(Level,String,Object[]) log}(level, message, params)</code>
416: * where the {@code params} array is built from the {@code param} object.
417: */
418: public void log(final Level level, final String message,
419: final Object param) {
420: if (isLoggable(level))
421: log(level, message, asArray(param));
422: }
423:
424: /**
425: * Logs a record at the specified level.
426: * The defaut implementation formats the message immediately, then delegates to
427: * <code>{@linkplain #log(Level,String) log}(level, message)</code>.
428: */
429: public void log(final Level level, final String message,
430: final Object[] params) {
431: if (isLoggable(level))
432: log(level, format(message, params));
433: }
434:
435: /**
436: * Logs a record at the specified level. The defaut implementation discards
437: * the source class and source method, then delegates to
438: * <code>{@linkplain #log(Level,String) log}(level, message)</code>.
439: */
440: public void logp(final Level level, final String sourceClass,
441: final String sourceMethod, final String message) {
442: log(level, message);
443: }
444:
445: /**
446: * Logs a record at the specified level. The defaut implementation discards
447: * the source class and source method, then delegates to
448: * <code>{@linkplain #log(Level,String,Throwable) log}(level, message, thrown)</code>.
449: */
450: public void logp(final Level level, final String sourceClass,
451: final String sourceMethod, final String message,
452: final Throwable thrown) {
453: log(level, message, thrown);
454: }
455:
456: /**
457: * Logs a record at the specified level. The defaut implementation delegates to
458: * <code>{@linkplain #logp(Level,String,String,String,Object[]) logp}(level, sourceClass,
459: * sourceMethod, message, params)</code> where the {@code params} array is built from the
460: * {@code param} object.
461: * <p>
462: * Note that {@code sourceClass} and {@code sourceMethod} will be discarted unless the
463: * target {@link #logp(Level,String,String,String) logp} method has been overriden.
464: */
465: public void logp(final Level level, final String sourceClass,
466: final String sourceMethod, final String message,
467: final Object param) {
468: if (isLoggable(level))
469: logp(level, sourceClass, sourceMethod, message,
470: asArray(param));
471: }
472:
473: /**
474: * Logs a record at the specified level. The defaut implementation formats the message
475: * immediately, then delegates to <code>{@linkplain #logp(Level,String,String,String)
476: * logp}(level, sourceClass, sourceMethod, message)</code>.
477: * <p>
478: * Note that {@code sourceClass} and {@code sourceMethod} will be discarted unless the
479: * target {@link #logp(Level,String,String,String) logp} method has been overriden.
480: */
481: public void logp(final Level level, final String sourceClass,
482: final String sourceMethod, final String message,
483: final Object[] params) {
484: if (isLoggable(level))
485: logp(level, sourceClass, sourceMethod, format(message,
486: params));
487: }
488:
489: /**
490: * Logs a localizable record at the specified level. The defaut implementation localizes the
491: * message immediately, then delegates to <code>{@linkplain #logp(Level,String,String,String)
492: * logp}(level, sourceClass, sourceMethod, message)</code>.
493: */
494: public void logrb(final Level level, final String sourceClass,
495: final String sourceMethod, final String bundleName,
496: final String message) {
497: if (isLoggable(level))
498: logp(level, sourceClass, sourceMethod, localize(bundleName,
499: message));
500: }
501:
502: /**
503: * Logs a localizable record at the specified level. The defaut implementation localizes the
504: * message immediately, then delegates to <code>{@linkplain #logp(Level,String,String,String,
505: * Throwable) logp}(level, sourceClass, sourceMethod, message, thrown)</code>.
506: */
507: public void logrb(final Level level, final String sourceClass,
508: final String sourceMethod, final String bundleName,
509: final String message, final Throwable thrown) {
510: if (isLoggable(level))
511: logp(level, sourceClass, sourceMethod, localize(bundleName,
512: message), thrown);
513: }
514:
515: /**
516: * Logs a localizable record at the specified level. The defaut implementation localizes the
517: * message immediately, then delegates to <code>{@linkplain #logp(Level,String,String,String,
518: * Object) logp}(level, sourceClass, sourceMethod, message, param)</code>.
519: */
520: public void logrb(final Level level, final String sourceClass,
521: final String sourceMethod, final String bundleName,
522: final String message, final Object param) {
523: if (isLoggable(level))
524: logp(level, sourceClass, sourceMethod, localize(bundleName,
525: message), param);
526: }
527:
528: /**
529: * Logs a localizable record at the specified level. The defaut implementation localizes the
530: * message immediately, then delegates to <code>{@linkplain #logp(Level,String,String,String,
531: * Object[]) logp}(level, sourceClass, sourceMethod, message, params)</code>.
532: */
533: public void logrb(final Level level, final String sourceClass,
534: final String sourceMethod, final String bundleName,
535: String message, final Object[] params) {
536: if (isLoggable(level))
537: logp(level, sourceClass, sourceMethod, localize(bundleName,
538: message), params);
539: }
540:
541: /**
542: * Do nothing since this logger adapter does not supports handlers.
543: * The configuration should be fully controlled by the external logging framework
544: * (e.g. <a href="http://commons.apache.org/logging/">Commons-logging</a>) instead,
545: * which is not expected to use {@link Handler} objects.
546: */
547: public void addHandler(Handler handler) {
548: }
549:
550: /**
551: * Do nothing since this logger adapter does not support handlers.
552: */
553: public void removeHandler(Handler handler) {
554: }
555:
556: /**
557: * Do nothing since this logger never use parent handlers. This is consistent
558: * with {@link #addHandler} not allowing to add any handlers, and avoid mixing
559: * loggings from the external framework with Java loggings.
560: */
561: public void setUseParentHandlers(boolean useParentHandlers) {
562: }
563:
564: /**
565: * Do nothing since this logger adapter does not support arbitrary parents.
566: * More specifically, it should not inherits any configuration from a parent
567: * logger using the Java logging framework.
568: */
569: public void setParent(Logger parent) {
570: }
571:
572: /**
573: * Do nothing since this logger adapter does not support filters. It is difficult to query
574: * efficiently the filter in this {@code LoggerAdapter} architecture (e.g. we would need to
575: * make sure that {@link Filter#isLoggable} is invoked only once even if a {@code log} call
576: * is cascaded into many other {@code log} calls, and this test must works in multi-threads
577: * environment).
578: */
579: public void setFilter(Filter filter) {
580: }
581:
582: /**
583: * Wraps the specified object in an array. This is a helper method for
584: * {@code log(..., Object)} methods that delegate their work to {@code log(..., Object[])}
585: */
586: private static Object[] asArray(final Object param) {
587: return (param != null) ? new Object[] { param } : null;
588: }
589:
590: /**
591: * Formats the specified message. This is a helper method for
592: * {@code log(..., Object[])} methods that delegate their work to {@code log(...)}
593: */
594: private static String format(String message, final Object[] params) {
595: if (params != null && params.length != 0) {
596: if (MESSAGE_FORMAT.matcher(message).find())
597: try {
598: message = MessageFormat.format(message, params);
599: } catch (IllegalArgumentException e) {
600: // The default Formatter.messageFormat implementation ignores this exception
601: // and uses the pattern as the message, so we mimic its behavior here.
602: }
603: }
604: return message;
605: }
606:
607: /**
608: * Localize the specified message. This is a helper method for
609: * {@code logrb(...)} methods that delegate their work to {@code logp(...)}
610: */
611: private static String localize(final String bundleName,
612: String message) {
613: if (bundleName != null)
614: try {
615: message = ResourceBundle.getBundle(bundleName)
616: .getString(message);
617: } catch (MissingResourceException e) {
618: // The default Formatter.messageFormat implementation ignores this exception
619: // and uses the bundle key as the message, so we mimic its behavior here.
620: }
621: return message;
622: }
623: }
|