commits@javamail.java.net

[javamail~mercurial:296] Update copyright exclude list; have to include logging files until copyri

From: <shannon_at_kenai.com>
Date: Mon, 27 Sep 2010 18:47:40 +0000

Project: javamail
Repository: mercurial
Revision: 296
Author: shannon
Date: 2010-09-23 21:35:00 UTC
Link:

Log Message:
------------
IMAPMessage getFrom and getReplyTo use same fallback strategy as MimeMessage.
Declare msg as MimeMessage so that setText(text, charset) could be used.
Updates from Jason Mehrens:

LogManagerProperties changed writeReplace behavior.
LogManagerProperties added clone method.
LogManagerProperties override setProperty and getProperty(String, String)
MailHandler add support to verify mail settings.
MailHandler apply fixes for 6933217.
MailHandler added equals/hashCode methods to TailNameFormatter.
MailHandler cache session to avoid extra lookups fix for 6228391.
MailHandler update push to mean high importance and urgent delivery.
MailHandler update push to use standard RFC 4021 headers
MailHandler moved envelope code outside of lock.
MailHandler use URLConnection.guessContentTypeXXX for body content type.
MailHandler add javadocs about line breaks in subject and attachment filenames.
MailHandler fixed empty subject bug.
MailHandler clear buffer on java.lang.Error.
MailHandlerDemo added method to perform LogManager check.
SummaryNameFormatter forbid ISO control chars in subject or part name.
MailHandlerTest test publish during close.
MailHandlerTest more capacity tests.
MailHandlerTest test TailNameFormatter.
MailHandlerTest check for different subject types.
MailHandlerTest check flush/push with capacity of one.
MailHandlerTest added test for java.lang.Error.
MailHandlerTest LogManager.reset test.
Update copyright exclude list; have to include logging files until copyright
checker is updated to handle files with two copyrights.
Also exclude test data files with unknown comment format.


Revisions:
----------
293
294
295
296


Modified Paths:
---------------
mail/src/main/java/com/sun/mail/imap/IMAPMessage.java
demo/src/main/java/msgsendsample.java
logging/src/main/java/FileErrorManager.java
logging/src/main/java/MailHandlerDemo.java
logging/src/main/java/README.txt
logging/src/main/java/SummaryFormatter.java
logging/src/main/java/SummaryNameFormatter.java
logging/src/main/java/maildemo.properties
mail/src/main/java/com/sun/mail/util/logging/LogManagerProperties.java
mail/src/main/java/com/sun/mail/util/logging/MailHandler.java
mail/src/main/java/com/sun/mail/util/logging/package.html
mail/src/test/java/com/sun/mail/util/logging/MailHandlerTest.java
copyright-exclude


Diffs:
------
diff -r fc7a360ef6be -r 46ee8836280f mail/src/main/java/com/sun/mail/imap/IMAPMessage.java
--- a/mail/src/main/java/com/sun/mail/imap/IMAPMessage.java Tue Sep 07 20:39:00 2010 -0700
+++ b/mail/src/main/java/com/sun/mail/imap/IMAPMessage.java Wed Sep 15 13:31:45 2010 -0700
@@ -235,7 +235,18 @@
     public Address[] getFrom() throws MessagingException {
         checkExpunged();
         loadEnvelope();
- return aaclone(envelope.from);
+ InternetAddress[] a = envelope.from;
+ /*
+ * Per RFC 2822, the From header is required, and thus the IMAP
+ * spec also requires that it be present, but we know that in
+ * practice it is often missing. Some servers fill in the
+ * From field with the Sender field in this case, but at least
+ * Exchange 2007 does not. Use the same fallback strategy used
+ * by MimeMessage.
+ */
+ if (a == null || a.length == 0)
+ a = envelope.sender;
+ return aaclone(a);
     }
 
     public void setFrom(Address address) throws MessagingException {
@@ -297,6 +308,14 @@
     public Address[] getReplyTo() throws MessagingException {
         checkExpunged();
         loadEnvelope();
+ /*
+ * The IMAP spec requires that the Reply-To field never be
+ * null, but at least Exchange 2007 fails to fill it in in
+ * some cases. Use the same fallback strategy used by
+ * MimeMessage.
+ */
+ if (envelope.replyTo == null || envelope.replyTo.length == 0)
+ return getFrom();
         return aaclone(envelope.replyTo);
     }
 


diff -r 46ee8836280f -r 7ded56a2c295 demo/src/main/java/msgsendsample.java
--- a/demo/src/main/java/msgsendsample.java Wed Sep 15 13:31:45 2010 -0700
+++ b/demo/src/main/java/msgsendsample.java Thu Sep 23 13:18:49 2010 -0700
@@ -73,7 +73,7 @@
         
         try {
             // create a message
- Message msg = new MimeMessage(session);
+ MimeMessage msg = new MimeMessage(session);
             msg.setFrom(new InternetAddress(from));
             InternetAddress[] address = {new InternetAddress(args[0])};
             msg.setRecipients(Message.RecipientType.TO, address);


diff -r 7ded56a2c295 -r 1cb789e6c6be logging/src/main/java/FileErrorManager.java
--- a/logging/src/main/java/FileErrorManager.java Thu Sep 23 13:18:49 2010 -0700
+++ b/logging/src/main/java/FileErrorManager.java Thu Sep 23 13:56:56 2010 -0700
@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 2009-2010 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009-2010 Jason Mehrens. All Rights Reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -192,7 +193,6 @@
             ps.flush();
             tmp = null; //Don't delete 'tmp' if all bytes were written.
             ps.close();
-
         } finally {
             close(out);
             delete(tmp); //Only deletes if not null.
@@ -217,7 +217,8 @@
                         tmp.deleteOnExit();
                     } catch (final RuntimeException shutdown) {
                         if (!tmp.delete()) {
- internal.error(tmp.getAbsolutePath(), shutdown, ErrorManager.CLOSE_FAILURE);
+ internal.error(tmp.getAbsolutePath(), shutdown,
+ ErrorManager.CLOSE_FAILURE);
                         }
                     }
                 }

