commits@javamail.java.net

[javamail~mercurial:386] Updates from Jason:

From: <shannon_at_kenai.com>
Date: Thu, 22 Sep 2011 00:06:51 +0000

Project: javamail
Repository: mercurial
Revision: 386
Author: shannon
Date: 2011-09-21 22:46:54 UTC
Link:

Log Message:
------------
Add more defensive checks for null before using protocol in close() method.
Updates from Jason:

FileErrorManager capture LinkageError from File.deleteOnExit.
LogManagerProperties new create object factory methods.
LogManagerProperties add toLanguageTag method.
LogManagerProperties prevent escaping of static init runtime exceptions.
LogManagerProperties override more properties methods to cache values.
LogManagerPropertiesTest new object factory tests.
LogManagerPropertiesTest more properties method testing.
LogManagerPropertiesTest added PropUtil test.
MailHandler convert java charsets to mime charsets.
MailHandler fixed IllegalStateException when NoClassDefFoundError was thrown.
MailHandler refactor LogManager object init code.
MailHandler add Incomplete-Copy header when parts are omitted by formatters.
MailHandler map Accept-Language header to Locale.getDefault()
MailHandler map Content-Language header if LogRecord has a ResourceBundle.
MailHandler Message description should include comparator class and push info.
MailHandler Mime part description should include formatter name class.
MailHandler additional verify checks for SendFailedException.
MailHandlerTest tests for attachment invariants.
MailHandlerTest tests for creating classes where the constructor throws.
MailHandlerTest tests for creating classes where the static init throws.
MailHandlerTest tests for content and accept language headers.
MailHandlerTest tests for content description for all parts and mime message.


Revisions:
----------
385
386


Modified Paths:
---------------
mail/src/main/java/com/sun/mail/imap/IMAPFolder.java
logging/src/main/java/FileErrorManager.java
mail/src/main/java/com/sun/mail/imap/IMAPMessage.java
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/test/java/com/sun/mail/util/logging/MailHandlerTest.java


Diffs:
------
diff -r 3b2c104e3bc2 -r cb43ea8dcf59 mail/src/main/java/com/sun/mail/imap/IMAPFolder.java
--- a/mail/src/main/java/com/sun/mail/imap/IMAPFolder.java Fri Sep 02 15:09:00 2011 -0700
+++ b/mail/src/main/java/com/sun/mail/imap/IMAPFolder.java Tue Sep 06 15:02:11 2011 -0700
@@ -1099,7 +1099,7 @@
                             "an Authenticated connection");
 
                     // If the expunge flag is set, close the folder first.
- if (expunge)
+ if (expunge && protocol != null)
                         protocol.close();
 
                     if (protocol != null)
