commits@javamail.java.net

[javamail~mercurial:642] CompactFormatter width should have been precision.

From: <shannon_at_java.net>
Date: Mon, 7 Apr 2014 23:29:12 +0000

Project: javamail
Repository: mercurial
Revision: 642
Author: shannon
Date: 2014-04-07 21:03:06 UTC
Link:

Log Message:
------------
Added CollectorFormatter, CompactFormatter, and SeverityComparator to support
auto generated subject lines. - bug 6353
LogManagerProperties added support for stack trace inspection.
MailHandler remove control chars from subject and part names.

(From Jason)
CompactFormatter width should have been precision.

(From Jason)


Revisions:
----------
641
642


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


Added Paths:
------------
mail/src/main/java/com/sun/mail/util/logging/CollectorFormatter.java
mail/src/main/java/com/sun/mail/util/logging/CompactFormatter.java
mail/src/main/java/com/sun/mail/util/logging/SeverityComparator.java
mail/src/test/java/com/sun/mail/util/logging/CollectorFormatterTest.java
mail/src/test/java/com/sun/mail/util/logging/CompactFormatterTest.java
mail/src/test/java/com/sun/mail/util/logging/SeverityComparatorTest.java


Diffs:
------
diff -r bede51b8123d -r d6955daac275 doc/release/CHANGES.txt
--- a/doc/release/CHANGES.txt Fri Apr 04 15:41:36 2014 -0700
+++ b/doc/release/CHANGES.txt Mon Apr 07 13:58:47 2014 -0700
@@ -36,6 +36,7 @@
 K 6328 add ability to specify an Executor to process events
 K 6336 handle multiple IMAP BODY elements in a single FETCH response
 K 6352 add more efficient way to monitor multiple folders for new messages
+K 6353 Include a subject formatter for the logging package
 
 
                   CHANGES IN THE 1.5.1 RELEASE

