commits@javamail.java.net

[javamail~mercurial:876] CompactFormatter should handle overridden Throwable.toString methods - Bu

From: <shannon_at_java.net>
Date: Wed, 2 Nov 2016 20:13:29 +0000

Project: javamail
Repository: mercurial
Revision: 876
Author: shannon
Date: 2016-11-02 20:11:51 UTC
Link:

Log Message:
------------
CompactFormatter should handle overridden Throwable.toString methods - Bug K8408
CompactFormatter backtrace should have a best guess fallback.
CompactFormatterTest add tests for known overridden toString methods.
CollectorFormatterTest fix copy paste error in example tests.

(From Jason)


Revisions:
----------
876


Modified Paths:
---------------
doc/release/CHANGES.txt
mail/src/main/java/com/sun/mail/util/logging/CompactFormatter.java
mail/src/test/java/com/sun/mail/util/logging/CollectorFormatterTest.java
mail/src/test/java/com/sun/mail/util/logging/CompactFormatterTest.java


Diffs:
------
diff -r 8ae6606d365b -r d0e30f93a591 doc/release/CHANGES.txt
--- a/doc/release/CHANGES.txt Fri Oct 21 12:32:57 2016 -0700
+++ b/doc/release/CHANGES.txt Wed Nov 02 13:11:51 2016 -0700
@@ -31,6 +31,7 @@
 K 8403 Test fails: javax.mail.internet.GetLocalAddressTest
 K 8404 Tests fail: com.sun.mail.util.WriteTimeoutSocketTest
 K 8405 MboxFolder.expunge can corrupt mailbox file
+K 8408 CompactFormatter should handle overridden Throwable.toString methods
 K 8415 Update public API to use generics
 K 8420 Malformed IMAP FETCH response throws the wrong exception
 K 8422 RFC822.SIZE > 2GB isn't handled