diff -r 7ded56a2c295 -r 1cb789e6c6be logging/src/main/java/MailHandlerDemo.java
--- a/logging/src/main/java/MailHandlerDemo.java Thu Sep 23 13:18:49 2010 -0700
+++ b/logging/src/main/java/MailHandlerDemo.java Thu Sep 23 13:56:56 2010 -0700
@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 2009-2010 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009-2010 Jason Mehrens. All Rights Reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -32,7 +33,10 @@
 import com.sun.mail.util.logging.MailHandler;
 import java.util.logging.*;
 import javax.mail.*;
+import javax.mail.internet.InternetAddress;
+import java.util.Arrays;
 import java.util.Comparator;
+import java.util.Properties;
 import java.io.*;
 
 /**
@@ -74,6 +78,77 @@
     }
 
     /**
+ * Used debug problems with the logging.properties.
+ * @param prefix a string to prefix the output.
+ * @param err any PrintStream or null for System.out.
+ */
+ private static void checkConfig(String prefix, PrintStream err) {
+ if (prefix == null || prefix.trim().length() == 0) {
+ prefix = "DEBUG";
+ }
+
+ if (err == null) {
+ err = System.out;
+ }
+
+ try {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ err.println(prefix + ": SecurityManager.class=" + sm.getClass().getName());
+ err.println(prefix + ": SecurityManager.toString=" + sm);
+ } else {
+ err.println(prefix + ": SecurityManager.class=" + null);
+ err.println(prefix + ": SecurityManager.toString=" + null);
+ }
+
+ LogManager manager = LogManager.getLogManager();
+ String key = "java.util.logging.config.file";
+ String cfg = System.getProperty(key);
+ if (cfg != null) {
+ err.println(prefix + ": " + cfg);
+ File f = new File(cfg);
+ err.println(prefix + ": AbsolutePath=" + f.getAbsolutePath());
+ err.println(prefix + ": CanonicalPath=" + f.getCanonicalPath());
+ err.println(prefix + ": length=" + f.length());
+ err.println(prefix + ": canRead=" + f.canRead());
+ err.println(prefix + ": lastModified="
+ + new java.util.Date(f.lastModified()));
+ //force any errors, only safe is key is present.
+ manager.readConfiguration();
+ } else {
+ err.println(prefix + ": " + key + " is not set as a system property.");
+ }
+ err.println(prefix + ": LogManager.class=" + manager.getClass().getName());
+ err.println(prefix + ": LogManager.toString=" + manager);
+
+ final String p = MailHandler.class.getName();
+ key = p.concat(".mail.to");
+ String to = manager.getProperty(key);
+ err.println(prefix + ": TO=" + to);
+ err.println(prefix + ": TO="
+ + Arrays.toString(InternetAddress.parse(to, false)));
+ err.println(prefix + ": TO="
+ + Arrays.toString(InternetAddress.parse(to, true)));
+
+ key = p.concat(".mail.from");
+ String from = manager.getProperty(key);
+ if (from == null || from.length() == 0) {
+ Session session = Session.getInstance(new Properties());
+ InternetAddress local = InternetAddress.getLocalAddress(session);
+ err.println(prefix + ": FROM=" + local);
+ } else {
+ err.println(prefix + ": FROM="
+ + Arrays.toString(InternetAddress.parse(from, false)));
+ err.println(prefix + ": FROM="
+ + Arrays.toString(InternetAddress.parse(from, true)));
+ }
+ } catch (Throwable error) {
+ err.print(prefix + ": ");
+ error.printStackTrace(err);
+ }
+ }
+
+ /**
      * Example for body only messages.
      * On close the remaining messages are sent.
      */
@@ -206,10 +281,10 @@
         //extract simple name, replace the rest with formatters.
         h.setAttachmentNames(new Formatter[]{h.getAttachmentNames()[0],
                     new SummaryNameFormatter("{0} records and {1} errors"),
- new SummaryNameFormatter("{0,choice,0#no records|1#1 record|" +
- "1<{0,number,integer} records} and " +
- "{1,choice,0#no errors|1#1 error|1<" +
- "{1,number,integer} errors}")});
+ new SummaryNameFormatter("{0,choice,0#no records|1#1 record|"
+ + "1<{0,number,integer} records} and "
+ + "{1,choice,0#no errors|1#1 error|1<"
+ + "{1,number,integer} errors}")});
 
         LOGGER.addHandler(h);
     }