diff -r bede51b8123d -r d6955daac275 mail/src/main/java/com/sun/mail/util/logging/CollectorFormatter.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mail/src/main/java/com/sun/mail/util/logging/CollectorFormatter.java Mon Apr 07 13:58:47 2014 -0700
@@ -0,0 +1,481 @@
+package com.sun.mail.util.logging;
+
+import java.lang.reflect.UndeclaredThrowableException;
+import java.text.MessageFormat;
+import java.util.Comparator;
+import java.util.Locale;
+import java.util.ResourceBundle;
+import java.util.logging.Formatter;
+import java.util.logging.Handler;
+import java.util.logging.LogManager;
+import java.util.logging.LogRecord;
+
+/**
+ * A LogRecord formatter that takes a sequence of LogRecords and combines them
+ * into a single summary result. Formating of the head, LogRecord, and tail are
+ * delegated to the wrapped formatter.
+ *
+ * <p>
+ * The LogManager properties are:
+ * <ul>
+ * <li>&lt;formatter-name&gt;.comparator name of a
+ * {_at_linkplain java.util.Comparator} class used to choose the collected
+ * <tt>LogRecord</tt>. If a comparator is specified then the max
+ * <tt>LogRecord</tt> is chosen. If a comparator is not specified then, the last
+ * record is chosen (defaults to <tt>null</tt>).
+ *
+ * <li>&lt;formatter-name&gt;.comparator.reverse a boolean
+ * <tt>true</tt> to collect the min <tt>LogRecord</tt> or <tt>false</tt> to
+ * collect the max <tt>LogRecord</tt>. (defaults to <tt>false</tt>)
+ *
+ * <li>&lt;formatter-name&gt;.format the
+ * {_at_linkplain java.text.MessageFormat MessageFormat} string used to format the
+ * collected summary statistics. The arguments are explained in detail in the
+ * {_at_linkplain #getTail(java.util.logging.Handler) getTail} documentation.
+ * (defaults to "{0}{1}{2}{4,choice,-1#|0&lt;... {4,number,integer} more}\n")
+ *
+ * <li>&lt;formatter-name&gt;.formatter name of a <tt>Formatter</tt> class used
+ * to format the collected LogRecord. (defaults to {_at_link CompactFormatter})
+ *
+ * </ul>
+ *
+ * @author Jason Mehrens
+ * @since JavaMail 1.5.2
+ */
+public class CollectorFormatter extends Formatter {
+
+ /**
+ * Avoid depending on JMX runtime bean to get the start time.
+ */
+ private static final long INIT_TIME = System.currentTimeMillis();
+ /**
+ * The message format string used as the formatted output.
+ */
+ private final String fmt;
+ /**
+ * The formatter used to format the chosen log record.
+ */
+ private final Formatter formatter;
+ /**
+ * The comparator used to pick the log record to format.
+ */
+ private final Comparator<? super LogRecord> comparator;
+ /**
+ * The last accepted record. Synchronized access is preferred over volatile
+ * for this class.
+ */
+ private LogRecord last;
+ /**
+ * The number of log records that have been formatted.
+ */
+ private long count;
+ /**
+ * The number of log records that have been formatted with a thrown object.
+ */
+ private long thrown;
+ /**
+ * The eldest log record time.
+ */
+ private long minMillis;
+ /**
+ * The newest log record time.
+ */
+ private long maxMillis;
+
+ /**
+ * Creates the formatter using the LogManager defaults.
+ *
+ * @throws SecurityException if a security manager exists and the caller
+ * does not have <tt>LoggingPermission("control")</tt>.
+ * @throws UndeclaredThrowableException if there are problems when loading
+ * from the LogManager.
+ */
+ public CollectorFormatter() {
+ final String p = getClass().getName();
+ this.fmt = initFormat(p);
+ this.formatter = initFormatter(p);
+ this.comparator = initComparator(p);
+ reset();
+ }
+
+ /**
+ * Creates the formatter using the given format.
+ *
+ * @param format the message format.
+ * @throws SecurityException if a security manager exists and the caller
+ * does not have <tt>LoggingPermission("control")</tt>.
+ * @throws UndeclaredThrowableException if there are problems when loading
+ * from the LogManager.
+ */
+ public CollectorFormatter(String format) {
+ final String p = getClass().getName();
+ this.fmt = format == null ? initFormat(p) : format;
+ this.formatter = initFormatter(p);
+ this.comparator = initComparator(p);
+ reset();
+ }
+
+ /**
+ * Creates the formatter using the given values.
+ *
+ * @param format the format string.
+ * @param f the formatter used on the collected log record or null.
+ * @param c the comparator used to determine which log record to format or
+ * null.
+ * @throws SecurityException if a security manager exists and the caller
+ * does not have <tt>LoggingPermission("control")</tt>.
+ * @throws UndeclaredThrowableException if there are problems when loading
+ * from the LogManager.
+ */
+ public CollectorFormatter(String format, Formatter f,
+ Comparator<? super LogRecord> c) {
+ final String p = getClass().getName();
+ this.fmt = format == null ? initFormat(p) : format;
+ this.formatter = f;
+ this.comparator = c;
+ reset();
+ }
+
+ /**
+ * Accumulates log records which will be used to produce the final output.
+ * The output is generated using the {_at_link getTail} method which also
+ * resets this formatter back to its original state.
+ *
+ * @param record the record to store.
+ * @return an empty string.
+ * @throws NullPointerException if the given record is null.
+ */
+ @Override
+ public String format(final LogRecord record) {
+ if (record == null) {
+ throw new NullPointerException();
+ }
+
+ boolean accepted;
+ do {
+ final LogRecord peek = peek();
+ //The self compare of the first record acts like a type check.
+ LogRecord update = apply(peek != null ? peek : record, record);
+ if (peek != update) { //Not identical.
+ update.getSourceMethodName(); //Infer caller.
+ accepted = acceptAndUpdate(peek, update);
+ } else {
+ accepted = true;
+ accept(record);
+ }
+ } while (!accepted);
+ return "";
+ }
+
+ /**
+ * Formats the collected LogRecord and summary statistics. The collected
+ * results are reset after calling this method.
+ *
+ * <ol start='0'>
+ * <li>{_at_code head} the
+ * {_at_linkplain Formatter#getHead(java.util.logging.Handler) head} string
+ * returned from the target formatter and
+ * {_at_linkplain #finish(java.lang.String) finished} by this formatter.
+ * <li>{_at_code formatted} the current log record
+ * {_at_linkplain Formatter#format(java.util.logging.LogRecord) formatted} by
+ * the target formatter and {_at_linkplain #finish(java.lang.String) finished}
+ * by this formatter.
+ * <li>{_at_code tail} the
+ * {_at_linkplain Formatter#getTail(java.util.logging.Handler) tail} string
+ * returned from the target formatter and
+ * {_at_linkplain #finish(java.lang.String) finished} by this formatter.
+ * <li>{_at_code count} the total number of log records
+ * {_at_linkplain #format consumed} by this formatter.
+ * <li>{_at_code remaining} the count minus one.
+ * <li>{_at_code thrown} the total number of log records
+ * {_at_linkplain #format consumed} by this formatter with an assigned
+ * throwable.
+ * <li>{_at_code normal messages} the count minus the thrown.
+ * <li>{_at_code minMillis} the eldest log record event time
+ * {_at_linkplain #format consumed} by this formatter. If no records were
+ * formatted then this is set to the approximate start time of the JVM. By
+ * default this parameter is defined as a number. The format type and format
+ * style rules from the {_at_link java.text.MessageFormat} should be used to
+ * convert this to a date or time.
+ * <li>{_at_code maxMillis} the most recent log record event time
+ * {_at_linkplain #format consumed} by this formatter. If no records were
+ * formatted then this is set to the current time. By default this parameter
+ * is defined as a number. The format type and format style rules from the
+ * {_at_link java.text.MessageFormat} should be used to convert this to a date
+ * or time.
+ * </ol>
+ *
+ * @param h the handler or null.
+ * @return the output string.
+ */
+ @Override
+ public String getTail(final Handler h) {
+ return formatRecord(h, true);
+ }
+
+ /**
+ * Peeks at the current LogRecord and formats it.
+ *
+ * @return the current record formatted or the default toString.
+ * @see #getTail(java.util.logging.Handler)
+ */
+ @Override
+ public String toString() {
+ String result;
+ try {
+ result = formatRecord((Handler) null, false);
+ } catch (final RuntimeException ignore) {
+ result = super.toString();
+ }
+ return result;
+ }
+
+ /**
+ * Used to choose the collected LogRecord. This implementation returns the
+ * greater of two LogRecords.
+ *
+ * @param t the current record.
+ * @param u the record that could replace the current.
+ * @return the greater of the given log records.
+ * @throws NullPointerException may occur if either record is null.
+ */
+ protected LogRecord apply(final LogRecord t, final LogRecord u) {
+ if (t == null || u == null) {
+ throw new NullPointerException();
+ }
+
+ if (comparator != null) {
+ return comparator.compare(t, u) >= 0 ? t : u;
+ } else {
+ return u;
+ }
+ }
+
+ /**
+ * Updates the summary statistics but does not store the given LogRecord.
+ *
+ * @param record the LogRecord used to collect statistics.
+ */
+ private synchronized void accept(final LogRecord record) {
+ final long millis = record.getMillis();
+ minMillis = Math.min(minMillis, millis);
+ maxMillis = Math.max(maxMillis, millis);
+ ++count;
+ if (record.getThrown() != null) {
+ ++thrown;
+ }
+ }
+
+ /**
+ * Resets all of the collected summary statistics including the LogRecord.
+ */
+ private synchronized void reset() {
+ last = null;
+ count = 0L;
+ thrown = 0L;
+ minMillis = Long.MAX_VALUE;
+ maxMillis = Long.MIN_VALUE;
+ }
+
+ /**
+ * Formats the given record with the head and tail.
+ *
+ * @param h the Handler or null.
+ * @param reset true if the summary statistics and LogRecord should be reset
+ * back to initial values.
+ * @return the formatted string.
+ * @see #getTail(java.util.logging.Handler)
+ */
+ private String formatRecord(final Handler h, final boolean reset) {
+ final LogRecord record;
+ final long c;
+ final long t;
+ long msl;
+ long msh;
+ synchronized (this) {
+ record = last;
+ c = count;
+ t = thrown;
+ msl = minMillis;
+ msh = maxMillis;
+
+ if (reset) { //BUG ID 6351685
+ reset();
+ }
+ }
+
+ if (c == 0L) { //Use the estimated lifespan of this class.
+ msl = INIT_TIME;
+ msh = System.currentTimeMillis();
+ }
+
+ final String head;
+ final String msg;
+ final String tail;
+ final Formatter f = this.formatter;
+ if (f != null) {
+ synchronized (f) {
+ head = f.getHead(h);
+ msg = record != null ? f.format(record) : "";
+ tail = f.getTail(h);
+ }
+ } else {
+ head = msg = tail = "";
+ }
+
+ Locale l = null;
+ if (record != null) {
+ ResourceBundle rb = record.getResourceBundle();
+ l = rb == null ? null : rb.getLocale();
+ }
+
+ //NumberFormat used by the MessageFormat requires a non null locale.
+ final MessageFormat mf;
+ if (l == null) {
+ mf = new MessageFormat(fmt);
+ } else {
+ mf = new MessageFormat(fmt, l);
+ }
+
+ /**
+ * These arguments are described in the getTail documentation.
+ */
+ return mf.format(new Object[]{finish(head), finish(msg), finish(tail),
+ c, (c - 1), t, (c - t), msl, msh});
+ }
+
+ /**
+ * Applied to the head, format, and tail returned by the target formatter.
+ * This implementation trims all input strings.
+ *
+ * @param s the string to transform.
+ * @return the transformed string.
+ * @throws NullPointerException if the given string is null.
+ */
+ protected String finish(String s) {
+ return s.trim();
+ }
+
+ /**
+ * Peek at the current log record.
+ *
+ * @return null or the current log record.
+ */
+ private synchronized LogRecord peek() {
+ return this.last;
+ }
+
+ /**
+ * Updates the summary statistics and stores given LogRecord if the expected
+ * record matches the current record.
+ *
+ * @param e the expected record.
+ * @param u the update record.
+ * @return true if the update was performed.
+ */
+ private synchronized boolean acceptAndUpdate(LogRecord e, LogRecord u) {
+ if (e == this.last) {
+ accept(u);
+ this.last = u;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Gets the message format string from the LogManager or creates the default
+ * message format string.
+ *
+ * @param p the class name prefix.
+ * @return the format string.
+ */
+ private String initFormat(final String p) {
+ final LogManager m = LogManagerProperties.getLogManager();
+ String v = m.getProperty(p.concat(".format"));
+ if (v == null || v.length() == 0) {
+ v = "{0}{1}{2}{4,choice,-1#|0<... {4,number,integer} more}\n";
+ }
+ return v;
+ }
+
+ /**
+ * Gets and creates the formatter from the LogManager or creates the default
+ * formatter.
+ *
+ * @param p the class name prefix.
+ * @return the formatter.
+ */
+ private Formatter initFormatter(final String p) {
+ final LogManager m = LogManagerProperties.getLogManager();
+ Formatter f;
+ String v = m.getProperty(p.concat(".formatter"));
+ if (v != null && v.length() != 0) {
+ if (!"null".equalsIgnoreCase(v)) {
+ try {
+ f = LogManagerProperties.newFormatter(v);
+ } catch (final RuntimeException re) {
+ throw re;
+ } catch (final Exception e) {
+ throw new UndeclaredThrowableException(e);
+ }
+ } else {
+ f = null;
+ }
+ } else {
+ //Don't force the byte code verifier to load the formatter.
+ f = Formatter.class.cast(new CompactFormatter());
+ }
+ return f;
+ }
+
+ /**
+ * Gets and creates the comparator from the LogManager or returns the
+ * default comparator.
+ *
+ * @param p the class name prefix.
+ * @return the comparator or null.
+ * @throws IllegalArgumentException if it was specified that the comparator
+ * should be reversed but no initial comparator was specified.
+ * @throws UndeclaredThrowableException if the comparator can not be
+ * created.
+ */
+ @SuppressWarnings("unchecked")
+ private Comparator<? super LogRecord> initComparator(final String p) {
+ final LogManager m = LogManagerProperties.getLogManager();
+ Comparator<? super LogRecord> c;
+ final String name = m.getProperty(p.concat(".comparator"));
+ final String reverse = m.getProperty(p.concat(".comparator.reverse"));
+ try {
+ if (name != null) {
+ if (!"null".equalsIgnoreCase(name)) {
+ c = LogManagerProperties.newComparator(name);
+ if (Boolean.parseBoolean(reverse)) {
+ assert c != null;
+ c = LogManagerProperties.reverseOrder(c);
+ }
+ } else {
+ if (reverse != null) {
+ throw new IllegalArgumentException(
+ "No comparator to reverse.");
+ } else {
+ c = null; //No ordering.
+ }
+ }
+ } else {
+ if (reverse != null) {
+ throw new IllegalArgumentException(
+ "No comparator to reverse.");
+ } else {
+ //Don't force the byte code verifier to load the comparator.
+ c = Comparator.class.cast(SeverityComparator.getInstance());
+ }
+ }
+ } catch (final RuntimeException re) {
+ throw re; //Avoid catch all.
+ } catch (final Exception e) {
+ throw new UndeclaredThrowableException(e);
+ }
+ return c;
+ }
+}

diff -r bede51b8123d -r d6955daac275 mail/src/main/java/com/sun/mail/util/logging/CompactFormatter.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mail/src/main/java/com/sun/mail/util/logging/CompactFormatter.java Mon Apr 07 13:58:47 2014 -0700
@@ -0,0 +1,618 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright (c) 2013-2014 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013-2014 Jason Mehrens. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development
+ * and Distribution License("CDDL") (collectively, the "License"). You
+ * may not use this file except in compliance with the License. You can
+ * obtain a copy of the License at
+ * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
+ * or packager/legal/LICENSE.txt. See the License for the specific
+ * language governing permissions and limitations under the License.
+ *
+ * When distributing the software, include this License Header Notice in each
+ * file and include the License file at packager/legal/LICENSE.txt.
+ *
+ * GPL Classpath Exception:
+ * Oracle designates this particular file as subject to the "Classpath"
+ * exception as provided by Oracle in the GPL Version 2 section of the License
+ * file that accompanied this code.
+ *
+ * Modifications:
+ * If applicable, add the following below the License Header, with the fields
+ * enclosed by brackets [] replaced by your own identifying information:
+ * "Portions Copyright [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you don't indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to
+ * its licensees as provided above. However, if you add GPL Version 2 code
+ * and therefore, elected the GPL Version 2 license, then the option applies
+ * only if the new code is made subject to such option by the copyright
+ * holder.
+ */
+package com.sun.mail.util.logging;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.Locale;
+import java.util.ResourceBundle;
+import java.util.logging.LogManager;
+import java.util.logging.LogRecord;
+
+/**
+ * A plain text formatter that can produce fixed width output. By default this
+ * formatter will produce output no greater than 160 characters wide. Only
+ * specified fields support an {_at_link #toAlternate(java.lang.String) alternate}
+ * fixed width format.
+ * <p>
+ * The LogManager properties are:
+ * <ul>
+ * <li>&lt;formatter-name&gt;.format - the {_at_link java.util.Formatter
+ * format} string used to transform the output. The format string can be used to
+ * fix the output size. (defaults to "%7$#160s%n")</li>
+ * </ul>
+ *
+ * @author Jason Mehrens
+ * @since JavaMail 1.5.2
+ */
+public class CompactFormatter extends java.util.logging.Formatter {
+
+ /**
+ * Holds the java.util.Formatter pattern.
+ */
+ private final String fmt;
+
+ /**
+ * Creates an instance with a default format pattern.
+ */
+ public CompactFormatter() {
+ String p = getClass().getName();
+ this.fmt = initFormat(p);
+ }
+
+ /**
+ * Creates an instance with the given format pattern.
+ *
+ * @param format the {_at_link java.util.Formatter pattern}. The arguments are
+ * described in the {_at_link #format(java.util.logging.LogRecord) format}
+ * method.
+ */
+ public CompactFormatter(final String format) {
+ if (format == null) {
+ throw new NullPointerException();
+ }
+ this.fmt = format;
+ }
+
+ /**
+ * Format the given log record and returns the formatted string.
+ *
+ * <ol start='0'>
+ * <li>{_at_code format} - the {_at_link java.util.Formatter
+ * java.util.Formatter} format string specified in the
+ * {_at_code &lt;formatter-name&gt;.format} property or the format that was
+ * given when this formatter was created.</li>
+ * <li>{_at_code date} - a {_at_link 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 name.</li>
+ * <li>{_at_code level} - the
+ * {_at_linkplain java.util.logging.Level#getLocalizedName log level}.</li>
+ * <li>{_at_code message} - the formatted log message returned from the
+ * {_at_link #formatMessage(LogRecord)} method.</li>
+ * <li>{_at_code thrown} - a string representing the
+ * {_at_linkplain LogRecord#getThrown throwable} associated with the log record
+ * and a relevant stack trace element if available. Otherwise, an empty
+ * string is used.</li>
+ * <li>{_at_code message|thrown} The message and the thrown properties joined
+ * as one parameter. The
+ * {_at_link java.util.Formatter conversion-dependent alternate form} should
+ * always be specified with this parameter. If a width is defined then that
+ * is treated as the maximum width.</li>
+ * <li>{_at_code thrown|message} The thrown and message properties joined as
+ * one parameter. The
+ * {_at_link java.util.Formatter conversion-dependent alternate form} should
+ * always be specified with this parameter. If a width is defined then that
+ * is treated as the maximum width.</li>
+ * </ol>
+ *
+ * @param record to format.
+ * @return the formatted record.
+ * @throws NullPointerException if the given record is null.
+ */
+ @Override
+ public String format(final LogRecord record) {
+ //LogRecord is mutable so define local vars.
+ ResourceBundle rb = record.getResourceBundle();
+ Locale l = rb == null ? null : rb.getLocale();
+
+ String msg = formatMessage(record);
+ String thrown = formatThrown(record);
+ Object[] params = {
+ new Date(record.getMillis()),
+ formatSource(record),
+ formatLoggerName(record),
+ formatLevel(record),
+ msg,
+ thrown,
+ new Alternate(msg, thrown),
+ new Alternate(thrown, msg)};
+
+ return String.format(l, fmt, params);
+ }
+
+ /**
+ * Formats message for the log record. This method removes any fully
+ * qualified throwable class names from the message.
+ *
+ * @param record the log record.
+ * @return the formatted message string.
+ */
+ @Override
+ public String formatMessage(final LogRecord record) {
+ String msg = super.formatMessage(record);
+ msg = replaceClassName(msg, record.getThrown());
+ msg = replaceClassName(msg, record.getParameters());
+ return msg;
+ }
+
+ /**
+ * 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.
+ *
+ * @param t the throwable to format.
+ * @return the formatted message string from the throwable.
+ */
+ public String formatMessage(final Throwable t) {
+ return t != null ? replaceClassName(apply(t).getMessage(), t) : "";
+ }
+
+ /**
+ * Formats the level property of the given log record.
+ *
+ * @param record the record.
+ * @return the formatted logger name.
+ * @throws NullPointerException if the given record is null.
+ */
+ public String formatLevel(final LogRecord record) {
+ return record.getLevel().getLocalizedName();
+ }
+
+ /**
+ * Formats the source from the given log record.
+ *
+ * @param record the record.
+ * @return the formatted source of the log record.
+ * @throws NullPointerException if the given record is null.
+ */
+ public String formatSource(final LogRecord record) {
+ String source = record.getSourceClassName();
+ if (source != null) {
+ if (record.getSourceMethodName() != null) {
+ source = simpleClassName(source) + " "
+ + record.getSourceMethodName();
+ } else {
+ source = simpleClassName(source);
+ }
+ } else {
+ source = simpleClassName(record.getLoggerName());
+ }
+ return source;
+ }
+
+ /**
+ * Formats the logger name property of the given log record.
+ *
+ * @param record the record.
+ * @return the formatted logger name.
+ * @throws NullPointerException if the given record is null.
+ */
+ public String formatLoggerName(final LogRecord record) {
+ return simpleClassName(record.getLoggerName());
+ }
+
+ /**
+ * Formats the thrown property of a LogRecord. The returned string will
+ * contain a throwable message with a back trace.
+ *
+ * @param record the record.
+ * @return empty string if nothing was thrown or formatted string.
+ * @throws NullPointerException if the given record is null.
+ * @see #apply(java.lang.Throwable)
+ * @see #formatBackTrace(java.util.logging.LogRecord)
+ */
+ public String formatThrown(final LogRecord record) {
+ String msg;
+ final Throwable t = record.getThrown();
+ if (t != null) {
+ final Throwable root = apply(t);
+ if (root != null) {
+ msg = formatMessage(t);
+ String site = formatBackTrace(record);
+ msg = root.getClass().getSimpleName() + ": " + msg
+ + (isNullOrSpaces(site) ? "" : ' ' + site);
+ } else {
+ msg = "";
+ }
+ } else {
+ msg = "";
+ }
+ return msg;
+ }
+
+ /**
+ * Formats the back trace for the given log record.
+ *
+ * @param record the log record to format.
+ * @return the formatted back trace.
+ * @throws NullPointerException if the given record is null.
+ * @see #apply(java.lang.Throwable)
+ * @see #formatThrown(java.util.logging.LogRecord)
+ * @see #ignore(java.lang.StackTraceElement)
+ */
+ public String formatBackTrace(LogRecord record) {
+ String site = "";
+ final Throwable t = record.getThrown();
+ if (t != null) {
+ final Throwable root = apply(t);
+ if (root != null) {
+ site = findAndFormat(root.getStackTrace());
+ if (isNullOrSpaces(site)) {
+ int limit = 0;
+ for (Throwable c = t; c != null; c = c.getCause()) {
+ site = findAndFormat(c.getStackTrace());
+ if (!isNullOrSpaces(site)) {
+ break;
+ }
+
+ //Deal with excessive cause chains
+ //and cyclic throwables.
+ if (++limit == (1 << 16)) {
+ break; //Give up.
+ }
+ }
+ }
+ }
+ }
+ return site;
+ }
+
+ /**
+ * Finds and formats the first stack frame of interest.
+ *
+ * @param trace the fill stack to examine.
+ * @return a String that best describes the call site.
+ * @throws NullPointerException if stack trace element array is null.
+ */
+ private String findAndFormat(StackTraceElement[] trace) {
+ String site = "";
+ for (StackTraceElement s : trace) {
+ if (!ignore(s)) {
+ site = formatStackTraceElement(s);
+ break;
+ }
+ }
+
+ //Check if all code was compiled with no debugging info.
+ if (isNullOrSpaces(site)) {
+ for (StackTraceElement s : trace) {
+ if (!defaultIgnore(s)) {
+ site = formatStackTraceElement(s);
+ break;
+ }
+ }
+ }
+ return site;
+ }
+
+ /**
+ * Formats a stack trace element into a simple call site.
+ *
+ * @param s the stack trace element to format.
+ * @return the formatted stack trace element.
+ * @throws NullPointerException if stack trace element is null.
+ * @see #formatThrown(java.util.logging.LogRecord)
+ */
+ private String formatStackTraceElement(final StackTraceElement s) {
+ String v = simpleClassName(s.getClassName());
+ String result;
+ if (v != null) {
+ result = s.toString().replace(s.getClassName(), v);
+ } else {
+ result = s.toString();
+ }
+
+ //If the class name contains the simple file name then remove file name.
+ v = simpleFileName(s.getFileName());
+ if (v != null && result.startsWith(v)) {
+ result = result.replace(s.getFileName(), "");
+ }
+ return result;
+ }
+
+ /**
+ * Chooses a single throwable from the cause chain that will be formatted.
+ * This implementation chooses the throwable that best describes the chain.
+ * Subclasses can override this method to choose an alternate throwable for
+ * formatting.
+ *
+ * @param t the throwable from the log record.
+ * @return the throwable or null.
+ * @see #formatThrown(java.util.logging.LogRecord)
+ */
+ protected Throwable apply(final Throwable t) {
+ return SeverityComparator.getInstance().apply(t);
+ }
+
+ /**
+ * Determines if a stack frame should be ignored as the cause of an error.
+ *
+ * @param s the stack trace element.
+ * @return true if this frame should be ignored.
+ * @see #formatThrown(java.util.logging.LogRecord)
+ */
+ protected boolean ignore(StackTraceElement s) {
+ return isUnknown(s) || defaultIgnore(s);
+ }
+
+ /**
+ * Defines the alternate format. This implementation removes all control
+ * characters from the given string.
+ *
+ * @param s any string or null.
+ * @return null if the argument was null otherwise, an alternate string.
+ */
+ protected String toAlternate(final String s) {
+ return s != null ? s.replaceAll("[\\x00-\\x1F\\x7F]+", "") : null;
+ }
+
+ /**
+ * Determines if a stack frame should be ignored as the cause of an error.
+ * This does not check for unknown line numbers because code can be compiled
+ * without debugging info.
+ *
+ * @param s the stack trace element.
+ * @return true if this frame should be ignored.
+ */
+ private boolean defaultIgnore(StackTraceElement s) {
+ return isSynthetic(s) || isStaticUtility(s) || isReflection(s);
+ }
+
+ /**
+ * Determines if a stack frame is for a static utility class.
+ *
+ * @param s the stack trace element.
+ * @return true if this frame should be ignored.
+ */
+ private boolean isStaticUtility(final StackTraceElement s) {
+ try {
+ return LogManagerProperties.isStaticUtilityClass(s.getClassName());
+ } catch (RuntimeException ignore) {
+ } catch (Exception ignore) {
+ } catch (LinkageError ignore) {
+ }
+ return false;
+ }
+
+ /**
+ * Determines if a stack trace element is for a synthetic method.
+ *
+ * @param s the stack trace element.
+ * @return true if synthetic.
+ * @throws NullPointerException if stack trace element is null.
+ */
+ private boolean isSynthetic(final StackTraceElement s) {
+ return s.getMethodName().indexOf('$') > -1;
+ }
+
+ /**
+ * Determines if a stack trace element has an unknown line number or a
+ * native line number.
+ *
+ * @param s the stack trace element.
+ * @return true if the line number is unknown.
+ * @throws NullPointerException if stack trace element is null.
+ */
+ private boolean isUnknown(final StackTraceElement s) {
+ return s.getLineNumber() < 0;
+ }
+
+ /**
+ * Determines if a stack trace element represents a reflection frame.
+ *
+ * @param s the stack trace element.
+ * @return true if the line number is unknown.
+ * @throws NullPointerException if stack trace element is null.
+ */
+ private boolean isReflection(final StackTraceElement s) {
+ try {
+ return LogManagerProperties.isReflectionClass(s.getClassName());
+ } catch (RuntimeException ignore) {
+ } catch (Exception ignore) {
+ } catch (LinkageError ignore) {
+ }
+ return false;
+ }
+
+ /**
+ * Creates the format pattern for this formatter.
+ *
+ * @param p the class name prefix.
+ * @return the java.util.Formatter format string.
+ * @throws NullPointerException if the given class name is null.
+ */
+ private String initFormat(final String p) {
+ LogManager m = LogManagerProperties.getLogManager();
+ String v = m.getProperty(p.concat(".format"));
+ if (isNullOrSpaces(v)) {
+ v = "%7$#160s%n"; //160 chars split between message and thrown.
+ }
+ return v;
+ }
+
+ /**
+ * Searches the given message for all instances fully qualified class name
+ * with simple class name based off of the types contained in the given
+ * parameter array.
+ *
+ * @param msg the message.
+ * @param t the throwable cause chain to search or null.
+ * @return the modified message string.
+ */
+ private static String replaceClassName(String msg, Throwable t) {
+ if (!isNullOrSpaces(msg)) {
+ int limit = 0;
+ for (Throwable c = t; c != null; c = c.getCause()) {
+ final Class<?> k = c.getClass();
+ msg = msg.replace(k.getName(), k.getSimpleName());
+
+ //Deal with excessive cause chains and cyclic throwables.
+ if (++limit == (1 << 16)) {
+ break; //Give up.
+ }
+ }
+ }
+ return msg;
+ }
+
+ /**
+ * Searches the given message for all instances fully qualified class name
+ * with simple class name based off of the types contained in the given
+ * parameter array.
+ *
+ * @param msg the message or null.
+ * @param p the parameter array or null.
+ * @return the modified message string.
+ */
+ private static String replaceClassName(String msg, Object[] p) {
+ if (!isNullOrSpaces(msg) && p != null) {
+ for (Object o : p) {
+ if (o != null) {
+ final Class<?> k = o.getClass();
+ msg = msg.replace(k.getName(), k.getSimpleName());
+ }
+ }
+ }
+ return msg;
+ }
+
+ /**
+ * Converts a fully qualified class name to a simple class name.
+ *
+ * @param name the fully qualified class name or null.
+ * @return the simple class name or null.
+ */
+ private static String simpleClassName(String name) {
+ if (name != null) {
+ final int index = name.lastIndexOf('.');
+ name = index > -1 ? name.substring(index + 1) : name;
+ }
+ return name;
+ }
+
+ /**
+ * Converts a file name with an extension to a file name without an
+ * extension.
+ *
+ * @param name the full file name or null.
+ * @return the simple file name or null.
+ */
+ private static String simpleFileName(String name) {
+ if (name != null) {
+ final int index = name.lastIndexOf('.');
+ name = index > -1 ? name.substring(0, index) : name;
+ }
+ return name;
+ }
+
+ /**
+ * Determines is the given string is null or spaces.
+ *
+ * @param s the string or null.
+ * @return true if null or spaces.
+ */
+ private static boolean isNullOrSpaces(final String s) {
+ return s == null || s.trim().length() == 0;
+ }
+
+ /**
+ * Used to format two arguments as fixed width message. This class violates
+ * the width contract because it is treated as a maximum values instead of
+ * minimum value.
+ */
+ private class Alternate implements java.util.Formattable {
+
+ /**
+ * The left side of the output.
+ */
+ private final String left;
+ /**
+ * The right side of the output.
+ */
+ private final String right;
+
+ /**
+ * Creates an alternate output.
+ *
+ * @param left the left side or null.
+ * @param right the right side or null.
+ */
+ Alternate(final String left, final String right) {
+ this.left = String.valueOf(left);
+ this.right = String.valueOf(right);
+ }
+
+ public void formatTo(java.util.Formatter formatter, int flags,
+ int width, int precision) {
+
+ String l = left;
+ String r = right;
+ if ((flags & java.util.FormattableFlags.UPPERCASE)
+ == java.util.FormattableFlags.UPPERCASE) {
+ l = l.toUpperCase(formatter.locale());
+ r = r.toUpperCase(formatter.locale());
+ }
+
+ if ((flags & java.util.FormattableFlags.ALTERNATE)
+ == java.util.FormattableFlags.ALTERNATE) {
+ l = toAlternate(l);
+ r = toAlternate(r);
+ }
+
+ if (width <= 0) {
+ width = Integer.MAX_VALUE;
+ }
+
+ int fence = Math.min(l.length(), width);
+ if (fence > (width >> 1)) {
+ fence = Math.max(fence - r.length(), fence >> 1);
+ }
+
+ if (fence > 0) {
+ if (fence > l.length()
+ && Character.isHighSurrogate(l.charAt(fence - 1))) {
+ --fence;
+ }
+ l = l.substring(0, fence);
+ }
+ r = r.substring(0, Math.min(width - fence, r.length()));
+
+ Object[] empty = Collections.emptySet().toArray();
+ formatter.format(l, empty);
+ if (l.length() != 0 && r.length() != 0) {
+ formatter.format("|", empty);
+ }
+ formatter.format(r, empty);
+ }
+ }
+}

diff -r bede51b8123d -r d6955daac275 mail/src/main/java/com/sun/mail/util/logging/LogManagerProperties.java
--- a/mail/src/main/java/com/sun/mail/util/logging/LogManagerProperties.java Fri Apr 04 15:41:36 2014 -0700
+++ b/mail/src/main/java/com/sun/mail/util/logging/LogManagerProperties.java Mon Apr 07 13:58:47 2014 -0700
@@ -1,8 +1,8 @@
 /*
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
- * Copyright (c) 2009-2013 Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2009-2013 Jason Mehrens. All rights reserved.
+ * Copyright (c) 2009-2014 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009-2014 Jason Mehrens. All rights reserved.
  *
  * The contents of this file are subject to the terms of either the GNU
  * General Public License Version 2 only ("GPL") or the Common Development
@@ -53,22 +53,22 @@
 
 /**
  * An adapter class to allow the Mail API to access the LogManager properties.
- * The LogManager properties are treated as the root of all properties.
- * First, the parent properties are searched. If no value is found, then,
- * the LogManager is searched with prefix value. If not found, then, just the
- * key itself is searched in the LogManager. If a value is found in the
- * LogManager it is then copied to this properties object with no key prefix.
- * If no value is found in the LogManager or the parent properties, then this
- * properties object is searched only by passing the key value.
+ * The LogManager properties are treated as the root of all properties. First,
+ * the parent properties are searched. If no value is found, then, the
+ * LogManager is searched with prefix value. If not found, then, just the key
+ * itself is searched in the LogManager. If a value is found in the LogManager
+ * it is then copied to this properties object with no key prefix. If no value
+ * is found in the LogManager or the parent properties, then this properties
+ * object is searched only by passing the key value.
  *
  * <p>
  * This class also emulates the LogManager functions for creating new objects
- * from string class names. This is to support initial setup of objects such as
+ * from string class names. This is to support initial setup of objects such as
  * log filters, formatters, error managers, etc.
  *
  * <p>
- * This class should never be exposed outside of this package. Keep this
- * class package private (default access).
+ * This class should never be exposed outside of this package. Keep this class
+ * package private (default access).
  *
  * @author Jason Mehrens
  * @since JavaMail 1.4.3
@@ -82,10 +82,18 @@
     /**
      * Caches the LogManager so we only read the config once.
      */
- private final static LogManager LOG_MANAGER = LogManager.getLogManager();
+ private static final LogManager LOG_MANAGER = LogManager.getLogManager();
+ /**
+ * Caches the read only reflection class names string array.
+ * Declared volatile for safe publishing only. The
+ * VO_VOLATILE_REFERENCE_TO_ARRAY warning is a false positive.
+ */
+ @SuppressWarnings("VolatileArrayField")
+ private static volatile String[] REFLECT_NAMES;
 
     /**
      * Gets the LogManger for the running JVM.
+ *
      * @return the LogManager.
      * @since JavaMail 1.4.5
      */
@@ -95,6 +103,7 @@
 
     /**
      * Converts a locale to a language tag.
+ *
      * @param locale the locale to convert.
      * @return the language tag.
      * @throws NullPointerException if the given locale is null.
@@ -125,6 +134,7 @@
 
     /**
      * Creates a new filter from the given class name.
+ *
      * @param name the fully qualified class name.
      * @return a new filter.
      * @throws ClassCastException if class name does not match the type.
@@ -145,6 +155,7 @@
 
     /**
      * Creates a new formatter from the given class name.
+ *
      * @param name the fully qualified class name.
      * @return a new formatter.
      * @throws ClassCastException if class name does not match the type.
@@ -165,6 +176,7 @@
 
     /**
      * Creates a new log record comparator from the given class name.
+ *
      * @param name the fully qualified class name.
      * @return a new comparator.
      * @throws ClassCastException if class name does not match the type.
@@ -203,7 +215,7 @@
     @SuppressWarnings("unchecked")
     static <T> Comparator<T> reverseOrder(final Comparator<T> c) {
         if (c == null) {
- throw new NullPointerException();
+ throw new NullPointerException();
         }
 
         Comparator<T> reverse = null;
@@ -237,6 +249,7 @@
 
     /**
      * Creates a new error manager from the given class name.
+ *
      * @param name the fully qualified class name.
      * @return a new error manager.
      * @throws ClassCastException if class name does not match the type.
@@ -257,6 +270,7 @@
 
     /**
      * Creates a new authenticator from the given class name.
+ *
      * @param name the fully qualified class name.
      * @return a new authenticator.
      * @throws ClassCastException if class name does not match the type.
@@ -276,7 +290,106 @@
     }
 
     /**
+ * Determines if the given class name identifies a utility class.
+ *
+ * @param name the fully qualified class name.
+ * @return true if the given class name
+ * @throws ClassNotFoundException if the class name was not found.
+ * @throws IllegalAccessException if the constructor is inaccessible.
+ * @throws LinkageError if the linkage fails.
+ * @throws ExceptionInInitializerError if the static initializer fails.
+ * @throws Exception to match the error method of the ErrorManager.
+ * @throws SecurityException if unable to inspect properties of class.
+ * @since JavaMail 1.5.2
+ */
+ static boolean isStaticUtilityClass(String name) throws Exception {
+ final Class<?> c = findClass(name);
+ final Class<?> obj = Object.class;
+ Method[] methods = c.getMethods();
+ boolean util;
+ if (c != obj && methods.length != 0) {
+ util = true;
+ for (Method m : methods) {
+ if (m.getDeclaringClass() != obj
+ && !Modifier.isStatic(m.getModifiers())) {
+ util = false;
+ break;
+ }
+ }
+ } else {
+ util = false;
+ }
+ return util;
+ }
+
+ /**
+ * Determines if the given class name is a reflection class name responsible
+ * for invoking methods and or constructors.
+ *
+ * @param name the fully qualified class name.
+ * @return true if the given class name
+ * @throws ClassNotFoundException if the class name was not found.
+ * @throws IllegalAccessException if the constructor is inaccessible.
+ * @throws LinkageError if the linkage fails.
+ * @throws ExceptionInInitializerError if the static initializer fails.
+ * @throws Exception to match the error method of the ErrorManager.
+ * @throws SecurityException if unable to inspect properties of class.
+ * @since JavaMail 1.5.2
+ */
+ static boolean isReflectionClass(String name) throws Exception {
+ String[] names = String[].class.cast(REFLECT_NAMES);
+ if (names == null) { //Benign data race.
+ names = reflectionClassNames();
+ REFLECT_NAMES = names;
+ }
+
+ for (String rf : names) { //The set of names is small.
+ if (name.equals(rf)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Determines all of the reflection class names used to invoke methods.
+ *
+ * This method performs indirect and direct calls on a throwable to
+ * capture the standard class names and the implementation class names.
+ * @return a string array containing the fully qualified class names.
+ * @throws Exception if there is a problem.
+ */
+ private static String[] reflectionClassNames() throws Exception {
+ final Class<?> thisClass = LogManagerProperties.class;
+ assert Modifier.isFinal(thisClass.getModifiers()) : thisClass;
+ try {
+ final HashSet<String> traces = new HashSet<String>();
+ Throwable t = Throwable.class.getConstructor().newInstance();
+ for (StackTraceElement ste : t.getStackTrace()) {
+ if (!thisClass.getName().equals(ste.getClassName())) {
+ traces.add(ste.getClassName());
+ } else {
+ break;
+ }
+ }
+
+ Throwable.class.getMethod("fillInStackTrace").invoke(t);
+ for (StackTraceElement ste : t.getStackTrace()) {
+ if (!thisClass.getName().equals(ste.getClassName())) {
+ traces.add(ste.getClassName());
+ } else {
+ break;
+ }
+ }
+ return traces.toArray(new String[traces.size()]);
+ } catch (final InvocationTargetException ITE) {
+ throw paramOrError(ITE);
+ }
+ }
+
+ /**
      * Creates a new object from the given class name.
+ *
      * @param <T> The generic class type.
      * @param name the fully qualified class name.
      * @param type the assignable type for the given name.
@@ -322,6 +435,7 @@
 
     /**
      * Returns the given exception or throw
[truncated due to length]