@@ -1110,12 +1110,15 @@
                     // before closing, or unselect it if supported.
                     if (!expunge && mode == READ_WRITE) {
                         try {
- if (protocol.hasCapability("UNSELECT"))
+ if (protocol != null &&
+ protocol.hasCapability("UNSELECT"))
                                 protocol.unselect();
                             else {
- MailboxInfo mi = protocol.examine(fullName);
- if (protocol != null) // XXX - unnecessary?
- protocol.close();
+ if (protocol != null) {
+ MailboxInfo mi = protocol.examine(fullName);
+ if (protocol != null) // XXX - unnecessary?
+ protocol.close();
+ }
                             }
                         } catch (ProtocolException pex2) {
                             if (protocol != null)


diff -r cb43ea8dcf59 -r 91e372ffcc60 logging/src/main/java/FileErrorManager.java
--- a/logging/src/main/java/FileErrorManager.java Tue Sep 06 15:02:11 2011 -0700
+++ b/logging/src/main/java/FileErrorManager.java Wed Sep 21 15:46:54 2011 -0700
@@ -1,6 +1,6 @@
 /*
- * Copyright (c) 2009-2010 Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2009-2010 Jason Mehrens. All Rights Reserved.
+ * Copyright (c) 2009-2011 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009-2011 Jason Mehrens. All Rights Reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -109,7 +109,7 @@
      * If the message parameter is a raw email, and passes the store term, then
      * this method will store the email to the file system. If the message
      * parameter is not a raw email then the message is forwarded to the super
- * class. If an email is written to the filesystem without error, then the
+ * class. If an email is written to the file system without error, then the
      * original reported error is ignored.
      * @param msg String raw email or plain error message.
      * @param ex Exception that occurred in the mail handler.
@@ -218,7 +218,11 @@
             try {
                 if (!tmp.delete() && tmp.exists()) {
                     try {
- tmp.deleteOnExit();
+ try {
+ tmp.deleteOnExit();
+ } catch (final LinkageError shutdown) {
+ throw new RuntimeException(shutdown);
+ }
                     } catch (final RuntimeException shutdown) {
                         if (!tmp.delete()) {
                             super.error(tmp.getAbsolutePath(), shutdown,

diff -r cb43ea8dcf59 -r 91e372ffcc60 mail/src/main/java/com/sun/mail/imap/IMAPMessage.java
--- a/mail/src/main/java/com/sun/mail/imap/IMAPMessage.java Tue Sep 06 15:02:11 2011 -0700
+++ b/mail/src/main/java/com/sun/mail/imap/IMAPMessage.java Wed Sep 21 15:46:54 2011 -0700
@@ -694,8 +694,12 @@
             }
         }
 
- if (is == null)
+ if (is == null) {
+ forceCheckExpunged(); // may throw MessageRemovedException
+ // nope, the server doesn't think it's expunged,
+ // something else is wrong
             throw new MessagingException("No content");
+ }
         
         // write out the bytes
         byte[] bytes = new byte[1024];

diff -r cb43ea8dcf59 -r 91e372ffcc60 mail/src/main/java/com/sun/mail/util/logging/LogManagerProperties.java
--- a/mail/src/main/java/com/sun/mail/util/logging/LogManagerProperties.java Tue Sep 06 15:02:11 2011 -0700
+++ b/mail/src/main/java/com/sun/mail/util/logging/LogManagerProperties.java Wed Sep 21 15:46:54 2011 -0700
@@ -41,10 +41,14 @@
 package com.sun.mail.util.logging;
 
 import java.io.ObjectStreamException;
+import java.lang.reflect.InvocationTargetException;
 import java.security.*;
+import java.util.Comparator;
 import java.util.Enumeration;
+import java.util.Locale;
 import java.util.Properties;
 import java.util.logging.*;
+import javax.mail.Authenticator;
 
 /**
  * An adapter class to allow the Mail API to access the LogManager properties.
@@ -74,7 +78,195 @@
     /**
      * Caches the LogManager so we only read the config once.
      */
- final static LogManager manager = LogManager.getLogManager();
+ private final static LogManager LOG_MANAGER = LogManager.getLogManager();
+
+ /**
+ * Gets the LogManger for the running JVM.
+ * @return the LogManager.
+ * @since JavaMail 1.4.5
+ */
+ static LogManager getLogManager() {
+ return LOG_MANAGER;
+ }
+
+ /**
+ * 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.
+ * @since JavaMail 1.4.5
+ */
+ static String toLanguageTag(final Locale locale) {
+ final String l = locale.getLanguage();
+ final String c = locale.getCountry();
+ final String v = locale.getVariant();
+ final char[] b = new char[l.length() + c.length() + v.length() + 2];
+ int count = l.length();
+ l.getChars(0, count, b, 0);
+ if (c.length() != 0 || (l.length() != 0 && v.length() != 0)) {
+ b[count] = '-';
+ ++count; //be nice to the client compiler.
+ c.getChars(0, c.length(), b, count);
+ count += c.length();
+ }
+
+ if (v.length() != 0 && (l.length() != 0 || c.length() != 0)) {
+ b[count] = '-';
+ ++count; //be nice to the client compiler.
+ v.getChars(0, v.length(), b, count);
+ count += v.length();
+ }
+ return String.valueOf(b, 0, count);
+ }
+
+ /**
+ * 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.
+ * @throws ClassNotFoundException if the class name was not found.
+ * @throws IllegalAccessException if the constructor is inaccessible.
+ * @throws InstantiationException if the given class name is abstract.
+ * @throws InvocationTargetException if the constructor throws an exception.
+ * @throws LinkageError if the linkage fails.
+ * @throws ExceptionInInitializerError if the static initializer fails.
+ * @throws Exception to match the error method of the ErrorManager.
+ * @throws NoSuchMethodException if the class name does not have a no
+ * argument constructor.
+ * @since JavaMail 1.4.5
+ */
+ static Filter newFilter(String name) throws Exception {
+ return (Filter) newObjectFrom(name, Filter.class);
+ }
+
+ /**
+ * 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.
+ * @throws ClassNotFoundException if the class name was not found.
+ * @throws IllegalAccessException if the constructor is inaccessible.
+ * @throws InstantiationException if the given class name is abstract.
+ * @throws InvocationTargetException if the constructor throws an exception.
+ * @throws LinkageError if the linkage fails.
+ * @throws ExceptionInInitializerError if the static initializer fails.
+ * @throws Exception to match the error method of the ErrorManager.
+ * @throws NoSuchMethodException if the class name does not have a no
+ * argument constructor.
+ * @since JavaMail 1.4.5
+ */
+ static Formatter newFormatter(String name) throws Exception {
+ return (Formatter) newObjectFrom(name, Formatter.class);
+ }
+
+ /**
+ * 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.
+ * @throws ClassNotFoundException if the class name was not found.
+ * @throws IllegalAccessException if the constructor is inaccessible.
+ * @throws InstantiationException if the given class name is abstract.
+ * @throws InvocationTargetException if the constructor throws an exception.
+ * @throws LinkageError if the linkage fails.
+ * @throws ExceptionInInitializerError if the static initializer fails.
+ * @throws Exception to match the error method of the ErrorManager.
+ * @throws NoSuchMethodException if the class name does not have a no
+ * argument constructor.
+ * @since JavaMail 1.4.5
+ * @see java.util.logging.LogRecord
+ */
+ static Comparator newComparator(String name) throws Exception {
+ return (Comparator) newObjectFrom(name, Comparator.class);
+ }
+
+ /**
+ * 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.
+ * @throws ClassNotFoundException if the class name was not found.
+ * @throws IllegalAccessException if the constructor is inaccessible.
+ * @throws InstantiationException if the given class name is abstract.
+ * @throws InvocationTargetException if the constructor throws an exception.
+ * @throws LinkageError if the linkage fails.
+ * @throws ExceptionInInitializerError if the static initializer fails.
+ * @throws Exception to match the error method of the ErrorManager.
+ * @throws NoSuchMethodException if the class name does not have a no
+ * argument constructor.
+ * @since JavaMail 1.4.5
+ */
+ static ErrorManager newErrorManager(String name) throws Exception {
+ return (ErrorManager) newObjectFrom(name, ErrorManager.class);
+ }
+
+ /**
+ * 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.
+ * @throws ClassNotFoundException if the class name was not found.
+ * @throws IllegalAccessException if the constructor is inaccessible.
+ * @throws InstantiationException if the given class name is abstract.
+ * @throws InvocationTargetException if the constructor throws an exception.
+ * @throws LinkageError if the linkage fails.
+ * @throws ExceptionInInitializerError if the static initializer fails.
+ * @throws Exception to match the error method of the ErrorManager.
+ * @throws NoSuchMethodException if the class name does not have a no
+ * argument constructor.
+ * @since JavaMail 1.4.5
+ */
+ static Authenticator newAuthenticator(String name) throws Exception {
+ return (Authenticator) newObjectFrom(name, Authenticator.class);
+ }
+
+ /**
+ * Creates a new object from the given class name.
+ * @param name the fully qualified class name.
+ * @param type the assignable type for the given name.
+ * @return a new object assignable to the given type.
+ * @throws ClassCastException if class name does not match the type.
+ * @throws ClassNotFoundException if the class name was not found.
+ * @throws IllegalAccessException if the constructor is inaccessible.
+ * @throws InstantiationException if the given class name is abstract.
+ * @throws InvocationTargetException if the constructor throws an exception.
+ * @throws LinkageError if the linkage fails.
+ * @throws ExceptionInInitializerError if the static initializer fails.
+ * @throws Exception to match the error method of the ErrorManager.
+ * @throws NoSuchMethodException if the class name does not have a no
+ * argument constructor.
+ * @since JavaMail 1.4.5
+ */
+ private static Object newObjectFrom(String name, Class type) throws Exception {
+ try {
+ final Class clazz = LogManagerProperties.findClass(name);
+ //This check avoids additional side effects when the name parameter
+ //is a literal name and not a class name.
+ if (type.isAssignableFrom(clazz)) {
+ return clazz.getConstructor((Class[]) null).newInstance((Object[]) null);
+ } else {
+ throw new ClassCastException(clazz.getName()
+ + " cannot be cast to " + type.getName());
+ }
+ } catch (final NoClassDefFoundError NCDFE) {
+ //No class def found can occur on filesystems that are
+ //case insensitive (BUG ID 6196068). In some cases, we allow class
+ //names or literal names, this code guards against the case where a
+ //literal name happens to match a class name in a different case.
+ //This is also a nice way to adap this error for the error manager.
+ throw new ClassNotFoundException(NCDFE.toString(), NCDFE);
+ } catch (final ExceptionInInitializerError EIIE) {
+ //This linkage error will escape the constructor new instance call.
+ //If the cause is an error, rethrow to skip any error manager.
+ if (EIIE.getCause() instanceof Error) {
+ throw EIIE;
+ } else {
+ //Considered a bug in the code, wrap the error so it can be
+ //reported to the error manager.
+ throw new InvocationTargetException(EIIE);
+ }
+ }
+ }
 
     /**
      * This code is modified from the LogManager, which explictly states
@@ -83,10 +275,13 @@
      * searching the context class loader.
      * @param name full class name
      * @return the class.
- * @throws ClassNotFoundException if not found.
+ * @throws LinkageError if the linkage fails.
+ * @throws ClassNotFoundException if the class name was not found.
+ * @throws ExceptionInInitializerError if static initializer fails.
      */
- static Class findClass(String name) throws ClassNotFoundException {
+ private static Class findClass(String name) throws ClassNotFoundException {
         ClassLoader[] loaders = getClassLoaders();
+ assert loaders.length == 2 : loaders.length;
         Class clazz;
         if (loaders[0] != null) {
             try {
@@ -128,6 +323,9 @@
             }
         });
     }
+ /**
+ * The namespace prefix to search LogManager and defaults.
+ */
     private final String prefix;
 
     /**
@@ -161,15 +359,19 @@
     }
 
     /**
- * Performs the super action, then searches the log manager
+ * Searches defaults, then searches the log manager
      * by the prefix property, and then by the key itself.
      * @param key a non null key.
      * @return the value for that key.
      */
- public synchronized String getProperty(String key) {
+ public synchronized String getProperty(final String key) {
         String value = defaults.getProperty(key);
- if (value == null && key.length() > 0) {
- value = manager.getProperty(prefix + '.' + key);
+ if (value == null) {
+ final LogManager manager = getLogManager();
+ if (key.length() > 0) {
+ value = manager.getProperty(prefix + '.' + key);
+ }
+
             if (value == null) {
                 value = manager.getProperty(key);
             }
@@ -182,9 +384,9 @@
              * closed handlers.
              */
             if (value != null) {
- put(key, value);
+ super.put(key, value);
             } else {
- Object v = get(key); //Call 'get' so defaults are not used.
+ Object v = super.get(key); //defaults are not used.
                 value = v instanceof String ? (String) v : null;
             }
         }
@@ -199,12 +401,78 @@
      * @return the value for the key.
      * @since JavaMail 1.4.4
      */
- public String getProperty(String key, String def) {
+ public String getProperty(final String key, final String def) {
         final String value = this.getProperty(key);
         return value == null ? def : value;
     }
 
     /**
+ * Required to work with PropUtil. Calls getProperty directly if the
+ * given key is a string. Otherwise, performs a normal get operation.
+ * @param key any key.
+ * @return the value for the key or null.
+ * @since JavaMail 1.4.5
+ */
+ public Object get(final Object key) {
+ if (key instanceof String) {
+ return this.getProperty((String) key);
+ } else {
+ return super.get(key);
+ }
+ }
+
+ /**
+ * Required to work with PropUtil. An updated copy of the key is fetched
+ * from the log manager if the key doesn't exist in this properties.
+ * @param key any key.
+ * @return the value for the key or the default value for the key.
+ * @since JavaMail 1.4.5
+ */
+ public synchronized Object put(final Object key, final Object value) {
+ final Object def = preWrite(key);
+ final Object man = super.put(key, value);
+ return man == null ? def : man;
+ }
+
+ /**
+ * Calls the put method directly.
+ * @param key any key.
+ * @return the value for the key or the default value for the key.
+ * @since JavaMail 1.4.5
+ */
+ public Object setProperty(String key, String value) {
+ return this.put(key, value);
+ }
+
+ /**
+ * Required to work with PropUtil. An updated copy of the key is fetched
+ * from the log manager prior to returning.
+ * @param key any key.
+ * @return the value for the key or null.
+ * @since JavaMail 1.4.5
+ */
+ public boolean containsKey(final Object key) {
+ if (key instanceof String) {
+ return this.getProperty((String) key) != null;
+ } else {
+ return super.containsKey(key);
+ }
+ }
+
+ /**
+ * Required to work with PropUtil. An updated copy of the key is fetched
+ * from the log manager if the key doesn't exist in this properties.
+ * @param key any key.
+ * @return the value for the key or the default value for the key.
+ * @since JavaMail 1.4.5
+ */
+ public synchronized Object remove(final Object key) {
+ final Object def = preWrite(key);
+ final Object man = super.remove(key);
+ return man == null ? def : man;
+ }
+
+ /**
      * It is assumed that this method will never be called.
      * No way to get the property names from LogManager.
      * @return the property names
@@ -220,7 +488,7 @@
      * @param o any object or null.
      * @return true if equal, otherwise false.
      */
- public boolean equals(Object o) {
+ public boolean equals(final Object o) {
         if (o == null) {
             return false;
         }
@@ -244,6 +512,25 @@
     }
 
     /**
+ * Called before a write operation of a key.
+ * Caches a key read from the log manager in this properties object.
+ * The key is only cached if it is an instance of a String and
+ * this properties doesn't contain a copy of the key.
+ * @param key the key to search.
+ * @return the default value for the key.
+ */
+ private Object preWrite(final Object key) {
+ assert Thread.holdsLock(this);
+ Object value;
+ if (key instanceof String && !super.containsKey(key)) {
+ value = this.getProperty((String) key); //fetch and cache.
+ } else {
+ value = null;
+ }
+ return value;
+ }
+
+ /**
      * Creates a public snapshot of this properties object using
      * the given parent properties.
      * @param parent the defaults to use with the snapshot.

diff -r cb43ea8dcf59 -r 91e372ffcc60 mail/src/main/java/com/sun/mail/util/logging/MailHandler.java
--- a/mail/src/main/java/com/sun/mail/util/logging/MailHandler.java Tue Sep 06 15:02:11 2011 -0700
+++ b/mail/src/main/java/com/sun/mail/util/logging/MailHandler.java Wed Sep 21 15:46:54 2011 -0700
@@ -48,7 +48,9 @@
 import java.net.URLConnection;
 import java.util.Arrays;
 import java.util.Comparator;
+import java.util.Locale;
 import java.util.Properties;
+import java.util.ResourceBundle;
 import java.util.logging.*;
 import javax.activation.*;
 import javax.mail.*;
@@ -111,7 +113,7 @@
  * attachment file names must not contain any line breaks.
  * (default is no attachments names)
  *
- * <li>com.sun.mail.util.logging.MailHandler.authenticator name of a
+ * <li>com.sun.mail.util.logging.MailHandler.authenticator name of an
  * {_at_linkplain javax.mail.Authenticator} class used to provide login credentials
  * to the email server. (default is <tt>null</tt>)
  *
@@ -130,10 +132,13 @@
  * <tt>false</tt> to retain the original order. (defaults to <tt>false</tt>)
  * -->
  *
- * <li>com.sun.mail.util.logging.MailHandler.encoding the name of the character
- * set encoding to use (defaults to the default platform encoding).
+ * <li>com.sun.mail.util.logging.MailHandler.encoding the name of the Java
+ * {_at_linkplain java.nio.charset.Charset#name() character set} to use for the
+ * email message. (defaults to <tt>null</tt>, the
+ * {_at_linkplain javax.mail.internet.MimeUtility#getDefaultJavaCharset() default}
+ * platform encoding).
  *
- * <li>com.sun.mail.util.logging.MailHandler.errorManager name of a
+ * <li>com.sun.mail.util.logging.MailHandler.errorManager name of an
  * <tt>ErrorManager</tt> class used to handle any configuration or mail
  * transport problems. (defaults to <tt>java.util.logging.ErrorManager</tt>)
  *
@@ -158,14 +163,15 @@
  * addresses which will be carbon copied. Typically, this is set to the
  * recipients that may need to be notified of a log message but, are not
  * required to provide direct support. (defaults to <tt>null</tt>, none)
- *
+ *
  * <li>com.sun.mail.util.logging.MailHandler.mail.from a comma separated list of
  * addresses which will be from addresses. Typically, this is set to the email
  * address identifying the user running the application.
  * (defaults to {_at_linkplain javax.mail.Message#setFrom()})
  *
  * <li>com.sun.mail.util.logging.MailHandler.mail.host the host name or IP
- * address of the email server. (defaults to <tt>null</tt>, no host)
+ * address of the email server. (defaults to <tt>null</tt>, use default
+ * <tt>Java Mail</tt> behavior)
  *
  * <li>com.sun.mail.util.logging.MailHandler.mail.reply.to a comma separated
  * list of addresses which will be reply-to addresses. Typically, this is set
@@ -288,7 +294,14 @@
  * @since JavaMail 1.4.3
  */
 public class MailHandler extends Handler {
-
+ /**
+ * Use the emptyFilterArray method.
+ */
+ private static final Filter[] EMPTY_FILTERS = new Filter[0];
+ /**
+ * Use the emptyFormatterArray method.
+ */
+ private static final Formatter[] EMPTY_FORMATTERS = new Formatter[0];
     /**
      * Cache the off value.
      */
@@ -711,7 +724,7 @@
      * objects into one email message.
      * @return the capacity.
      */
- public final synchronized int getCapacity() {
+ public final synchronized int getCapacity() {
         assert capacity != Integer.MIN_VALUE && capacity != 0 : capacity;
         return Math.abs(capacity);
     }
@@ -856,10 +869,14 @@
      */
     public final void setAttachmentFormatters(Formatter[] formatters) {
         checkAccess();
- formatters = (Formatter[]) formatters.clone();
- for (int i = 0; i < formatters.length; ++i) {
- if (formatters[i] == null) {
- throw new NullPointerException(atIndexMsg(i));
+ if (formatters.length == 0) { //null check and length check.
+ formatters = emptyFormatterArray();
+ } else {
+ formatters = (Formatter[]) formatters.clone();
+ for (int i = 0; i < formatters.length; ++i) {
+ if (formatters[i] == null) {
+ throw new NullPointerException(atIndexMsg(i));
+ }
             }
         }
 
@@ -903,10 +920,16 @@
      * @throws IllegalStateException if called from inside a push.
      * @see Character#isISOControl(char)
      */
- public final void setAttachmentNames(String[] names) {
+ public final void setAttachmentNames(final String[] names) {
         checkAccess();
 
- Formatter[] formatters = new Formatter[names.length];
+ final Formatter[] formatters;
+ if (names.length == 0) {
+ formatters = emptyFormatterArray();
+ } else {
+ formatters = new Formatter[names.length];
+ }
+
         for (int i = 0; i < names.length; ++i) {
             final String name = names[i];
             if (name != null) {
@@ -1059,7 +1082,7 @@
      */
     final void checkAccess() {
         if (sealed) {
- LogManagerProperties.manager.checkAccess();
+ LogManagerProperties.getLogManager().checkAccess();
         }
     }
 
@@ -1079,13 +1102,14 @@
                 head = head.substring(0, MAX_CHARS);
             }
             try {
- final ByteArrayInputStream in;
- final String encoding = getEncoding();
+ String encoding = getEncoding();
                 if (encoding == null) {
- in = new ByteArrayInputStream(head.getBytes());
- } else {
- in = new ByteArrayInputStream(head.getBytes(encoding));
+ encoding = MimeUtility.getDefaultJavaCharset();
                 }
+
+ final ByteArrayInputStream in
+ = new ByteArrayInputStream(head.getBytes(encoding));
+
                 assert in.markSupported() : in.getClass().getName();
                 return URLConnection.guessContentTypeFromStream(in);
             } catch (final IOException IOE) {
@@ -1129,21 +1153,20 @@
     }
 
     /**
- * Set the content for a part.
+ * Set the content for a part using the encoding assigned to the handler.
      * @param part the part to assign.
      * @param buf the formatted data.
      * @param type the mime type.
      * @throws MessagingException if there is a problem.
      */
     private void setContent(MimeBodyPart part, CharSequence buf, String type) throws MessagingException {
- final String encoding = getEncoding();
+ String encoding = getEncoding();
+ if (encoding == null) {
+ encoding = MimeUtility.getDefaultJavaCharset();
+ }
+
         if (type != null && !"text/plain".equals(type)) {
- if (encoding == null) {
- type = contentWithDefault(type);
- } else {
- type = contentWithEncoding(type, encoding);
- }
-
+ type = contentWithEncoding(type, encoding);
             try {
                 DataSource source = new ByteArrayDataSource(buf.toString(), type);
                 part.setDataHandler(new DataHandler(source));
@@ -1152,21 +1175,21 @@
                 part.setText(buf.toString(), encoding);
             }
         } else {
- part.setText(buf.toString(), encoding);
+ part.setText(buf.toString(), MimeUtility.mimeCharset(encoding));
         }
     }
 
     /**
- * Only call if encoding is not null.
      * Replaces the charset parameter with the current encoding.
      * @param type the content type.
- * @param encoding the encoding.
+ * @param encoding the java charset name.
      * @return the type with a specified encoding.
      */
     private String contentWithEncoding(String type, String encoding) {
+ assert encoding != null;
         try {
             final ContentType ct = new ContentType(type);
- ct.setParameter("charset", encoding);
+ ct.setParameter("charset", MimeUtility.mimeCharset(encoding));
             encoding = ct.toString();
             if (encoding != null) {
                 type = encoding;
@@ -1178,29 +1201,6 @@
     }
 
     /**
- * Removes the encoding parameter from the content type.
- * @param type the content type.
- * @return the content type without encoding.
- */
- private String contentWithDefault(String type) {
- try {
- final ContentType ct = new ContentType(type);
- if (ct.getParameter("charset") != null) {
- final ParameterList list = ct.getParameterList();
- list.remove("charset");
- ct.setParameterList(list);
- final String newType = ct.toString();
- if (newType != null) {
- type = newType;
- }
- }
- } catch (final MessagingException ME) {
- reportError(type, ME, ErrorManager.FORMAT_FAILURE);
- }
- return type;
- }
-
- /**
      * Sets the capacity for this handler. This method is kept private
      * because we would have to define a public policy for when the size is
      * greater than the capacity.
@@ -1235,24 +1235,19 @@
     }
 
     /**
- * Check that the log manager is creating a valid set of formatters.
- * This is only called during init.
+ * Factory for empty formatter arrays.
+ * @return an empty array.
      */
- private void fixUpAttachmentFormatters() {
- assert Thread.holdsLock(this);
- final int attachments = attachmentFormatters.length;
- for (int i = 0; i < attachments; ++i) {
- if (attachmentFormatters[i] == null) {
- final NullPointerException NPE = new NullPointerException(atIndexMsg(i));
- attachmentFormatters[i] = new SimpleFormatter();
- reportError("attachment formatter.", NPE, ErrorManager.OPEN_FAILURE);
- } else if (attachmentFormatters[i] instanceof TailNameFormatter) {
- final ClassNotFoundException CNFE =
- new ClassNotFoundException(attachmentFormatters[i].toString());
- attachmentFormatters[i] = new SimpleFormatter();
- reportError("attachment formatter.", CNFE, ErrorManager.OPEN_FAILURE);
- }
- }
+ private static Formatter[] emptyFormatterArray() {
+ return EMPTY_FORMATTERS;
+ }
+
+ /**
+ * Factory for empty filter arrays.
+ * @return an empty array.
+ */
+ private static Filter[] emptyFilterArray() {
+ return EMPTY_FILTERS;
     }
 
     /**
@@ -1267,12 +1262,18 @@
         if (current != expect) {
             this.attachmentNames = (Formatter[]) copyOf(attachmentNames, expect);
             fixed = current != 0;
- }
+ }
 
- for (int i = 0; i < expect; ++i) {
- if (this.attachmentNames[i] == null) {
- this.attachmentNames[i] = new TailNameFormatter(
- toString(this.attachmentFormatters[i]));
+ //copy of zero length array is cheap, warm up copyOf.
+ if (expect == 0) {
+ this.attachmentNames = emptyFormatterArray();
+ assert this.attachmentNames.length == 0;
+ } else {
+ for (int i = 0; i < expect; ++i) {
+ if (this.attachmentNames[i] == null) {
+ this.attachmentNames[i] = new TailNameFormatter(
+ toString(this.attachmentFormatters[i]));
+ }
             }
         }
         return fixed;
@@ -1285,13 +1286,20 @@
     private boolean fixUpAttachmentFilters() {
         assert Thread.holdsLock(this);
 
+ boolean fixed = false;
         final int expect = this.attachmentFormatters.length;
         final int current = this.attachmentFilters.length;
         if (current != expect) {
             this.attachmentFilters = (Filter[]) copyOf(attachmentFilters, expect);
- return current != 0;
+ fixed = current != 0;
         }
- return false;
+
+ //copy of zero length array is cheap, warm up copyOf.
+ if (expect == 0) {
+ this.attachmentFilters = emptyFilterArray();
+ assert this.attachmentFilters.length == 0;
+ }
+ return fixed;
     }
 
     /**
@@ -1339,120 +1347,164 @@
      * caller does not have <tt>LoggingPermission("control")</tt>.
      */
     private synchronized void init() {
- final LogManager manager = LogManagerProperties.manager;
+ final LogManager manager = LogManagerProperties.getLogManager();
         final String p = getClass().getName();
         this.mailProps = new Properties();
         this.contentTypes = FileTypeMap.getDefaultFileTypeMap();
 
         //Assign any custom error manager first so it can detect all failures.
- ErrorManager em = (ErrorManager) initObject(p.concat(".errorManager"), ErrorManager.class);
- if (em != null) {
- setErrorManager(em);
- }
+ initErrorManager(manager, p);
 
         initLevel(manager, p);
- initFilter(p);
+ initFilter(manager, p);
         initCapacity(manager, p);
-
- this.auth = (Authenticator) initObject(p.concat(".authenticator"), Authenticator.class);
+ initAuthenticator(manager, p);
         final Session settings = this.fixUpSession();
 
         initEncoding(manager, p);
- initFormatter(p);
- initComparator(p);
+ initFormatter(manager, p);
+ initComparator(manager, p);
         initPushLevel(manager, p);
+ initPushFilter(manager, p);
 
- this.pushFilter = (Filter) initObject(p.concat(".pushFilter"), Filter.class);
+ initSubject(manager, p);
 
- initSubject(p);
+ initAttachmentFormaters(manager, p);
+ initAttachmentFilters(manager, p);
+ initAttachmentNames(manager, p);
 
- this.attachmentFormatters = (Formatter[]) initArray(p.concat(".attachment.formatters"), Formatter.class);
- this.attachmentFilters = (Filter[]) initArray(p.concat(".attachment.filters"), Filter.class);
- this.attachmentNames = (Formatter[]) initArray(p.concat(".attachment.names"), Formatter.class);
-
- fixUpAttachmentFormatters();
-
- if (fixUpAttachmentFilters()) {
- reportError("attachment filters.",
- attachmentMismatch("length mismatch"), ErrorManager.OPEN_FAILURE);
- }
-
- if (fixUpAttachmentNames()) {
- reportError("attachment names.",
- attachmentMismatch("length mismatch"), ErrorManager.OPEN_FAILURE);
- }
         verifySettings(settings);
     }
 
- private /*<T> T*/ Object objectFromNew(final String name,
- final Class/*<T>*/ type) throws NoSuchMethodException {
- Object obj = null;
- try {
- try {
- try {
- Class clazz = LogManagerProperties.findClass(name);
- if (type.isAssignableFrom(clazz)) {
- return clazz.getConstructor((Class[]) null).
- newInstance((Object[]) null);
- } else {
- throw new ClassCastException(clazz.getName()
- + " cannot be cast to " + type.getName());
- }
- } catch (final NoClassDefFoundError NCDFE) {
- throw (ClassNotFoundException) new ClassNotFoundException(
- NCDFE.getMessage()).initCause(NCDFE);
- }
- } catch (final ClassNotFoundException CNFE) {
- if (type == Formatter.class) {
- return /*type.cast(*/ new TailNameFormatter(name);
- } 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.
- } catch (final Exception E) {
- reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
- }
- return obj;
+
+ private static boolean hasValue(final String name) {
+ return name != null && name.length() > 0 && !"null".equalsIgnoreCase(name);
     }
 
- private /*<T> T*/ Object initObject(final String key, Class/*<T>*/ type) {
- String name = LogManagerProperties.manager.getProperty(key);
- if (name != null && name.length() > 0 && !"null".equalsIgnoreCase(name)) {
- try {
- return objectFromNew(name, type);
- } catch (NoSuchMethodException E) {
- reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
- }
- }
- return null;
- }
-
- private /*<T> T[]*/ Object[] initArray(final String key, Class/*<T>*/ type) {
- final String list = LogManagerProperties.manager.getProperty(key);
+ private void initAttachmentFilters(LogManager manager, String p) {
+ assert Thread.holdsLock(this);
+ assert this.attachmentFormatters != null;
+ final String list = manager.getProperty(p.concat(".attachment.filters"));
         if (list != null && list.length() > 0) {
             final String[] names = list.split(",");
- Object[] a = (Object[]) Array.newInstance(type, names.length);
+ Filter[] a = new Filter[names.length];
             for (int i = 0; i < a.length; ++i) {
                 names[i] = names[i].trim();
                 if (!"null".equalsIgnoreCase(names[i])) {
                     try {
- a[i] = objectFromNew(names[i], type);
- } catch (NoSuchMethodException E) {
+ a[i] = LogManagerProperties.newFilter(names[i]);
+ } catch (final SecurityException SE) {
+ throw SE; //avoid catch all.
+ } catch (final Exception E) {
                         reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
                     }
                 }
             }
- return a;
+
+ this.attachmentFilters = a;
+ if (fixUpAttachmentFilters()) {
+ reportError("Attachment filters.",
+ attachmentMismatch("Length mismatch."), ErrorManager.OPEN_FAILURE);
+ }
         } else {
- return /*(T[])*/ (Object[]) Array.newInstance(type, 0);
+ this.attachmentFilters = emptyFilterArray();
+ fixUpAttachmentFilters();
+ }
+ }
+
+ private void initAttachmentFormaters(LogManager manager, String p) {
+ assert Thread.holdsLock(this);
+ final String list = manager.getProperty(p.concat(".attachment.formatters"));
+ if (list != null && list.length() > 0) {
+ final Formatter[] a;
+ final String[] names = list.split(",");
+ if (names.length == 0) {
+ a = emptyFormatterArray();
+ } else {
+ a = new Formatter[names.length];
+ }
+
+ for (int i = 0; i < a.length; ++i) {
+ names[i] = names[i].trim();
+ if (!"null".equalsIgnoreCase(names[i])) {
+ try {
+ a[i] = LogManagerProperties.newFormatter(names[i]);
+ if (a[i] instanceof TailNameFormatter) {
+ a[i] = new SimpleFormatter();
+ final Exception CNFE = new ClassNotFoundException(a[i].toString());
+ reportError("Attachment formatter.", CNFE, ErrorManager.OPEN_FAILURE);
+ }
+ } catch (final SecurityException SE) {
+ throw SE; //avoid catch all.
+ } catch (final Exception E) {
+ a[i] = new SimpleFormatter();
+ reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
+ }
+ } else {
+ a[i] = new SimpleFormatter();
+ final Exception NPE = new NullPointerException(atIndexMsg(i));
+ reportError("Attachment formatter.", NPE, ErrorManager.OPEN_FAILURE);
+ }
+ }
+
+ this.attachmentFormatters = a;
+ } else {
+ this.attachmentFormatters = emptyFormatterArray();
+ }
+ }
+
+ private void initAttachmentNames(LogManager manager, String p) {
+ assert Thread.holdsLock(this);
+ assert this.attachmentFormatters != null;
+
+ final String list = manager.getProperty(p.concat(".attachment.names"));
+ if (list != null && list.length() > 0) {
+ final String[] names = list.split(",");
+ final Formatter[] a = new Formatter[names.length];
+ for (int i = 0; i < a.length; ++i) {
+ names[i] = names[i].trim();
+ if (!"null".equalsIgnoreCase(names[i])) {
+ try {
+ try {
+ a[i] = LogManagerProperties.newFormatter(names[i]);
+ } catch (final ClassNotFoundException literal) {
+ a[i] = new TailNameFormatter(names[i]);
+ } catch (final ClassCastException literal) {
+ a[i] = new TailNameFormatter(names[i]);
+ }
+ } catch (final SecurityException SE) {
+ throw SE; //avoid catch all.
+ } catch (final Exception E) {
+ reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
+ }
+ } else {
+ final Exception NPE = new NullPointerException(atIndexMsg(i));
+ reportError("Attachment names.", NPE, ErrorManager.OPEN_FAILURE);
+ }
+ }
+
+ this.attachmentNames = a;
+ if (fixUpAttachmentNames()) { //any null indexes are repaired.
+ reportError("Attachment names.",
+ attachmentMismatch("Length mismatch."), ErrorManager.OPEN_FAILURE);
+ }
+ } else {
+ this.attachmentNames = emptyFormatterArray();
+ fixUpAttachmentNames();
+ }
+ }
+
+ private void initAuthenticator(LogManager manager, String p) {
+ assert Thread.holdsLock(this);
+ String name = manager.getProperty(p.concat(".authenticator"));
+ if (hasValue(name)) {
+ try {
+ this.auth = LogManagerProperties.newAuthenticator(name);
+ } catch (final SecurityException SE) {
+ throw SE;
+ } catch (final Exception E) {
+ reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
+ }
         }
     }
 
@@ -1466,25 +1518,28 @@
                 super.setLevel(Level.WARNING);
             }
         } catch (final SecurityException SE) {
- throw SE;
+ throw SE; //avoid catch all.
         } catch (final RuntimeException RE) {
             reportError(RE.getMessage(), RE, ErrorManager.OPEN_FAILURE);
             try {
                 super.setLevel(Level.WARNING);
- } catch (RuntimeException fail) {
+ } catch (final RuntimeException fail) {
                 reportError(fail.getMessage(), fail, ErrorManager.OPEN_FAILURE);
             }
         }
     }
 
- private void initFilter(String p) {
+ private void initFilter(LogManager manager, String p) {
         assert Thread.holdsLock(this);
         try {
- super.setFilter((Filter) initObject(p.concat(".filter"), Filter.class));
+ String name = manager.getProperty(p.concat(".filter"));
+ if (hasValue(name)) {
+ super.setFilter(LogManagerProperties.newFilter(name));
+ }
         } catch (final SecurityException SE) {
- throw SE;
- } catch (final RuntimeException RE) {
- reportError(RE.getMessage(), RE, ErrorManager.OPEN_FAILURE);
+ throw SE; //avoid catch all.
+ } catch (final Exception E) {
+ reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
         }
     }
 
@@ -1514,7 +1569,7 @@
         try {
             super.setEncoding(manager.getProperty(p.concat(".encoding")));
         } catch (final SecurityException SE) {
- throw SE;
+ throw SE; //avoid catch all.
         } catch (final UnsupportedEncodingException UEE) {
             reportError(UEE.getMessage(), UEE, ErrorManager.OPEN_FAILURE);
         } catch (final RuntimeException RE) {
@@ -1522,52 +1577,77 @@
         }
     }
 
- private void initFormatter(String p) {
+ private void initErrorManager(LogManager manager, String p) {
         assert Thread.holdsLock(this);
- try {
- final Formatter formatter = (Formatter) initObject(p.concat(".formatter"), Formatter.class);
- if (formatter != null && formatter instanceof TailNameFormatter == false) {
- super.setFormatter(formatter);
- } else {
- super.setFormatter(new SimpleFormatter());
- }
- } catch (final SecurityException SE) {
- throw SE;
- } catch (final RuntimeException RE) {
- reportError(RE.getMessage(), RE, ErrorManager.OPEN_FAILURE);
+ String name = manager.getProperty(p.concat(".errorManager"));
+ if (name != null) {
             try {
- super.setFormatter(new SimpleFormatter());
- } catch (RuntimeException fail) {
- reportError(fail.getMessage(), fail, ErrorManager.OPEN_FAILURE);
+ ErrorManager em = LogManagerProperties.newErrorManager(name);
+ super.setErrorManager(em);
+ } catch (final SecurityException SE) {
+ throw SE; //avoid catch all.
+ } catch (final Exception E) {
+ reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
             }
         }
     }
 
- /*_at_SuppressWarnings("unchecked")*/
- private void initComparator(String p) {
+ private void initFormatter(LogManager manager, String p) {
         assert Thread.holdsLock(this);
- try {
- this.comparator = (Comparator) this.initObject(p.concat(".comparator"), Comparator.class);
- } catch (final Exception RE) {
- reportError(RE.getMessage(), RE, ErrorManager.OPEN_FAILURE);
+ String name = manager.getProperty(p.concat(".formatter"));
+ if (hasValue(name)) {
+ try {
+ final Formatter formatter = LogManagerProperties.newFormatter(name);
+ assert formatter != null;
+ if (formatter instanceof TailNameFormatter == false) {
+ super.setFormatter(formatter);
+ } else {
+ super.setFormatter(new SimpleFormatter());
+ }
+ } catch (final SecurityException SE) {
+ throw SE; //avoid catch all.
+ } catch (final Exception E) {
+ reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
+ try {
+ super.setFormatter(new SimpleFormatter());
+ } catch (final RuntimeException fail) {
+ reportError(fail.getMessage(), fail, ErrorManager.OPEN_FAILURE);
+ }
+ }
+ } else {
+ super.setFormatter(new SimpleFormatter());
         }
+ }
 
- /*try {
- final String reverse = manager.getProperty(p.concat(".comparator.reverse"));
- if (reverse != null) {
- if (Boolean.parseBoolean(reverse)) {
- if (this.comparator != null) {
- this.comparator = Collections.reverseOrder(this.comparator);
- }
- else {
- throw new IllegalArgumentException("No comparator to reverse.");
+ private void initComparator(LogManager manager, String p) {
+ assert Thread.holdsLock(this);
+ String name = manager.getProperty(p.concat(".comparator"));
+ if (hasValue(name)) {
+ try {
+ this.comparator = LogManagerProperties.newComparator(name);
+ } catch (final SecurityException SE) {
+ throw SE; //avoid catch all.
+ } catch (final Exception E) {
+ reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
+ }
+
+ /*try {
+ final String reverse = manager.getProperty(p.concat(".comparator.reverse"));
+ if (reverse != null) {
+ if (Boolean.parseBoolean(reverse)) {
+ if (this.comparator != null) {
+ this.comparator = Collections.reverseOrder(this.comparator);
+ }
+ else {
+ throw new IllegalArgumentException("No comparator to reverse.");
+ }
                     }
                 }
             }
+ catch (final RuntimeException RE) {
+ reportError(RE.getMessage(), RE, ErrorManager.OPEN_FAILURE);
+ }*/
         }
- catch (final RuntimeException RE) {
- reportError(RE.getMessage(), RE, ErrorManager.OPEN_FAILURE);
- }*/
     }
 
     private void initPushLevel(LogManager manager, String p) {
@@ -1586,10 +1666,39 @@
         }
     }
 
- private void initSubject(String p) {
+ private void initPushFilter(LogManager manager, String p) {
         assert Thread.holdsLock(this);
- this.subjectFormatter = (Formatter) initObject(p.concat(".subject"), Formatter.class);
- if (this.subjectFormatter == null) {
+ String name = manager.getProperty(p.concat(".pushFilter"));
+ if (hasValue(name)) {
+ try {
+ this.pushFilter = LogManagerProperties.newFilter(name);
+ } catch (final SecurityException SE) {
+ throw SE; //avoid catch all.
+ } catch (final Exception E) {
+ reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
+ }
+ }
+ }
+
+ private void initSubject(LogManager manager, final String p) {
+ assert Thread.holdsLock(this);
+ String name = manager.getProperty(p.concat(".subject"));
+ if (hasValue(name)) {
+ try {
+ this.subjectFormatter = LogManagerProperties.newFormatter(name);
+ } catch (final SecurityE
[truncated due to length]