@@ -218,6 +293,11 @@
      * Sets up the demos that will run.
      */
     private static void init() {
+ Session session = Session.getInstance(System.getProperties());
+ if (session.getDebug()) {
+ checkConfig(CLASS_NAME, session.getDebugOut());
+ }
+
         initBodyOnly();
         initLowCapacity();
         initSimpleAttachment();
@@ -253,8 +333,10 @@
     }
 
     private static void fallbackSettings(Handler h) {
- h.setErrorManager(new FileErrorManager());
- h.setLevel(Level.ALL);
+ if (h != null) {
+ h.setErrorManager(new FileErrorManager());
+ h.setLevel(Level.ALL);
+ }
     }
 
     private static String getTempDir() {
@@ -273,7 +355,7 @@
 
         private final boolean complement;
 
- MessageErrorsFilter(boolean complement) {
+ MessageErrorsFilter(final boolean complement) {
             this.complement = complement;
         }
 

diff -r 7ded56a2c295 -r 1cb789e6c6be logging/src/main/java/README.txt
--- a/logging/src/main/java/README.txt Thu Sep 23 13:18:49 2010 -0700
+++ b/logging/src/main/java/README.txt Thu Sep 23 13:56:56 2010 -0700
@@ -35,7 +35,7 @@
 
     6. Run the demo. For example:
 
- java -Djava.util.logging.config.file=/u/me/download/javamail/demo/maildemo.properties MailHandlerDemo
+ java -Dmail.debug=false -Djava.util.logging.config.file=/u/me/download/javamail/demo/maildemo.properties MailHandlerDemo
 
 
 

diff -r 7ded56a2c295 -r 1cb789e6c6be logging/src/main/java/SummaryFormatter.java
--- a/logging/src/main/java/SummaryFormatter.java Thu Sep 23 13:18:49 2010 -0700
+++ b/logging/src/main/java/SummaryFormatter.java Thu Sep 23 13:56:56 2010 -0700
@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 2009-2010 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009-2010 Jason Mehrens. All Rights Reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions

diff -r 7ded56a2c295 -r 1cb789e6c6be logging/src/main/java/SummaryNameFormatter.java
--- a/logging/src/main/java/SummaryNameFormatter.java Thu Sep 23 13:18:49 2010 -0700
+++ b/logging/src/main/java/SummaryNameFormatter.java Thu Sep 23 13:56:56 2010 -0700
@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 2009-2010 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009-2010 Jason Mehrens. All Rights Reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -57,19 +58,23 @@
      * Creates formatter using a message format style pattern.
      * @param pattern the pattern.
      * @throws NullPointerException if pattern is null.
+ * @throws IllegalArgumentException if pattern contains an ISO control
+ * character.
      */
     public SummaryNameFormatter(final String pattern) {
- if (pattern == null) {
- throw new NullPointerException();
+ for (int i=0; i<pattern.length(); i++) {
+ if (Character.isISOControl(pattern.charAt(i))) {
+ throw new IllegalArgumentException("At index "+ i);
+ }
         }
         this.pattern = pattern;
     }
 
     public synchronized String format(LogRecord r) {
- count++;
         if (r.getThrown() != null) {
             errors++;
         }
+ count++;
         return "";
     }
 
@@ -90,15 +95,15 @@
     }
 
     private String extFrom(Handler h) {
- if(h instanceof MailHandler) {
+ if (h instanceof MailHandler) {
             MailHandler mh = (MailHandler)h;
- if(mh.getSubject() != this) {
+ if (mh.getSubject() != this) {
                 Formatter[] content = mh.getAttachmentFormatters();
                 Formatter[] names = mh.getAttachmentNames();
                 assert content.length == names.length;
- for(int i=0; i<content.length; i++) {
- if(names[i] == this) {
- if(content[i] instanceof XMLFormatter) {
+ for (int i=0; i<content.length; i++) {
+ if (names[i] == this) {
+ if (content[i] instanceof XMLFormatter) {
                             return ".xml";
                         }
                         break;

diff -r 7ded56a2c295 -r 1cb789e6c6be logging/src/main/java/maildemo.properties
--- a/logging/src/main/java/maildemo.properties Thu Sep 23 13:18:49 2010 -0700
+++ b/logging/src/main/java/maildemo.properties Thu Sep 23 13:56:56 2010 -0700
@@ -1,43 +1,3 @@
-#
-# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
-#
-# Copyright (c) 2010 Oracle and/or its affiliates. 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.
-#
-
 # This can be used by setting the system property
 # -Djava.util.logging.config.file=path to this file
 
@@ -56,6 +16,7 @@
 com.sun.mail.util.logging.MailHandler.mail.host = my-mail-server
 com.sun.mail.util.logging.MailHandler.mail.from = me_at_example.com
 com.sun.mail.util.logging.MailHandler.mail.to = me_at_example.com
+com.sun.mail.util.logging.MailHandler.verify = local
 
 # Add attachments if needed.
 #com.sun.mail.util.logging.MailHandler.attachment.formatters = java.util.logging.SimpleFormatter, java.util.logging.XMLFormatter

diff -r 7ded56a2c295 -r 1cb789e6c6be mail/src/main/java/com/sun/mail/util/logging/LogManagerProperties.java
--- a/mail/src/main/java/com/sun/mail/util/logging/LogManagerProperties.java Thu Sep 23 13:18:49 2010 -0700
+++ b/mail/src/main/java/com/sun/mail/util/logging/LogManagerProperties.java Thu Sep 23 13:56:56 2010 -0700
@@ -2,6 +2,7 @@
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
  * Copyright (c) 2009-2010 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009-2010 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
@@ -44,14 +45,17 @@
 import java.security.*;
 import java.util.Enumeration;
 import java.util.Properties;
-import java.util.logging.LogManager;
+import java.util.logging.*;
 
 /**
  * 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 local properties and the parent properties are searched.
- * If no value is found, then, the LogManager is searched with prefix value
- * and finally, just the key itself is searched in the LogManager.
+ * 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
@@ -68,22 +72,21 @@
 final class LogManagerProperties extends Properties {
 
     private static final long serialVersionUID = -2239983349056806252L;
-
     /**
      * Caches the LogManager so we only read the config once.
      */
     final static LogManager manager = LogManager.getLogManager();
 
     /**
- * This code is modifed from the LogManager, which explictly states
+ * This code is modified from the LogManager, which explictly states
      * searching the system class loader first, then the context class loader.
- * There is resistance (compatiblity) to change this behavior to simply
+ * There is resistance (compatibility) to change this behavior to simply
      * searching the context class loader.
      * @param name full class name
      * @return the class.
      * @throws ClassNotFoundException if not found.
      */
- static final Class findClass(String name) throws ClassNotFoundException {
+ static Class findClass(String name) throws ClassNotFoundException {
         ClassLoader[] loaders = getClassLoaders();
         Class clazz;
         if (loaders[0] != null) {
@@ -126,21 +129,37 @@
             }
         });
     }
-
     private final String prefix;
 
     /**
      * Creates a log manager properties object.
      * @param parent the parent properties.
      * @param prefix the namespace prefix.
- * @throws NullPointerException if <tt>prefix</tt> is <tt>null</tt>.
+ * @throws NullPointerException if <tt>prefix</tt> or <tt>parent</tt> is
+ * <tt>null</tt>.
      */
     LogManagerProperties(final Properties parent, final String prefix) {
         super(parent);
- if(prefix == null) {
+ parent.isEmpty(); //null check, happens-before
+ if (prefix == null) {
             throw new NullPointerException();
         }
         this.prefix = prefix;
+
+ //'defaults' is not decalared final.
+ super.isEmpty(); //happens-before.
+ }
+
+ /**
+ * Clones the default properties.
+ * @return the clone.
+ */
+ public Object clone() {
+ Properties parent;
+ synchronized (this) {
+ parent = defaults;
+ }
+ return parent.clone();
     }
 
     /**
@@ -149,18 +168,45 @@
      * @param key a non null key.
      * @return the value for that key.
      */
- public String getProperty(String key) {
- String value = super.getProperty(key);
+ public synchronized String getProperty(String key) {
+ String value = defaults.getProperty(key);
         if (value == null && key.length() > 0) {
             value = manager.getProperty(prefix + '.' + key);
             if (value == null) {
                 value = manager.getProperty(key);
             }
+
+ /**
+ * Copy the log manager properties as we read them. If a value is
+ * no longer present in the LogManager read it from here.
+ * The reason this works is because LogManager.reset() closes
+ * all attached handlers therefore, stale values only exist in
+ * closed handlers.
+ */
+ if (value != null) {
+ put(key, value);
+ } else {
+ Object v = get(key); //Call 'get' so defaults are not used.
+ value = v instanceof String ? (String) v : null;
+ }
         }
         return value;
     }
 
     /**
+ * Calls getProperty directly. If getProperty returns null the default
+ * value is returned.
+ * @param key a key to search for.
+ * @param def the default value to use if not found.
+ * @return the value for the key.
+ * @since JavaMail 1.4.4
+ */
+ public String getProperty(String key, String def) {
+ final String value = this.getProperty(key);
+ return value == null ? def : value;
+ }
+
+ /**
      * It is assumed that this method will never be called.
      * No way to get the property names from LogManager.
      * @return the property names
@@ -186,7 +232,7 @@
         if (o instanceof Properties == false) {
             return false;
         }
- assert false;
+ assert false : prefix;
         return super.equals(o);
     }
 
@@ -195,21 +241,17 @@
      * @return the hash code.
      */
     public int hashCode() {
- assert false;
+ assert false : prefix.hashCode();
         return super.hashCode();
     }
 
     /**
      * It is assumed that this method will never be called.
- * @return a new properties object copied from this one.
+ * @return the parent properties.
      * @throws ObjectStreamException if there is a problem.
      */
     private synchronized Object writeReplace() throws ObjectStreamException {
         assert false;
- final Properties out = new Properties(defaults);
- if (!super.isEmpty()) { //should always be empty.
- out.putAll(this);
- }
- return out;
+ return new Properties(defaults);
     }
 }

diff -r 7ded56a2c295 -r 1cb789e6c6be mail/src/main/java/com/sun/mail/util/logging/MailHandler.java
--- a/mail/src/main/java/com/sun/mail/util/logging/MailHandler.java Thu Sep 23 13:18:49 2010 -0700
+++ b/mail/src/main/java/com/sun/mail/util/logging/MailHandler.java Thu Sep 23 13:56:56 2010 -0700
@@ -2,6 +2,7 @@
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  *
  * Copyright (c) 2009-2010 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009-2010 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
@@ -42,9 +43,9 @@
 
 import java.io.*;
 import java.lang.reflect.*;
+import java.net.URLConnection;
 import java.util.Arrays;
 import java.util.Comparator;
-import java.util.Locale;
 import java.util.Properties;
 import java.util.logging.*;
 import javax.activation.*;
@@ -81,6 +82,7 @@
  * com.sun.mail.util.logging.MailHandler.mail.smtp.host = my-mail-server
  * com.sun.mail.util.logging.MailHandler.mail.to = me_at_example.com
  * com.sun.mail.util.logging.MailHandler.level = WARNING
+ * com.sun.mail.util.logging.MailHandler.verify = local
  * </pre></tt>
  *
  * All mail properties documented in the <tt>Java Mail API</tt> cascade to the
@@ -103,7 +105,7 @@
  *
  * <li>com.sun.mail.util.logging.MailHandler.attachment.names a comma separated
  * list of names or <tt>Formatter</tt> class names of each attachment.
- * (default is no attachments names)
+ * (default is no attachments names)
  *
  * <li>com.sun.mail.util.logging.MailHandler.authenticator name of a
  * {_at_linkplain javax.mail.Authenticator} class used to provide login credentials
@@ -135,9 +137,9 @@
  * class used for the body of the message. (defaults to <tt>null</tt>,
  * allow all records)
  *
- * <li>com.sun.mail.util.logging.MailHandler.formatter name of <tt>Formatter</tt>
- * class used to format the body of this message. (defaults to
- * <tt>SimpleFormatter</tt>)
+ * <li>com.sun.mail.util.logging.MailHandler.formatter name of a
+ * <tt>Formatter</tt> class used to format the body of this message.
+ * (defaults to <tt>java.util.logging.SimpleFormatter</tt>)
  *
  * <li>com.sun.mail.util.logging.MailHandler.level specifies the default level
  * for this <tt>Handler</tt> (defaults to <tt>Level.WARNING</tt>).
@@ -175,7 +177,7 @@
  * identifying sender of the email; never equal to the from address. Typically,
  * this is set to the email address identifying the application itself.
  * (defaults to <tt>null</tt>, none)
- *
+ *
  * <li>com.sun.mail.util.logging.MailHandler.pushLevel the level which will
  * trigger an early push. (defaults to <tt>Level.OFF</tt>, only push when full)
  *
@@ -186,7 +188,15 @@
  * <li>com.sun.mail.util.logging.MailHandler.subject the name of a
  * <tt>Formatter</tt> class or string literal used to create the subject line.
  * (defaults to empty string)
- * </ul>
+ *
+ * <li>com.sun.mail.util.logging.MailHandler.verify <a name="verify">used</a> to
+ * verify all of the <tt>Handler</tt> properties prior to a push. If set to a value of
+ * <tt>local</tt> the <tt>Handler</tt> will only verify settings of the local
+ * machine. If set to a value of <tt>remote</tt>, the <tt>Handler</tt> will
+ * verify all local settings and try to establish a connection with the email
+ * server. If the value is not set, equal to an empty string, or equal to the
+ * literal <tt>null</tt> then minimal or no settings are verified prior to a
+ * push. (defaults to <tt>null</tt>, no verify).
  *
  * <p>
  * <b>Sorting:</b>
@@ -223,11 +233,10 @@
  * <p>
  * <b>Push Level and Push Filter:</b>
  * The push method, push level, and optional push filter can be used to
- * conditionally trigger a push for log messages that require urgent delivery to
- * all recipents. When a push occurs, the current buffer is formatted into an
- * email and is sent to the email server. If the push method, push level, or
- * push filter trigger a push then the outgoing email is flagged as high
- * priority.
+ * conditionally trigger a push at or prior to full capacity. When a push
+ * occurs, the current buffer is formatted into an email and is sent to the
+ * email server. If the push method, push level, or push filter trigger a push
+ * then the outgoing email is flagged as high importance with urgent priority.
  *
  * <p>
  * <b>Buffering:</b>
@@ -270,11 +279,6 @@
 public class MailHandler extends Handler {
 
     /**
- * The initial length the data array.
- * The data length can be as small as one.
- */
- private static final int INITIAL_DATA_LENGTH = 6;
- /**
      * Cache the off value.
      */
     private static final int offValue = Level.OFF.intValue();
@@ -296,6 +300,12 @@
      */
     private Authenticator auth;
     /**
+ * Holds the session object used to generate emails.
+ * Sessions can be shared by multiple threads.
+ * See BUGID 6228391
+ */
+ private Session session;
+ /**
      * Holds all of the log records that will be used to create the email.
      */
     private LogRecord[] data;
@@ -376,7 +386,7 @@
     public MailHandler(final int capacity) {
         init();
         sealed = true;
- setCapacity(capacity);
+ setCapacity0(capacity);
     }
 
     /**
@@ -392,7 +402,7 @@
     public MailHandler(final Properties props) {
         init();
         sealed = true;
- setMailProperties(props);
+ setMailProperties0(props);
     }
 
     /**
@@ -413,8 +423,8 @@
             return false;
         }
 
- Filter filter = getFilter();
- if (filter == null || filter.isLoggable(record)) {
+ Filter body = getFilter();
+ if (body == null || body.isLoggable(record)) {
             return true;
         }
 
@@ -443,28 +453,33 @@
          * See close().
          */
         if (isLoggable(record)) {
- record.getSourceMethodName(); //infer caller
- Message msg = null;
+ record.getSourceMethodName(); //infer caller, outside of lock
+ MessageContext ctx;
+ boolean priority;
             synchronized (this) {
- data[size++] = record;
- final boolean priority = isPushable(record);
+ data[size] = record;
+ ++size; //be nice to client compiler.
+ priority = isPushable(record);
                 if (priority || size >= capacity) {
- msg = writeLogRecords(priority, ErrorManager.WRITE_FAILURE);
+ ctx = writeLogRecords(ErrorManager.WRITE_FAILURE);
                 } else {
+ ctx = null;
                     if (data.length == size) {
                         grow();
                     }
- return;
                 }
             }
- send(msg, ErrorManager.WRITE_FAILURE);
+
+ if (ctx != null) {
+ send(ctx, priority, ErrorManager.WRITE_FAILURE);
+ }
         }
     }
 
     /**
- * Pushes any buffered records to the email server as high priority.
- * The internal buffer is then cleared. Does nothing if called from inside
- * a push.
+ * Pushes any buffered records to the email server as high importance with
+ * urgent priority. The internal buffer is then cleared. Does nothing if
+ * called from inside a push.
      * @see #flush()
      */
     public void push() {
@@ -486,29 +501,41 @@
      * Pushes any buffered records to the email server as normal priority.
      * The internal buffer is then cleared. Once this handler is closed it
      * will remain closed.
+ * <p>
+ * If this <tt>Handler</tt> is only implicitly closed by the
+ * <tt>LogManager</tt>, then <a href="#verify">verification</a> should be
+ * turned on.
      * @throws SecurityException if a security manager exists and the
      * caller does not have <tt>LoggingPermission("control")</tt>.
      * @see #flush()
      */
     public void close() {
- Message msg = null;
+ MessageContext ctx = null;
         synchronized (this) {
             super.setLevel(Level.OFF); //security check first.
- msg = writeLogRecords(false, ErrorManager.CLOSE_FAILURE);
- /**
- * The sign bit of the capacity is set to ensure that records that
- * have passed isLoggable, but have yet to be added to the internal
- * buffer, are immediately pushed as an email.
- */
- if (this.capacity > 0) {
- this.capacity = -this.capacity;
- if (size == 0) { //ensure not inside a push.
+ try {
+ if (size > 0) {
+ ctx = writeLogRecords(ErrorManager.CLOSE_FAILURE);
+ }
+ } finally {
+ /**
+ * The sign bit of the capacity is set to ensure that records
+ * that have passed isLoggable, but have yet to be added to the
+ * internal buffer, are immediately pushed as an email.
+ */
+ if (this.capacity > 0) {
+ this.capacity = -this.capacity;
+ }
+
+ if (size == 0 && data.length != 1) { //ensure not inside a push.
                     this.data = new LogRecord[1];
                 }
             }
- assert this.capacity < 0;
         }
- send(msg, ErrorManager.CLOSE_FAILURE);
+
+ if (ctx != null) {
+ send(ctx, false, ErrorManager.CLOSE_FAILURE);
+ }
     }
 
     /**
@@ -516,7 +543,8 @@
      * logged by this <tt>Handler</tt>. Message levels lower than this
      * value will be discarded.
      * @param newLevel the new value for the log level
- * @throws SecurityException if a security manager exists and
+ * @throws NullPointerException if <tt>newLevel</tt> is <tt>null</tt>.
+ * @throws SecurityException if a security manager exists and
      * the caller does not have <tt>LoggingPermission("control")</tt>.
      */
     public synchronized void setLevel(final Level newLevel) {
@@ -545,7 +573,7 @@
      * the push level triggers a send, the resulting email is flagged as
      * high priority.
      * @param level Level object.
- * @throws NullPointerException if <tt>level</tt> is <tt>null</tt>
+ * @throws NullPointerException if <tt>level</tt> is <tt>null</tt>.
      * @throws SecurityException if a security manager exists and the
      * caller does not have <tt>LoggingPermission("control")</tt>.
      * @throws IllegalStateException if called from inside a push.
@@ -650,6 +678,7 @@
             throw new IllegalStateException();
         }
         this.auth = auth;
+ this.fixUpSession();
     }
 
     /**
@@ -664,6 +693,10 @@
      * @throws IllegalStateException if called from inside a push.
      */
     public final void setMailProperties(Properties props) {
+ this.setMailProperties0(props);
+ }
+
+ private void setMailProperties0(Properties props) {
         checkAccess();
         props = (Properties) props.clone();
         synchronized (this) {
@@ -671,6 +704,7 @@
                 throw new IllegalStateException();
             }
             this.mailProps = props;
+ this.fixUpSession();
         }
     }
 
@@ -754,7 +788,7 @@
     public final void setAttachmentFormatters(Formatter[] formatters) {
         checkAccess();
         formatters = (Formatter[]) formatters.clone();
- for (int i = 0; i < formatters.length; i++) {
+ for (int i = 0; i < formatters.length; ++i) {
             if (formatters[i] == null) {
                 throw new NullPointerException(atIndexMsg(i));
             }
@@ -787,8 +821,9 @@
     }
 
     /**
- * Sets the attachment file name for each attachment. This method will
- * create a set of custom formatters.
+ * Sets the attachment file name for each attachment. The caller must
+ * ensure that the attachment file name does not contain any line breaks.
+ * This method will create a set of custom formatters.
      * @param names an array of names.
      * @throws SecurityException if a security manager exists and the
      * caller does not have <tt>LoggingPermission("control")</tt>.
@@ -797,12 +832,13 @@
      * @throws IllegalArgumentException if any name is empty.
      * @throws NullPointerException if any given array or name is <tt>null</tt>.
      * @throws IllegalStateException if called from inside a push.
+ * @see Character#isISOControl(char)
      */
     public final void setAttachmentNames(String[] names) {
         checkAccess();
 
         Formatter[] formatters = new Formatter[names.length];
- for (int i = 0; i < names.length; i++) {
+ for (int i = 0; i < names.length; ++i) {
             final String name = names[i];
             if (name != null) {
                 if (name.length() > 0) {
@@ -830,11 +866,14 @@
     /**
      * Sets the attachment file name formatters. The format method of each
      * attachment formatter will see only the <tt>LogRecord</tt> objects that
- * passed its attachment filter during formatting. The format method should
- * always return the empty string. Instead of being used to format records,
- * it is used to gather information about the contents of an attachment.
- * The <tt>getTail</tt> method should be used to construct the attachment
- * file name and reset any formatter collected state.
+ * passed its attachment filter during formatting. The format method will
+ * typically return an empty string. Instead of being used to format
+ * records, it is used to gather information about the contents of an
+ * attachment. The <tt>getTail</tt> method should be used to construct the
+ * attachment file name and reset any formatter collected state. The
+ * formatter must ensure that the attachment file name does not contain any
+ * line breaks. The <tt>toString</tt> method of the given formatter should
+ * be overridden to provide a useful attachment file name, if possible.
      * @param formatters and array of attachment name formatters.
      * @throws SecurityException if a security manager exists and the
      * caller does not have <tt>LoggingPermission("control")</tt>.
@@ -842,12 +881,13 @@
      * name formatters do not match the number of attachment formatters.
      * @throws NullPointerException if any given array or name is <tt>null</tt>.
      * @throws IllegalStateException if called from inside a push.
+ * @see Character#isISOControl(char)
      */
     public final void setAttachmentNames(Formatter[] formatters) {
         checkAccess();
 
         formatters = (Formatter[]) formatters.clone();
- for (int i = 0; i < formatters.length; i++) {
+ for (int i = 0; i < formatters.length; ++i) {
             if (formatters[i] == null) {
                 throw new NullPointerException(atIndexMsg(i));
             }
@@ -877,17 +917,20 @@
     }
 
     /**
- * Sets a literal string for the email subject.
+ * Sets a literal string for the email subject. The caller must ensure that
+ * the subject line does not contain any line breaks.
      * @param subject a non <tt>null</tt> string.
      * @throws SecurityException if a security manager exists and the
      * caller does not have <tt>LoggingPermission("control")</tt>.
      * @throws NullPointerException if <tt>subject</tt> is <tt>null</tt>.
      * @throws IllegalStateException if called from inside a push.
+ * @see Character#isISOControl(char)
      */
     public final void setSubject(final String subject) {
         if (subject != null) {
             this.setSubject(new TailNameFormatter(subject));
         } else {
+ checkAccess();
             throw new NullPointerException();
         }
     }
@@ -895,28 +938,33 @@
     /**
      * Sets the subject formatter for email. The format method of the subject
      * formatter will see all <tt>LogRecord</tt> objects that were published to
- * this <tt>Handler</tt> during formatting and should always return the empty
- * string. This formatter is used to gather information to create a summary
- * about what information is contained in the email. The <tt>getTail</tt>
- * method should be used to construct the subject and reset any
- * formatter collected state. The <tt>toString</tt> method of the given
- * formatter should be overridden to provide a useful subject, if possible.
+ * this <tt>Handler</tt> during formatting and will typically return an
+ * empty string. This formatter is used to gather information to create a
+ * summary about what information is contained in the email. The
+ * <tt>getTail</tt> method should be used to construct the subject and reset
+ * any formatter collected state. The formatter must ensure that the
+ * subject line does not contain any line breaks. The <tt>toString</tt>
+ * method of the given formatter should be overridden to provide a useful
+ * subject, if possible.
      * @param format the subject formatter.
      * @throws SecurityException if a security manager exists and the
      * caller does not have <tt>LoggingPermission("control")</tt>.
      * @throws NullPointerException if <tt>format</tt> is <tt>null</tt>.
      * @throws IllegalStateException if called from inside a push.
+ * @see Character#isISOControl(char)
      */
- public final synchronized void setSubject(final Formatter format) {
+ public final void setSubject(final Formatter format) {
         checkAccess();
         if (format == null) {
             throw new NullPointerException();
         }
 
- if (isWriting) {
- throw new IllegalStateException();
+ synchronized (this) {
+ if (isWriting) {
+ throw new IllegalStateException();
+ }
+ this.subjectFormatter = format;
         }
- this.subjectFormatter = format;
     }
 
     /**
@@ -937,6 +985,9 @@
         }
     }
 
+ /**
+ * Calls log manager checkAccess if this is sealed.
+ */
     final void checkAccess() {
         if (sealed) {
             LogManagerProperties.manager.checkAccess();
@@ -946,17 +997,21 @@
     /**
      * Determines the mimeType of a formatter from the getHead call.
      * This could be made protected, or a new class could be created to do
- * this type of conversion. Currently, this is only used for the body.
+ * this type of conversion. Currently, this is only used for the body
+ * since the attachments are computed by filename.
+ * Package-private for unit testing.
      * @param head any head string.
      * @return return the mime type or null for text/plain.
      */
- private String contentTypeOf(String head) {
- if (head != null) {
- final Locale locale = Locale.ENGLISH;
- if (head.trim().toUpperCase(locale).indexOf("<HTML") > -1) {
- return "text/html";
- } else if (head.trim().toUpperCase(locale).indexOf("<XML") > -1) {
- return "text/xml";
+ final String contentTypeOf(final String head) {
+ if (head != null && head.length() > 0) {
+ try {
+ final ByteArrayInputStream in =
+ new ByteArrayInputStream(head.getBytes("US-ASCII"));
+ assert in.markSupported() : in.getClass().getName();
+ return URLConnection.guessContentTypeFromStream(in);
+ } catch (final IOException IOE) {
+ reportError(IOE.getMessage(), IOE, ErrorManager.FORMAT_FAILURE);
             }
         }
         return null; //text/plain
@@ -991,7 +1046,7 @@
      * @param newCapacity the max number of records.
      * @throws IllegalStateException if called from inside a push.
      */
- private final synchronized void setCapacity(int newCapacity) {
+ private synchronized void setCapacity0(final int newCapacity) {
         if (newCapacity <= 0) {
             throw new IllegalArgumentException("Capacity must be greater than zero.");
         }
@@ -1024,7 +1079,7 @@
     private void fixUpAttachmentFormatters() {
         assert Thread.holdsLock(this);
         final int attachments = attachmentFormatters.length;
- for (int i = 0; i < attachments; i++) {
+ for (int i = 0; i < attachments; ++i) {
             if (attachmentFormatters[i] == null) {
                 final NullPointerException NPE = new NullPointerException(atIndexMsg(i));
                 attachmentFormatters[i] = new SimpleFormatter();
@@ -1040,6 +1095,7 @@
 
     /**
      * Expand or shrink the attachment name formatters.
+ * @return true if fixed.
      */
     private boolean fixUpAttachmentNames() {
         assert Thread.holdsLock(this);
@@ -1051,7 +1107,7 @@
             fixed = current != 0;
         }
 
- for (int i = 0; i < expect; i++) {
+ for (int i = 0; i < expect; ++i) {
             if (this.attachmentNames[i] == null) {
                 this.attachmentNames[i] = new TailNameFormatter(
                         toString(this.attachmentFormatters[i]));
@@ -1062,6 +1118,7 @@
 
     /**
      * Expand or shrink the attachment filters.
+ * @return true if fixed.
      */
     private boolean fixUpAttachmentFilters() {
         assert Thread.holdsLock(this);
@@ -1090,7 +1147,7 @@
     /**
      * Sets the size to zero and clears the current buffer.
      */
- private final void reset() {
+ private void reset() {
         assert Thread.holdsLock(this);
         Arrays.fill(data, 0, size, null);
         this.size = 0;
@@ -1101,14 +1158,16 @@
      */
     private void grow() {
         assert Thread.holdsLock(this);
- int newCapacity = Math.min(((data.length * 3) >>> 1) + 1, capacity);
- if (newCapacity < data.length) {
+ final int len = data.length;
+ int newCapacity = len + (len >> 1) + 1;
+ if (newCapacity > capacity || newCapacity < len) {
             newCapacity = capacity;
- assert data.length != capacity;
         }
- data = (LogRecord[]) copyOf(data, newCapacity);
+ assert len != capacity : len;
+ this.data = (LogRecord[]) copyOf(data, newCapacity);
     }
 
+
     /**
      * Configures the handler properties from the log manager.
      * @throws SecurityException if a security manager exists and the
@@ -1130,6 +1189,7 @@
         initCapacity(manager, p);
 
         this.auth = (Authenticator) initObject(p.concat(".authenticator"), Authenticator.class);
+ this.fixUpSession();
 
         initEncoding(manager, p);
         initFormatter(p);
@@ -1168,8 +1228,8 @@
                         return clazz.getConstructor((Class[]) null).
                                 newInstance((Object[]) null);
                     } else {
- throw new ClassCastException(clazz.getName() +
- " cannot be cast to " + type.getName());
+ throw new ClassCastException(clazz.getName()
+ + " cannot be cast to " + type.getName());
                     }
                 } catch (final NoClassDefFoundError NCDFE) {
                     throw (ClassNotFoundException) new ClassNotFoundException(
@@ -1181,6 +1241,12 @@
                 } else {
                     throw CNFE;
                 }
+ } catch (final ClassCastException CCE) {
+ if (type == Formatter.class) {
+ return /*type.cast(*/ new TailNameFormatter(name);
+ } else {
+ throw CCE;
+ }
             }
         } catch (final NoSuchMethodException NSME) {
             throw NSME; //avoid catch all.
@@ -1207,7 +1273,7 @@
         if (list != null && list.length() > 0) {
             final String[] names = list.split(",");
             Object[] a = (Object[]) Array.newInstance(type, names.length);
- for (int i = 0; i < a.length; i++) {
+ for (int i = 0; i < a.length; ++i) {
                 names[i] = names[i].trim();
                 if (!"null".equalsIgnoreCase(names[i])) {
                     try {
@@ -1261,9 +1327,9 @@
         try {
             final String value = manager.getProperty(p.concat(".capacity"));
             if (value != null) {
- this.setCapacity(Integer.parseInt(value));
+ this.setCapacity0(Integer.parseInt(value));
             } else {
- this.setCapacity(DEFAULT_CAPACITY);
+ this.setCapacity0(DEFAULT_CAPACITY);
             }
         } catch (final RuntimeException RE) {
             reportError(RE.getMessage(), RE, ErrorManager.OPEN_FAILURE);
@@ -1273,7 +1339,7 @@
             capacity = DEFAULT_CAPACITY;
         }
 
- this.data = new LogRecord[Math.min(INITIAL_DATA_LENGTH, capacity)];
+ this.data = new LogRecord[1];
     }
 
     private void initEncoding(LogManager manager, String p) {
@@ -1292,8 +1358,7 @@
     private void initFormatter(String p) {
         assert Thread.holdsLock(this);
         try {
- final Formatter formatter = (Formatter)
- initObject(p.concat(".formatter"), Formatter.class);
+ final Formatter formatter = (Formatter) initObject(p.concat(".formatter"), Formatter.class);
             if (formatter != null && formatter instanceof TailNameFormatter == false) {
                 super.setFormatter(formatter);
             } else {
@@ -1315,8 +1380,7 @@
     private void initComparator(String p) {
         assert Thread.holdsLock(this);
         try {
- this.comparator = (Comparator)
- this.initObject(p.concat(".comparator"), Comparator.class);
+ this.comparator = (Comparator) this.initObject(p.concat(".comparator"), Comparator.class);
         } catch (final Exception RE) {
             reportError(RE.getMessage(), RE, ErrorManager.OPEN_FAILURE);
         }
@@ -1372,7 +1436,7 @@
      */
     private boolean isAttachmentLoggable(final LogRecord record) {
         final Filter[] filters = readOnlyAttachmentFilters();
- for (int i = 0; i < filters.length; i++) {
+ for (int i = 0; i < filters.length; ++i) {
             final Filter f = filters[i];
             if (f == null || f.isLoggable(record)) {
                 return true;
@@ -1403,8 +1467,17 @@
      * @param priority true for high priority otherwise false for normal.
      * @param code the error manager code.
      */
- private final void push(final boolean priority, final int code) {
- send(writeLogRecords(priority, code), code);
+ private void push(final boolean priority, final int code) {
+ MessageContext ctx = null;
+ synchronized (this) {
+ if (size > 0) {
+ ctx = writeLogRecords(code);
+ }
+ }
+
+ if (ctx != null) {
+ send(ctx, priority, code);
+ }
     }
 
     /**
@@ -1412,21 +1485,22 @@
      * error manager for this handler. This method does not hold any
      * locks so new records can be added to this handler during a send or
      * failure.
- * @param msg the message or null.
+ * @param ctx the message context or null.
+ * @param priority true for high priority or false for normal.
      * @param code the ErrorManager code.
      */
- private final void send(final Message msg, final int code) {
- if (msg != null) {
- try {
- Transport.send(msg);
- } catch (final Exception E) {
- try { //use super call so we do not prefix raw email.
- super.reportError(toRawString(msg), E, code);
- } catch (final MessagingException rawMe) {
- reportError(toMsgString(rawMe), E, code); //report original cause.
- } catch (final IOException rawIo) {
- reportError(toMsgString(rawIo), E, code); //report original cause.
- }
+ private void send(MessageContext ctx, boolean priority, int code) {
+ final Message msg = ctx.getMessage();
+ try {
+ envelopeFor(ctx, priority);
+ Transport.send(msg);
+ } catch (final Exception E) {
+ try { //use super call so we do not prefix raw email.
+ super.reportError(toRawString(msg), E, code);
+ } catch (final MessagingException rawMe) {
+ reportError(toMsgString(rawMe), E, code);
+ } catch (final IOException rawIo) {
+ reportError(toMsgString(rawIo), E, code);
             }
         }
     }
@@ -1451,25 +1525,22 @@
      * This method holds a lock on this handler.
      * Normally code would not aggressively null locals but with this running
      * on older JVMs it seems better to
[truncated due to length]