diff -r 8ae6606d365b -r d0e30f93a591 mail/src/main/java/com/sun/mail/util/logging/CompactFormatter.java
--- a/mail/src/main/java/com/sun/mail/util/logging/CompactFormatter.java Fri Oct 21 12:32:57 2016 -0700
+++ b/mail/src/main/java/com/sun/mail/util/logging/CompactFormatter.java Wed Nov 02 13:11:51 2016 -0700
@@ -49,10 +49,10 @@
  * separator and newline characters. Only specified fields support an
  * {_at_linkplain #toAlternate(java.lang.String) alternate} fixed width format.
  * <p>
- * By default each <tt>CompactFormatter</tt> is initialized using the
- * following LogManager configuration properties where
+ * By default each <tt>CompactFormatter</tt> is initialized using the following
+ * LogManager configuration properties where
  * <tt>&lt;formatter-name&gt;</tt> refers to the fully qualified class name or
- * the fully qualified derived class name of the formatter. If properties are
+ * the fully qualified derived class name of the formatter. If properties are
  * not defined, or contain invalid values, then the specified default values are
  * used.
  * <ul>
@@ -65,6 +65,7 @@
  * @since JavaMail 1.5.2
  */
 public class CompactFormatter extends java.util.logging.Formatter {
+
     /**
      * Load any declared classes to workaround GLASSFISH-21258.
      */
@@ -74,7 +75,7 @@
 
     /**
      * Used to load declared classes encase class loader doesn't allow loading
- * during JVM termination. This method is used with unit testing.
+ * during JVM termination. This method is used with unit testing.
      *
      * @return an array of classes never null.
      */
@@ -118,13 +119,13 @@
      * &lt;formatter-name&gt;.format property or the format that was given when
      * this formatter was created.</li>
      * <li>{_at_code date} - if the log record supports nanoseconds then a
- * ZonedDateTime object representing the event time of the log record in
- * the system time zone. Otherwise, a {_at_linkplain Date} object
- * representing {_at_linkplain LogRecord#getMillis event time} of the log
- * record.</li>
+ * ZonedDateTime object representing the event time of the log record in the
+ * system time zone. Otherwise, a {_at_linkplain Date} object representing
+ * {_at_linkplain LogRecord#getMillis event time} of the log record.</li>
      * <li>{_at_code source} - a string representing the caller, if available;
      * otherwise, the logger's name.</li>
      * <li>{_at_code logger} - the logger's
+ * {_at_linkplain Class#getSimpleName() simple}
      * {_at_linkplain LogRecord#getLoggerName() name}.</li>
      * <li>{_at_code level} - the
      * {_at_linkplain java.util.logging.Level#getLocalizedName log level}.</li>
@@ -144,16 +145,17 @@
      * {_at_linkplain LogRecord#getSequenceNumber() sequence number} if the given
      * log record.</li>
      * <li>{_at_code thread id} the {_at_linkplain LogRecord#getThreadID() thread id}
- * of the given log record. By default this is formatted as a {_at_code long}
+ * of the given log record. By default this is formatted as a {_at_code long}
      * by an unsigned conversion.</li>
- * <li>{_at_code error} the throwable simple class name and
- * {_at_linkplain #formatError(LogRecord) error message} without any
- * stack trace.</li>
- * <li>{_at_code message|error} The message and error properties joined as
- * one parameter. This parameter supports
+ * <li>{_at_code error} the throwable
+ * {_at_linkplain Class#getSimpleName() simple class name} and
+ * {_at_linkplain #formatError(LogRecord) error message} without any stack
+ * trace.</li>
+ * <li>{_at_code message|error} The message and error properties joined as one
+ * parameter. This parameter supports
      * {_at_linkplain #toAlternate(java.lang.String) alternate} form.</li>
- * <li>{_at_code error|message} The error and message properties joined as
- * one parameter. This parameter supports
+ * <li>{_at_code error|message} The error and message properties joined as one
+ * parameter. This parameter supports
      * {_at_linkplain #toAlternate(java.lang.String) alternate} form.</li>
      * <li>{_at_code backtrace} only the
      * {_at_linkplain #formatBackTrace(LogRecord) stack trace} of the given
@@ -180,10 +182,10 @@
      * <li>{_at_code com.sun.mail.util.logging.CompactFormatter.format=%7$#.20s%n}
      * <p>
      * This prints only 20 characters of the message|thrown ({_at_code 7$}) using
- * the {_at_linkplain #toAlternate(java.lang.String) alternate} form. This
- * will perform a weighted truncation of both the message and thrown
- * properties of the log record. The separator is not included as part of
- * the total width.
+ * the {_at_linkplain #toAlternate(java.lang.String) alternate} form. This will
+ * perform a weighted truncation of both the message and thrown properties
+ * of the log record. The separator is not included as part of the total
+ * width.
      * <pre>
      * Encoding|NullPointerE
      * </pre>
@@ -209,8 +211,8 @@
      *
      * <li>{_at_code com.sun.mail.util.logging.CompactFormatter.format=[%9$d][%1$tT][%10$d][%2$s] %5$s%n%6$s%n}
      * <p>
- * This prints the sequence ({_at_code 9$}), event time ({_at_code 1$}) as
- * 24 hour time, thread id ({_at_code 10$}), source ({_at_code 2$}), log message
+ * This prints the sequence ({_at_code 9$}), event time ({_at_code 1$}) as 24 hour
+ * time, thread id ({_at_code 10$}), source ({_at_code 2$}), log message
      * ({_at_code 5$}), and the throwable with back trace ({_at_code 6$}).
      * <pre>
      * [125][14:11:42][38][MyClass fatal] Unable to send notification.
@@ -274,15 +276,42 @@
 
     /**
      * Formats the message from the thrown property of the log record. This
- * method removes any fully qualified throwable class names from the message
- * cause chain.
+ * method replaces fully qualified throwable class names from the message
+ * cause chain with simple class names.
      *
      * @param t the throwable to format or null.
      * @return the empty string if null was given or the formatted message
      * string from the throwable which may be null.
      */
     public String formatMessage(final Throwable t) {
- return t != null ? replaceClassName(apply(t).getMessage(), t) : "";
+ String r;
+ if (t != null) {
+ final Throwable apply = apply(t);
+ final String m = apply.getLocalizedMessage();
+ final String s = apply.toString();
+ final String sn = simpleClassName(apply.getClass());
+ if (!isNullOrSpaces(m)) {
+ if (s.contains(m)) {
+ if (s.startsWith(apply.getClass().getName())
+ || s.startsWith(sn)) {
+ r = replaceClassName(m, t);
+ } else {
+ r = replaceClassName(simpleClassName(s), t);
+ }
+ } else {
+ r = replaceClassName(simpleClassName(s) + ": " + m, t);
+ }
+ } else {
+ r = replaceClassName(simpleClassName(s), t);
+ }
+
+ if (!r.contains(sn)) {
+ r = sn + ": " + r;
+ }
+ } else {
+ r = "";
+ }
+ return r;
     }
 
     /**
@@ -330,7 +359,7 @@
     }
 
     /**
- * Formats the thread id property of the given log record. By default this
+ * Formats the thread id property of the given log record. By default this
      * is formatted as a {_at_code long} by an unsigned conversion.
      *
      * @param record the record.
@@ -341,8 +370,8 @@
     public Number formatThreadID(final LogRecord record) {
         /**
          * Thread.getID is defined as long and LogRecord.getThreadID is defined
- * as int. Convert to unsigned as a means to better map the two types
- * of thread identifiers.
+ * as int. Convert to unsigned as a means to better map the two types of
+ * thread identifiers.
          */
         return (((long) record.getThreadID()) & 0xffffffffL);
     }
@@ -362,7 +391,7 @@
         final Throwable t = record.getThrown();
         if (t != null) {
             String site = formatBackTrace(record);
- msg = formatToString(t) + (isNullOrSpaces(site) ? "" : ' ' + site);
+ msg = formatMessage(t) + (isNullOrSpaces(site) ? "" : ' ' + site);
         } else {
             msg = "";
         }
@@ -382,25 +411,7 @@
      * @since JavaMail 1.5.4
      */
     public String formatError(final LogRecord record) {
- Throwable t = record.getThrown();
- if (t != null) {
- return formatToString(t);
- } else {
- return "";
- }
- }
-
- /**
- * Gets the simple class name of the reduced throwable and message of
- * the reduced throwable.
- *
- * @param t the given throwable.
- * @return the formatted throwable.
- * @since JavaMail 1.5.4
- * @throws NullPointerException if given throwable is null.
- */
- private String formatToString(Throwable t) {
- return simpleClassName(apply(t).getClass()) + ": " + formatMessage(t);
+ return formatMessage(record.getThrown());
     }
 
     /**
@@ -413,18 +424,24 @@
      * @see #formatThrown(java.util.logging.LogRecord)
      * @see #ignore(java.lang.StackTraceElement)
      */
- public String formatBackTrace(LogRecord record) {
+ public String formatBackTrace(final LogRecord record) {
         String site = "";
         final Throwable t = record.getThrown();
         if (t != null) {
             final Throwable root = apply(t);
- site = findAndFormat(root.getStackTrace());
+ StackTraceElement[] trace = root.getStackTrace();
+ site = findAndFormat(trace);
             if (isNullOrSpaces(site)) {
                 int limit = 0;
                 for (Throwable c = t; c != null; c = c.getCause()) {
- site = findAndFormat(c.getStackTrace());
+ StackTraceElement[] ste = c.getStackTrace();
+ site = findAndFormat(ste);
                     if (!isNullOrSpaces(site)) {
                         break;
+ } else {
+ if (trace.length == 0) {
+ trace = ste;
+ }
                     }
 
                     //Deal with excessive cause chains
@@ -433,6 +450,11 @@
                         break; //Give up.
                     }
                 }
+
+ //Punt.
+ if (isNullOrSpaces(site) && trace.length != 0) {
+ site = formatStackTraceElement(trace[0]);
+ }
             }
         }
         return site;
@@ -445,7 +467,7 @@
      * @return a String that best describes the call site.
      * @throws NullPointerException if stack trace element array is null.
      */
- private String findAndFormat(StackTraceElement[] trace) {
+ private String findAndFormat(final StackTraceElement[] trace) {
         String site = "";
         for (StackTraceElement s : trace) {
             if (!ignore(s)) {
@@ -512,12 +534,12 @@
      * @return true if this frame should be ignored.
      * @see #formatThrown(java.util.logging.LogRecord)
      */
- protected boolean ignore(StackTraceElement s) {
+ protected boolean ignore(final StackTraceElement s) {
         return isUnknown(s) || defaultIgnore(s);
     }
 
     /**
- * Defines the alternate format. This implementation removes all control
+ * Defines the alternate format. This implementation removes all control
      * characters from the given string.
      *
      * @param s any string or null.
@@ -538,7 +560,7 @@
     private Comparable<?> formatZonedDateTime(final LogRecord record) {
         Comparable<?> zdt = LogManagerProperties.getZonedDateTime(record);
         if (zdt == null) {
- zdt = new java.util.Date(record.getMillis());
+ zdt = new java.util.Date(record.getMillis());
         }
         return zdt;
     }
@@ -551,7 +573,7 @@
      * @param s the stack trace element.
      * @return true if this frame should be ignored.
      */
- private boolean defaultIgnore(StackTraceElement s) {
+ private boolean defaultIgnore(final StackTraceElement s) {
         return isSynthetic(s) || isStaticUtility(s) || isReflection(s);
     }
 
@@ -565,8 +587,7 @@
         try {
             return LogManagerProperties.isStaticUtilityClass(s.getClassName());
         } catch (RuntimeException ignore) {
- } catch (Exception ignore) {
- } catch (LinkageError ignore) {
+ } catch (Exception | LinkageError ignore) {
         }
         final String cn = s.getClassName();
         return (cn.endsWith("s") && !cn.endsWith("es"))
@@ -607,8 +628,7 @@
         try {
             return LogManagerProperties.isReflectionClass(s.getClassName());
         } catch (RuntimeException ignore) {
- } catch (Exception ignore) {
- } catch (LinkageError ignore) {
+ } catch (Exception | LinkageError ignore) {
         }
         return s.getClassName().startsWith("java.lang.reflect.")
                 || s.getClassName().startsWith("sun.reflect.");
@@ -692,15 +712,45 @@
     }
 
     /**
- * Converts a fully qualified class name to a simple class name.
+ * Converts a fully qualified class name to a simple class name. If the
+ * leading part of the given string is not a legal class name then the given
+ * string is returned.
      *
- * @param name the fully qualified class name or null.
- * @return the simple class name or null.
+ * @param name the fully qualified class name prefix or null.
+ * @return the simple class name or given input.
      */
     private static String simpleClassName(String name) {
         if (name != null) {
- final int index = name.lastIndexOf('.');
- name = index > -1 ? name.substring(index + 1) : name;
+ int cursor = 0;
+ int sign = -1;
+ int dot = -1;
+ for (int c, prev = dot; cursor < name.length();
+ cursor += Character.charCount(c)) {
+ c = name.codePointAt(cursor);
+ if (!Character.isJavaIdentifierPart(c)) {
+ if (c == ((int) '.')) {
+ if ((dot + 1) != cursor && (dot + 1) != sign) {
+ prev = dot;
+ dot = cursor;
+ } else {
+ return name;
+ }
+ } else {
+ if ((dot + 1) == cursor) {
+ dot = prev;
+ }
+ break;
+ }
+ } else {
+ if (c == ((int) '$')) {
+ sign = cursor;
+ }
+ }
+ }
+
+ if (dot > -1 && ++dot < cursor && ++sign < cursor) {
+ name = name.substring(sign > dot ? sign : dot);
+ }
         }
         return name;
     }

diff -r 8ae6606d365b -r d0e30f93a591 mail/src/test/java/com/sun/mail/util/logging/CollectorFormatterTest.java
--- a/mail/src/test/java/com/sun/mail/util/logging/CollectorFormatterTest.java Fri Oct 21 12:32:57 2016 -0700
+++ b/mail/src/test/java/com/sun/mail/util/logging/CollectorFormatterTest.java Wed Nov 02 13:11:51 2016 -0700
@@ -634,9 +634,9 @@
 
     @Test
     public void testGetTailExample3a() {
- String p = "These {3} messages occurred between "
- + "{9,choice,86400000#{7,date} {7,time} and {8,time}"
- + "|86400000<{7,date} and {8,date}}\n";
+ String p = "These {3} messages occurred between\n"
+ + "{7,date,EEE, MMM dd HH:mm:ss:S ZZZ yyyy}"
+ + " and {8,time,EEE, MMM dd HH:mm:ss:S ZZZ yyyy}\n";
         CollectorFormatter cf = new CollectorFormatter(p);
         LogRecord min = new LogRecord(Level.SEVERE, "");
         setEpochMilli(min, 1248203502449L);
@@ -654,21 +654,9 @@
 
         String output = cf.getTail((Handler) null);
         assertNotNull(output);
-
- cf.format(min);
- for (int i = 0; i < 114; ++i) {
- LogRecord mid = new LogRecord(Level.SEVERE, "");
- setEpochMilli(mid, min.getMillis());
- cf.format(mid);
- }
-
- setEpochMilli(max, min.getMillis() + 2591000000L);
- cf.format(max);
-
- output = cf.getTail((Handler) null);
- assertNotNull(output);
     }
 
+ @Test
     public void testGetTailExample3b() {
         String p = "These {3} messages occurred between "
                 + "{9,choice,86400000#{7,date} {7,time} and {8,time}"

diff -r 8ae6606d365b -r d0e30f93a591 mail/src/test/java/com/sun/mail/util/logging/CompactFormatterTest.java
--- a/mail/src/test/java/com/sun/mail/util/logging/CompactFormatterTest.java Fri Oct 21 12:32:57 2016 -0700
+++ b/mail/src/test/java/com/sun/mail/util/logging/CompactFormatterTest.java Wed Nov 02 13:11:51 2016 -0700
@@ -364,6 +364,356 @@
     }
 
     @Test
+ public void testFormatThrownTrailingDot() {
+ testFormatThrownIllegalClassName("Hello.");
+ }
+
+ @Test
+ public void testFormatThrownClassDotSpace() {
+ String msg = "test";
+ String prefix = IllegalStateException.class.getName() + ". ";
+ Throwable t = new PrefixException(prefix, null, null);
+ assertEquals(prefix, t.toString());
+ LogRecord record = new LogRecord(Level.SEVERE, "");
+ record.setThrown(t);
+ CompactFormatter cf = new CompactFormatter("%6$s");
+ String result = cf.format(record);
+ StackTraceElement[] ste = t.getStackTrace();
+ String frame = CompactFormatterTest.class.getSimpleName()
+ + '.' + ste[0].getMethodName() + "(:"
+ + ste[0].getLineNumber() + ")";
+
+ String cns = t.getClass().getSimpleName()
+ + ": " + IllegalStateException.class.getSimpleName();
+ assertTrue(result, result.startsWith(cns));
+ assertTrue(result, result.indexOf(cns) == result.lastIndexOf(cns));
+ assertTrue(result, result.contains(msg));
+ assertTrue(result, result.indexOf(msg) == result.lastIndexOf(msg));
+ assertTrue(result, result.endsWith(frame));
+
+ cf = new CompactFormatter("%11$s %14$s");
+ assertEquals(result, cf.format(record));
+ }
+
+ @Test
+ public void testFormatThrownLeadingDot() {
+ testFormatThrownIllegalClassName(".Hello");
+ }
+
+ @Test
+ public void testFormatThrownDotDot() {
+ testFormatThrownIllegalClassName("Hello..World");
+ testFormatThrownIllegalClassName("..HelloWorld");
+ testFormatThrownIllegalClassName("HelloWorld..");
+ }
+
+ @Test
+ public void testFormatThrownColonSpace() {
+ testFormatThrownIllegalClassName("Hello: World");
+ }
+
+ @Test
+ public void testFormatThrownEndSign() {
+ //Some of these are legal but not worth considering legal.
+ testFormatThrownIllegalClassName("HelloWorld$");
+ testFormatThrownIllegalClassName("HelloWorld.$");
+ testFormatThrownIllegalClassName("Hello.World$");
+ }
+
+ @Test
+ public void testFormatThrownStartSign() {
+ testFormatThrownIllegalClassName("$HelloWorld");
+ testFormatThrownIllegalClassName("$.HelloWorld");
+ }
+
+ @Test
+ public void testFormatThrownDotDotSign() {
+ testFormatThrownIllegalClassName("Hello..$World");
+ testFormatThrownIllegalClassName("..$HelloWorld");
+ testFormatThrownIllegalClassName("HelloWorld..$");
+ }
+
+ @Test
+ public void testFormatThrownSignDot() {
+ testFormatThrownIllegalClassName("$.HelloWorld");
+ testFormatThrownIllegalClassName("HelloWorld$.");
+ }
+
+ @Test
+ public void testFormatThrownSignDotDot() {
+ testFormatThrownIllegalClassName("Hello$..World");
+ testFormatThrownIllegalClassName("$..HelloWorld");
+ testFormatThrownIllegalClassName("HelloWorld$..");
+ }
+
+ @Test
+ public void testFormatThrownDotSignDot() {
+ testFormatThrownIllegalClassName("Hello.$.World");
+ testFormatThrownIllegalClassName(".$.HelloWorld");
+ testFormatThrownIllegalClassName("HelloWorld.$.");
+ }
+
+ private void testFormatThrownIllegalClassName(String prefix) {
+ Throwable t = new PrefixException(prefix, null, null);
+ assertEquals(prefix, t.toString());
+ LogRecord record = new LogRecord(Level.SEVERE, "");
+ record.setThrown(t);
+ CompactFormatter cf = new CompactFormatter("%6$s");
+ String result = cf.format(record);
+ StackTraceElement[] ste = t.getStackTrace();
+ String frame = CompactFormatterTest.class.getSimpleName()
+ + '.' + ste[0].getMethodName() + "(:"
+ + ste[0].getLineNumber() + ")";
+
+ String cn = t.getClass().getSimpleName();
+ assertTrue(result, result.startsWith(cn));
+ assertTrue(result, result.indexOf(cn) == result.lastIndexOf(cn));
+ assertTrue(result, result.contains(prefix));
+ assertTrue(result, result.indexOf(prefix) == result.lastIndexOf(prefix));
+ assertTrue(result, result.endsWith(frame));
+
+ cf = new CompactFormatter("%11$s %14$s");
+ assertEquals(result, cf.format(record));
+ }
+
+ @Test
+ public void testFormatThrownSimpleClassNameNullMessage() {
+ //javax.management.BadStringOperationException
+ String op = "some op";
+ Throwable t = new PrefixException(PrefixException.class.getSimpleName()
+ + ": " + op, (String) null, null);
+ assertNull(t.getMessage());
+ assertNotNull(t.toString());
+ assertTrue(t.toString().startsWith(t.getClass().getSimpleName()));
+
+ LogRecord record = new LogRecord(Level.SEVERE, "");
+ record.setThrown(t);
+ CompactFormatter cf = new CompactFormatter("%6$s");
+ String result = cf.format(record);
+ StackTraceElement[] ste = t.getStackTrace();
+ String frame = CompactFormatterTest.class.getSimpleName()
+ + '.' + ste[0].getMethodName() + "(:"
+ + ste[0].getLineNumber() + ")";
+
+ String sn = t.getClass().getSimpleName();
+ assertTrue(result, result.startsWith(sn));
+ assertTrue(result, result.indexOf(sn) == result.lastIndexOf(sn));
+ assertTrue(result, result.contains(op));
+ assertTrue(result, result.indexOf(op) == result.lastIndexOf(op));
+ assertTrue(result, result.endsWith(frame));
+
+ cf = new CompactFormatter("%11$s %14$s");
+ assertEquals(result, cf.format(record));
+ }
+
+ @Test
+ public void testFormatServerSidetMetroException() {
+ //com.sun.xml.ws.developer.ServerSideException
+ String msg = "server error";
+ NullPointerException npe = new NullPointerException(msg);
+ Throwable t = new PrefixException(npe.getClass().getName(), msg, null);
+ assertEquals(msg, npe.getMessage());
+ assertEquals(msg, t.getMessage());
+ assertEquals(npe.toString(), t.toString());
+
+ LogRecord record = new LogRecord(Level.SEVERE, "");
+ record.setThrown(t);
+ CompactFormatter cf = new CompactFormatter("%6$s");
+ String result = cf.format(record);
+ StackTraceElement[] ste = t.getStackTrace();
+ String frame = CompactFormatterTest.class.getSimpleName()
+ + '.' + ste[0].getMethodName() + "(:"
+ + ste[0].getLineNumber() + ")";
+
+ String cns = t.getClass().getSimpleName()
+ + ": " + npe.getClass().getSimpleName();
+ assertTrue(result, result.startsWith(cns));
+ assertTrue(result, result.indexOf(cns) == result.lastIndexOf(cns));
+ assertTrue(result, result.contains(msg));
+ assertTrue(result, result.endsWith(frame));
+
+ cf = new CompactFormatter("%11$s %14$s");
+ assertEquals(result, cf.format(record));
+ }
+
+ @Test
+ public void testFormatThrownPrefixMessageRetainsFqn() {
+ String msg = "java.io.tmpdir";
+ NullPointerException npe = new NullPointerException(msg);
+ Throwable t = new PrefixException(npe.getClass().getName(), msg, null);
+ assertEquals(msg, npe.getMessage());
+ assertEquals(msg, t.getMessage());
+ assertEquals(npe.toString(), t.toString());
+
+ LogRecord record = new LogRecord(Level.SEVERE, "");
+ record.setThrown(t);
+ CompactFormatter cf = new CompactFormatter("%6$s");
+ String result = cf.format(record);
+ StackTraceElement[] ste = t.getStackTrace();
+ String frame = CompactFormatterTest.class.getSimpleName()
+ + '.' + ste[0].getMethodName() + "(:"
+ + ste[0].getLineNumber() + ")";
+
+ String cns = t.getClass().getSimpleName()
+ + ": " + npe.getClass().getSimpleName();
+ assertTrue(result, result.startsWith(cns));
+ assertTrue(result, result.indexOf(cns) == result.lastIndexOf(cns));
+ assertTrue(result, result.contains(msg));
+ assertTrue(result, result.endsWith(frame));
+
+ cf = new CompactFormatter("%11$s %14$s");
+ assertEquals(result, cf.format(record));
+ }
+
+ @Test
+ public void testFormatThrownHiddenMessageRetainsFqn() {
+ String msg = "java.io.tmpdir";
+ NullPointerException npe = new NullPointerException(msg);
+ Throwable t = new ToStringException(npe.getClass().getName(), msg);
+ assertEquals(msg, npe.getMessage());
+ assertEquals(msg, t.getMessage());
+
+ LogRecord record = new LogRecord(Level.SEVERE, "");
+ record.setThrown(t);
+ CompactFormatter cf = new CompactFormatter("%6$s");
+ String result = cf.format(record);
+ StackTraceElement[] ste = t.getStackTrace();
+ String frame = CompactFormatterTest.class.getSimpleName()
+ + '.' + ste[0].getMethodName() + "(:"
+ + ste[0].getLineNumber() + ")";
+
+ String cns = t.getClass().getSimpleName()
+ + ": " + npe.getClass().getSimpleName();
+ assertTrue(result, result.startsWith(cns));
+ assertTrue(result, result.indexOf(cns) == result.lastIndexOf(cns));
+ assertTrue(result, result.contains(msg));
+ assertTrue(result, result.endsWith(frame));
+
+ cf = new CompactFormatter("%11$s %14$s");
+ assertEquals(result, cf.format(record));
+ }
+
+ @Test
+ public void testFormatXMLParseXercesException() {
+ //com.sun.org.apache.xerces.internal.xni.parser.XMLParseException
+ String msg = "XML";
+ String prefix = "1:two:3:four";
+ Throwable t = new PrefixException(prefix, msg, null);
+
+ LogRecord record = new LogRecord(Level.SEVERE, "");
+ record.setThrown(t);
+ CompactFormatter cf = new CompactFormatter("%6$s");
+ String result = cf.format(record);
+ StackTraceElement[] ste = t.getStackTrace();
+ String frame = CompactFormatterTest.class.getSimpleName()
+ + '.' + ste[0].getMethodName() + "(:"
+ + ste[0].getLineNumber() + ")";
+
+ assertTrue(prefix, t.toString().startsWith(prefix));
+ String cn = t.getClass().getSimpleName();
+ assertTrue(result, result.startsWith(cn));
+ assertTrue(result, result.indexOf(cn) == result.lastIndexOf(cn));
+ assertTrue(result, result.contains(prefix));
+ assertTrue(result, result.indexOf(prefix) == result.lastIndexOf(prefix));
+ assertTrue(result, result.contains(msg));
+ assertTrue(result, result.endsWith(frame));
+
+ cf = new CompactFormatter("%11$s %14$s");
+ assertEquals(result, cf.format(record));
+ }
+
+ @Test
+ public void testFormatGSSException() {
+ //org.ietf.jgss.GSSException
+ String msg = "Invalid name provided";
+ String prefix = PrefixException.class.getSimpleName();
+ Throwable t = new PrefixException(prefix, msg, null);
+ assertTrue(t.toString().startsWith(t.getClass().getSimpleName()));
+ LogRecord record = new LogRecord(Level.SEVERE, "");
+ record.setThrown(t);
+ CompactFormatter cf = new CompactFormatter("%6$s");
+ String result = cf.format(record);
+ StackTraceElement[] ste = t.getStackTrace();
+ String frame = CompactFormatterTest.class.getSimpleName()
+ + '.' + ste[0].getMethodName() + "(:"
+ + ste[0].getLineNumber() + ")";
+
+ assertTrue(prefix, t.toString().startsWith(prefix));
+ String cn = t.getClass().getSimpleName();
+ assertTrue(result, result.startsWith(cn));
+ assertTrue(result, result.indexOf(cn) == result.lastIndexOf(cn));
+ assertTrue(result, result.contains(prefix));
+ assertTrue(result, result.indexOf(prefix) == result.lastIndexOf(prefix));
+ assertTrue(result, result.contains(msg));
+ assertTrue(result, result.endsWith(frame));
+
+ cf = new CompactFormatter("%11$s %14$s");
+ assertEquals(result, cf.format(record));
+ }
+
+ @Test
+ public void testFormatMismatchedTreeNodeException() {
+ //org.antlr.runtime.MismatchedTreeNodeException
+ String prefix = ToStringException.class.getSimpleName()
+ + '(' + String.class.getName() + "!="
+ + Throwable.class.getName() + ')';
+
+ Throwable t = new ToStringException(prefix, (String) null);
+ assertNull(t.getLocalizedMessage());
+ assertNull(t.getMessage());
+ LogRecord record = new LogRecord(Level.SEVERE, "");
+ record.setThrown(t);
+ CompactFormatter cf = new CompactFormatter("%6$s");
+ String result = cf.format(record);
+ StackTraceElement[] ste = t.getStackTrace();
+ String frame = CompactFormatterTest.class.getSimpleName()
+ + '.' + ste[0].getMethodName() + "(:"
+ + ste[0].getLineNumber() + ")";
+
+ assertTrue(prefix, t.toString().startsWith(prefix));
+ String cn = t.getClass().getSimpleName();
+ assertTrue(result, result.startsWith(cn));
+ assertTrue(result, result.indexOf(cn) == result.lastIndexOf(cn));
+ assertTrue(result, result.contains(prefix));
+ assertTrue(result, result.indexOf(prefix) == result.lastIndexOf(prefix));
+ assertTrue(result, result.endsWith(frame));
+
+ cf = new CompactFormatter("%11$s %14$s");
+ assertEquals(result, cf.format(record));
+ }
+
+ @Test
+ public void testFormatInnerException() {
+ String msg = "inner class";
+ String prefix = '(' + String.class.getName() + "!="
+ + Throwable.class.getName() + ')';
+
+ Throwable t = new ToStringException(ToStringException.class.getName()
+ + prefix, msg);
+ assertFalse(t.toString().contains(t.getLocalizedMessage()));
+ LogRecord record = new LogRecord(Level.SEVERE, "");
+ record.setThrown(t);
+ CompactFormatter cf = new CompactFormatter("%6$s");
+ String result = cf.format(record);
+ StackTraceElement[] ste = t.getStackTrace();
+ String frame = CompactFormatterTest.class.getSimpleName()
+ + '.' + ste[0].getMethodName() + "(:"
+ + ste[0].getLineNumber() + ")";
+
+ assertTrue(prefix, t.toString().contains(prefix));
+ String cn = t.getClass().getSimpleName();
+ assertTrue(result, result.startsWith(cn));
+ assertTrue(result, result.indexOf(cn) == result.lastIndexOf(cn));
+ assertTrue(result, result.contains(prefix));
+ assertTrue(result, result.indexOf(prefix) == result.lastIndexOf(prefix));
+ assertTrue(result, result.contains(msg));
+ assertTrue(result, result.endsWith(frame));
+
+ cf = new CompactFormatter("%11$s %14$s");
+ assertEquals(result, cf.format(record));
+ }
+
+ @Test
     public void testFormatMessage_Throwable() {
         Exception e = new IOException(Exception.class.getName());
         e = new Exception(e.toString(), e);
@@ -371,7 +721,8 @@
 
         CompactFormatter cf = new CompactFormatter();
         String result = cf.formatMessage(e);
- assertEquals(result, Exception.class.getSimpleName());
+ assertEquals(IOException.class.getSimpleName()
+ + ": " + Exception.class.getSimpleName(), result);
     }
 
     @Test
@@ -385,7 +736,8 @@
     public void testFormatMessage_ThrowableNullMessage() {
         CompactFormatter cf = new CompactFormatter();
         String result = cf.formatMessage(new Throwable());
- assertNull(result);
+ String expect = Throwable.class.getSimpleName();
+ assertEquals(expect, result);
     }
 
     @Test(timeout = 30000)
@@ -420,6 +772,44 @@
         assertEquals(Object.class.getSimpleName(), result);
     }
 
+ @Test
+ public void testFormatRootLogger() {
+ testFormatLoggerNonClassName("");
+ }
+
+ @Test
+ public void testFormatGlobalLogger() {
+ testFormatLoggerNonClassName("global");
+ }
+
+ @Test
+ public void testFormatLoggerLeadingDot() {
+ testFormatLoggerNonClassName(".Hello");
+ }
+
+ @Test
+ public void testFormatLoggerDotDot() {
+ testFormatLoggerNonClassName("Hello..World");
+ }
+
+ @Test
+ public void testFormatLoggerColonSpace() {
+ testFormatLoggerNonClassName("Hello: World");
+ }
+
+ private void testFormatLoggerNonClassName(String name) {
+ CompactFormatter cf = new CompactFormatter();
+ LogRecord record = new LogRecord(Level.SEVERE, "");
+ record.setSourceMethodName(null);
+ record.setSourceClassName(null);
+ record.setLoggerName(name);
+ String result = cf.formatLoggerName(record);
+ assertEquals(name, result);
+
+ cf = new CompactFormatter("%3$s");
+ assertEquals(result, cf.format(record));
+ }
+
     @Test(expected = NullPointerException.class)
     public void testFormatLoggerNull() {
         CompactFormatter cf = new CompactFormatter();
@@ -493,7 +883,7 @@
         }
     }
 
- @Test(expected=NullPointerException.class)
+ @Test(expected = NullPointerException.class)
     public void testFormatNull() {
         CompactFormatter cf = new CompactFormatter();
         cf.format((LogRecord) null);
@@ -601,6 +991,30 @@
         assertTrue(result, result.endsWith(cf.formatBackTrace(record)));
     }
 
+ @Test
+ public void testFormatThrownLocalized() {
+ //sun.security.provider.PolicyParser$ParsingException
+ CountLocalizedException cle = new CountLocalizedException();
+ CompactFormatter cf = new CompactFormatter();
+ LogRecord record = new LogRecord(Level.SEVERE, "");
+ record.setThrown(cle);
+ String result = cf.formatThrown(record);
+ assertNotNull(result, result);
+ assertTrue(cle.localizedMessage > 0);
+ }
+
+ @Test
+ public void testInheritsFormatMessage() {
+ InheritsFormatMessage cf = new InheritsFormatMessage();
+ LogRecord record = new LogRecord(Level.SEVERE, "");
+ record.setThrown(new Throwable());
+ String result = cf.formatThrown(record);
+ assertNotNull(cf.getClass().getName(), result);
+
+ result = cf.formatError(record);
+ assertNotNull(cf.getClass().getName(), result);
+ }
+
     @Test(expected = NullPointerException.class)
     public void testFormatThrownNullRecord() {
         CompactFormatter cf = new CompactFormatter();
@@ -617,7 +1031,6 @@
         String expect = Long.toString(record.getThreadID());
         assertEquals(expect, output);
 
-
         record.setThreadID(-1); //Largest value for the CompactFormatter.
         output = cf.format(record);
         expect = Long.toString((1L << 32L) - 1L);
@@ -629,7 +1042,7 @@
         assertEquals(expect, Long.toString(id.longValue()));
     }
 
- @Test(expected=NullPointerException.class)
+ @Test(expected = NullPointerException.class)
     public void testFormatThreadIDNull() {
         CompactFormatter cf = new CompactFormatter();
         cf.formatThreadID((LogRecord) null);
@@ -656,7 +1069,7 @@
         assertTrue(output.endsWith(record.getThrown().getMessage()));
     }
 
- @Test(expected=NullPointerException.class)
+ @Test(expected = NullPointerException.class)
     public void testFormatErrorNull() {
         CompactFormatter cf = new CompactFormatter();
         cf.formatError((LogRecord) null);
@@ -671,12 +1084,12 @@
         assertNotNull(output);
     }
 
- @Test(expected=NullPointerException.class)
+ @Test(expected = NullPointerException.class)
     public void testFormatThrownMessageApplyReturnsNull() {
         CompactFormatter cf = new ApplyReturnsNull();
- for (int i=0; i<10; i++) {
+ for (int i = 0; i < 10; i++) {
             String output = cf.formatMessage(new Throwable());
- assertNull(output);
+ assertEquals(Throwable.class.getSimpleName(), output);
         }
     }
 
@@ -718,11 +1131,11 @@
         assertTrue(output, output.endsWith(record.getMessage()));
     }
 
- @Test(expected=NullPointerException.class)
+ @Test(expected = NullPointerException.class)
     public void testErrorApplyReturnsNull() {
- CompactFormatter cf = new ApplyReturnsNull();
+ CompactFormatter cf = new ApplyReturnsNull();
         LogRecord r = new LogRecord(Level.SEVERE, "");
- for (int i=0; i<10; i++) {
+ for (int i = 0; i < 10; i++) {
             String output = cf.formatError(r);
             assertNotNull(output);
             r.setThrown(new Throwable(Integer.toString(i), r.getThrown()));
@@ -825,11 +1238,11 @@
         }
     }
 
- @Test(expected=NullPointerException.class)
+ @Test(expected = NullPointerException.class)
     public void testFormatApplyReturnsNull() {
- CompactFormatter cf = new ApplyReturnsNull();
+ CompactFormatter cf = new ApplyReturnsNull();
         LogRecord r = new LogRecord(Level.SEVERE, "");
- for (int i=0; i<10; i++) {
+ for (int i = 0; i < 10; i++) {
             String output = cf.format(r);
             assertNotNull(output);
             r.setThrown(new Throwable(Integer.toString(i), r.getThrown()));
@@ -855,11 +1268,11 @@
         assertEquals(result, cf.format(record));
     }
 
- @Test(expected=NullPointerException.class)
+ @Test(expected = NullPointerException.class)
     public void testBackTraceApplyReturnsNull() {
- CompactFormatter cf = new ApplyReturnsNull();
+ CompactFormatter cf = new ApplyReturnsNull();
         LogRecord r = new LogRecord(Level.SEVERE, "");
- for (int i=0; i<10; i++) {
+ for (int i = 0; i < 10; i++) {
             String output = cf.formatBackTrace(r);
             assertNotNull(output);
             r.setThrown(new Throwable(Integer.toString(i), r.getThrown()));
@@ -883,6 +1296,40 @@
         assertTrue(result, result.contains("testFormatBackTrace"));
     }
 
+ @Test
+ public void testFormatBackTracePunt() {
+ final Class<?> k = Collections.class;
+ Exception e = new NullPointerException("Fake NPE");
+ e.setStackTrace(new StackTraceElement[]{
+ new StackTraceElement(k.getName(), "newSetFromMap", null, 3878)});
+ assertNotNull(e.getMessage(), e.getMessage());
+
+ CompactFormatter cf = new CompactFormatter();
+ LogRecord record = new LogRecord(Level.SEVERE, "");
+ record.setThrown(e);
+ String result = cf.formatBackTrace(record);
+ assertTrue(result, result.startsWith(k.getSimpleName()));
+ assertTrue(result, result.contains("newSetFromMap"));
+ }
+
+ @Test
+ public void testFormatBackTraceChainPunt() {
+ final Class<?> k = Collections.class;
+ Throwable e = new NullPointerException("Fake NPE");
+ e.setStackTrace(new StackTraceElement[0]);
+ e = new RuntimeException(e);
+ e.setStackTrace(new StackTraceElement[]{
+ new StackTraceElement(k.getName(), "newSetFromMap", null, 3878)});
+ assertNotNull(e.getMessage(), e.getMessage());
+
+ CompactFormatter cf = new CompactFormatter();
+ LogRecord record = new LogRecord(Level.SEVERE, "");
+ record.setThrown(e);
+ String result = cf.formatBackTrace(record);
+ assertTrue(result, result.startsWith(k.getSimpleName()));
+ assertTrue(result, result.contains("newSetFromMap"));
+ }
+
     @Test(expected = NullPointerException.class)
     public void testFormatBackTraceNull() {
         CompactFormatter cf = new CompactFormatter();
@@ -1120,10 +1567,60 @@
         }
     }
 
+ private final static class CountLocalizedException extends RuntimeException {
+
+ private static final long serialVersionUID = 1L;
+ public int localizedMessage;
+
+ CountLocalizedException() {
+ super();
+ }
+
+ @Override
+ public String getLocalizedMessage() {
+ localizedMessage++;
+ return super.getLocalizedMessage();
+ }
+ }
+
+ private final static class ToStringException extends RuntimeException {
+
+ private static final long serialVersionUID = 1L;
+ private final String toString;
+
+ ToStringException(String toString, String msg) {
+ super(msg);
+ this.toString = toString;
+ }
+
+ @Override
+ public String toString() {
+ return toString;
+ }
+ }
+
+ private final static class PrefixException extends RuntimeException {
+
+ private static final long serialVersionUID = 1L;
+ private final String prefix;
+
+ PrefixException(String prefix, String msg, Throwable cause) {
+ super(msg, cause);
+ this.prefix = prefix;
+ }
+
+ @Override
+ public String toString() {
+ String message = getLocalizedMessage();
+ return (message != null) ? (prefix + ": " + message) : prefix;
+ }
+ }
+
     /**
      * An example of a broken implementation of thread ID.
      */
     private static class ThreadIDReturnsNull extends CompactFormatter {
+
         /**
          * Promote access level.
          */
@@ -1137,10 +1634,22 @@
         }
     }
 
+ private static class InheritsFormatMessage extends CompactFormatter {
+
+ InheritsFormatMessage() {
+ }
+
+ @Override
+ public String formatMessage(Throwable t) {
+ return InheritsFormatMessage.class.getName();
+ }
+ }
+
     /**
      * An example of a broken implementation of apply.
      */
     private static class ApplyReturnsNull extends CompactFormatter {
+
         /**
          * The number of throwables.
          */