/*
 * This software is distributed under following license based on modified BSD
 * style license.
 * ----------------------------------------------------------------------
 * 
 * Copyright 2009 The Nimbus2 Project. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer. 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE NIMBUS PROJECT ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
 * NO EVENT SHALL THE NIMBUS PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * The views and conclusions contained in the software and documentation are
 * those of the authors and should not be interpreted as representing official
 * policies, either expressed or implied, of the Nimbus2 Project.
 */
package jp.ossc.nimbus.service.writer.mail;

import java.util.*;
import java.io.*;
import javax.activation.*;
import javax.mail.*;
import javax.mail.internet.*;
import javax.naming.NamingException;

import jp.ossc.nimbus.core.*;
import jp.ossc.nimbus.io.CSVReader;
import jp.ossc.nimbus.service.distribute.KeepAliveCheckerSelector;
import jp.ossc.nimbus.service.distribute.SmtpKeepAliveChecker;
import jp.ossc.nimbus.service.jndi.JndiFinder;
import jp.ossc.nimbus.service.writer.*;

/**
 * メール送信サービス。<p>
 * Java Mailでは、Sessionの取得及び設定、Messageの生成及び設定、SMTPサーバへの送信の機能がある。<br>
 * このサービスでは、それらの機能をノンプログラミングで行う事ができる。<br>
 * <p>
 * Java MailでのSessionの取得には、２種類の方法がある。<br>
 * {@link Session#getInstance(Properties, Authenticator)}で、任意のセッション属性を指定したSessionを取得する方法。<br>
 * アプリケーションサーバで設定したSessionをJNDI経由で取得する方法。<br>
 * このサービスでは、その双方をサポートします。<br>
 * また、前者の方法の場合は、{@link #setSessionProperties(Properties)}で、設定したセッション属性を一律に設定する。<br>
 * 但し、以下のセッション属性は、一律指定がない場合は、それぞれ以下の方法で設定される。<br>
 * <table border="1">
 *   <tr bgcolor="#CCCCFF"><th>セッション属性</th><th>デフォルトの設定方法</th></tr>
 *   <tr><td>mail.smtp.host</td><td>{@link #setSmtpKeepAliveCheckerSelectorServiceName(ServiceName)}が指定されている場合は、指定された{@link KeepAliveCheckerSelector}によって選択された{@link SmtpKeepAliveChecker}の{@link SmtpKeepAliveChecker#getHostIp()}で取得した値。<br>そうでない場合は、{@link #setSmtpHostName(String)}で指定された値。</td></tr>
 *   <tr><td>mail.smtp.port</td><td>{@link #setSmtpKeepAliveCheckerSelectorServiceName(ServiceName)}が指定されている場合は、指定された{@link KeepAliveCheckerSelector}によって選択された{@link SmtpKeepAliveChecker}の{@link SmtpKeepAliveChecker#getHostPort()}で取得した値。<br>そうでない場合は、{@link #setSmtpPort(int)}で指定された値。</td></tr>
 *   <tr><td>mail.transport.protocol</td><td>smtp</td></tr>
 *   <tr><td>mail.smtp.from</td><td>{@link #setEnvelopeFromAddressKey(String)}で指定されたキーで、{@link #write(WritableRecord)}の引数の{@link WritableRecord}から取得した値。<br>指定されていない場合は、{@link #setEnvelopeFromAddress(String)}で指定された値。</td></tr>
 * </table>
 * <p>
 * Messageの生成は、javax.mail.MimeMessageを生成します。<br>
 * MimeMessageへの各種設定は、このサービスの各属性で設定可能である。<br>
 * 各属性毎に、２種類の設定方法が用意されている。<br>
 * {@link #write(WritableRecord)}の引数の{@link WritableRecord}から取得するためのキー名を指定する方法と、属性値そのものを設定する方法である。<br>
 * 前者の場合は、このMessageWriterを呼ぶクライアントが引数によって、任意に指定する事が可能で、後者の方法は、このサービスの設定で一律同じ値を設定する事が可能である。<br>
 * ある属性に対して、上記の２種類の設定方法が両方とも設定されている場合には、前者の方が有効となる。<br>
 * また、{@link Message#setSentDate(Date)}だけは、自動的に送信時の時刻が設定される。<br>
 * <p>
 * SMTPサーバへの送信機能としては、複数のSMTPサーバに対してロードバランスしながら送信する機能と、送信失敗時に、一定の間隔をあけて、リトライする機能がある。<br>
 *
 * @author M.Takata
 */
public class MailMessageWriterService extends ServiceBase
 implements MessageWriter, MailMessageWriterServiceMBean{
    
    private static final long serialVersionUID = 8479884337523286206L;
    
    private static final String SESSION_PROPERTY_NAME_HOST = "mail.smtp.host";
    private static final String SESSION_PROPERTY_NAME_PORT = "mail.smtp.port";
    private static final String SESSION_PROPERTY_NAME_TRANSPORT_PROTOCOL = "mail.transport.protocol";
    private static final String SESSION_PROPERTY_VALUE_TRANSPORT_PROTOCOL = "smtp";
    private static final String SESSION_PROPERTY_NAME_FROM = "mail.smtp.from";
    
    private Properties sessionProperties;
    
    private ServiceName authenticatorServiceName;
    private Authenticator authenticator;
    
    private Properties headers;
    private String[] headerKeys;
    
    private String envelopeFromAddressKey;
    private String envelopeFromAddress;
    private boolean isEnvelopeFromAddressValidate;
    
    private String fromAddressKey;
    private String fromAddress;
    private String fromPersonalKey;
    private String fromPersonal;
    private String fromPersonalEncodingKey;
    private String fromPersonalEncoding;
    private boolean isFromAddressValidate;
    
    private String senderAddressKey;
    private String senderAddress;
    private String senderPersonalKey;
    private String senderPersonal;
    private String senderPersonalEncodingKey;
    private String senderPersonalEncoding;
    private boolean isSenderAddressValidate;
    
    private String toAddressKey;
    private String[] toAddress;
    private String toPersonalKey;
    private String[] toPersonals;
    private String toPersonalEncodingKey;
    private String[] toPersonalEncodings;
    private String toPersonalEncoding;
    private boolean isToAddressValidate;
    
    private String ccAddressKey;
    private String[] ccAddress;
    private String ccPersonalKey;
    private String[] ccPersonals;
    private String ccPersonalEncodingKey;
    private String[] ccPersonalEncodings;
    private String ccPersonalEncoding;
    private boolean isCcAddressValidate;
    
    private String bccAddressKey;
    private String[] bccAddress;
    private String bccPersonalKey;
    private String[] bccPersonals;
    private String bccPersonalEncodingKey;
    private String[] bccPersonalEncodings;
    private String bccPersonalEncoding;
    private boolean isBccAddressValidate;
    
    private String replyToAddressKey;
    private String[] replyToAddress;
    private String replyToPersonalKey;
    private String[] replyToPersonals;
    private String replyToPersonalEncodingKey;
    private String[] replyToPersonalEncodings;
    private String replyToPersonalEncoding;
    private boolean isReplyToAddressValidate;
    
    private String subjectKey;
    private String subject;
    private String subjectEncodingKey;
    private String subjectEncoding;
    
    private String contentIDKey;
    private String contentID;
    
    private String contentLanguageKey;
    private String[] contentLanguage;
    
    private String contentMD5Key;
    private String contentMD5;
    
    private String descriptionKey;
    private String description;
    
    private String descriptionEncodingKey;
    private String descriptionEncoding;
    
    private String dispositionKey;
    private String disposition;
    
    private String filePartKey;
    private String fileCharset = MimeUtility.getDefaultJavaCharset();
    private String fileCharsetKey;
    private String fileLanguage;
    private String fileLanguageKey;
    
    private String bodyText;
    private String bodyIndexKey;
    private int bodyIndex = -1;
    private String bodyEncoding;
    
    private String smtpHostName;
    private int smtpPort = 25;
    private ServiceName smtpKeepAliveCheckerSelectorServiceName;
    private KeepAliveCheckerSelector smtpKeepAliveCheckerSelector;
    
    private int retryCount = -1;
    private long retryInterval = -1;
    
    private ServiceName jndiFinderServiceName;
    private JndiFinder jndiFinder;
    private String mailSessionJndiName = DEFAULT_MAIL_SESSION_JNDI_NAME;
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setSessionProperties(Properties prop){
        sessionProperties = prop;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public Properties getSessionProperties(){
        return sessionProperties;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setAuthenticatorServiceName(ServiceName name){
        authenticatorServiceName = name;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public ServiceName getAuthenticatorServiceName(){
        return authenticatorServiceName;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setHeaders(Properties prop){
        headers = prop;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public Properties getHeaders(){
        return headers;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setHeaderKeys(String[] keys){
        headerKeys = keys;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String[] getHeaderKeys(){
        return headerKeys;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setEnvelopeFromAddressKey(String key){
        envelopeFromAddressKey = key;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getEnvelopeFromAddressKey(){
        return envelopeFromAddressKey;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setEnvelopeFromAddress(String address){
        envelopeFromAddress = address;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getEnvelopeFromAddress(){
        return envelopeFromAddress;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setEnvelopeFromAddressValidate(boolean isValidate){
        isEnvelopeFromAddressValidate = isValidate;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public boolean isEnvelopeFromAddressValidate(){
        return isEnvelopeFromAddressValidate;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setFromAddressKey(String key){
        fromAddressKey = key;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getFromAddressKey(){
        return fromAddressKey;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setFromAddress(String address){
        fromAddress = address;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getFromAddress(){
        return fromAddress;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setFromPersonalKey(String key){
        fromPersonalKey = key;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getFromPersonalKey(){
        return fromPersonalKey;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setFromPersonal(String personal){
        fromPersonal = personal;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getFromPersonal(){
        return fromPersonal;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setFromPersonalEncodingKey(String key){
        fromPersonalEncodingKey = key;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getFromPersonalEncodingKey(){
        return fromPersonalEncodingKey;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setFromPersonalEncoding(String encoding){
        fromPersonalEncoding = encoding;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getFromPersonalEncoding(){
        return fromPersonalEncoding;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setFromAddressValidate(boolean isValidate){
        isFromAddressValidate = isValidate;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public boolean isFromAddressValidate(){
        return isFromAddressValidate;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setSenderAddressKey(String key){
        senderAddressKey = key;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getSenderAddressKey(){
        return senderAddressKey;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setSenderAddress(String address){
        senderAddress = address;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getSenderAddress(){
        return senderAddress;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setSenderPersonalKey(String key){
        senderPersonalKey = key;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getSenderPersonalKey(){
        return senderPersonalKey;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setSenderPersonal(String personal){
        senderPersonal = personal;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getSenderPersonal(){
        return senderPersonal;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setSenderPersonalEncodingKey(String key){
        senderPersonalEncodingKey = key;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getSenderPersonalEncodingKey(){
        return senderPersonalEncodingKey;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setSenderPersonalEncoding(String encoding){
        senderPersonalEncoding = encoding;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getSenderPersonalEncoding(){
        return senderPersonalEncoding;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setSenderAddressValidate(boolean isValidate){
        isSenderAddressValidate = isValidate;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public boolean isSenderAddressValidate(){
        return isSenderAddressValidate;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setToAddressKey(String key){
        toAddressKey = key;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getToAddressKey(){
        return toAddressKey;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setToAddress(String[] address){
        toAddress = address;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String[] getToAddress(){
        return toAddress;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setToPersonalKey(String key){
        toPersonalKey = key;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getToPersonalKey(){
        return toPersonalKey;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setToPersonals(String[] personal){
        toPersonals = personal;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String[] getToPersonals(){
        return toPersonals;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setToPersonalEncodingKey(String key){
        toPersonalEncodingKey = key;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getToPersonalEncodingKey(){
        return toPersonalEncodingKey;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setToPersonalEncodings(String[] encoding){
        toPersonalEncodings = encoding;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String[] getToPersonalEncodings(){
        return toPersonalEncodings;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setToPersonalEncoding(String encoding){
        toPersonalEncoding = encoding;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getToPersonalEncoding(){
        return toPersonalEncoding;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setToAddressValidate(boolean isValidate){
        isToAddressValidate = isValidate;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public boolean isToAddressValidate(){
        return isToAddressValidate;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setCcAddressKey(String key){
        ccAddressKey = key;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getCcAddressKey(){
        return ccAddressKey;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setCcAddress(String[] address){
        ccAddress = address;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String[] getCcAddress(){
        return ccAddress;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setCcPersonalKey(String key){
        ccPersonalKey = key;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getCcPersonalKey(){
        return ccPersonalKey;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setCcPersonals(String[] personal){
        ccPersonals = personal;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String[] getCcPersonals(){
        return ccPersonals;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setCcPersonalEncodingKey(String key){
        ccPersonalEncodingKey = key;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getCcPersonalEncodingKey(){
        return ccPersonalEncodingKey;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setCcPersonalEncodings(String[] encoding){
        ccPersonalEncodings = encoding;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String[] getCcPersonalEncodings(){
        return ccPersonalEncodings;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setCcPersonalEncoding(String encoding){
        ccPersonalEncoding = encoding;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getCcPersonalEncoding(){
        return ccPersonalEncoding;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setCcAddressValidate(boolean isValidate){
        isCcAddressValidate = isValidate;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public boolean isCcAddressValidate(){
        return isCcAddressValidate;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setBccAddressKey(String key){
        bccAddressKey = key;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getBccAddressKey(){
        return bccAddressKey;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setBccAddress(String[] address){
        bccAddress = address;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String[] getBccAddress(){
        return bccAddress;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setBccPersonalKey(String key){
        bccPersonalKey = key;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getBccPersonalKey(){
        return bccPersonalKey;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setBccPersonals(String[] personal){
        bccPersonals = personal;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String[] getBccPersonals(){
        return bccPersonals;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setBccPersonalEncodingKey(String key){
        bccPersonalEncodingKey = key;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getBccPersonalEncodingKey(){
        return bccPersonalEncodingKey;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setBccPersonalEncodings(String[] encoding){
        bccPersonalEncodings = encoding;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String[] getBccPersonalEncodings(){
        return bccPersonalEncodings;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setBccPersonalEncoding(String encoding){
        bccPersonalEncoding = encoding;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getBccPersonalEncoding(){
        return bccPersonalEncoding;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setBccAddressValidate(boolean isValidate){
        isBccAddressValidate = isValidate;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public boolean isBccAddressValidate(){
        return isBccAddressValidate;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setReplyToAddressKey(String key){
        replyToAddressKey = key;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getReplyToAddressKey(){
        return replyToAddressKey;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setReplyToAddress(String[] address){
        replyToAddress = address;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String[] getReplyToAddress(){
        return replyToAddress;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setReplyToPersonalKey(String key){
        replyToPersonalKey = key;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getReplyToPersonalKey(){
        return replyToPersonalKey;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setReplyToPersonals(String[] personal){
        replyToPersonals = personal;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String[] getReplyToPersonals(){
        return replyToPersonals;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setReplyToPersonalEncodingKey(String key){
        replyToPersonalEncodingKey = key;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getReplyToPersonalEncodingKey(){
        return replyToPersonalEncodingKey;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setReplyToPersonalEncodings(String[] encoding){
        replyToPersonalEncodings = encoding;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String[] getReplyToPersonalEncodings(){
        return replyToPersonalEncodings;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setReplyToPersonalEncoding(String encoding){
        replyToPersonalEncoding = encoding;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getReplyToPersonalEncoding(){
        return replyToPersonalEncoding;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setReplyToAddressValidate(boolean isValidate){
        isReplyToAddressValidate = isValidate;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public boolean isReplyToAddressValidate(){
        return isReplyToAddressValidate;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setSubjectKey(String key){
        subjectKey = key;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getSubjectKey(){
        return subjectKey;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setSubject(String subject){
        this.subject = subject;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getSubject(){
        return subject;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setSubjectEncodingKey(String key){
        subjectEncodingKey = key;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getSubjectEncodingKey(){
        return subjectEncodingKey;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setSubjectEncoding(String encoding){
        subjectEncoding = encoding;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getSubjectEncoding(){
        return subjectEncoding;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setContentIDKey(String key){
        contentIDKey = key;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getContentIDKey(){
        return contentIDKey;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setContentID(String id){
        this.contentID = id;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getContentID(){
        return contentID;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setContentLanguageKey(String key){
        contentLanguageKey = key;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getContentLanguageKey(){
        return contentLanguageKey;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setContentLanguage(String[] lang){
        contentLanguage = lang;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String[] getContentLanguage(){
        return contentLanguage;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setContentMD5Key(String key){
        contentMD5Key = key;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getContentMD5Key(){
        return contentMD5Key;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setContentMD5(String val){
        contentMD5 = val;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getContentMD5(){
        return contentMD5;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setDescriptionKey(String key){
        descriptionKey = key;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getDescriptionKey(){
        return descriptionKey;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setDescription(String val){
        description = val;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getDescription(){
        return description;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setDescriptionEncodingKey(String key){
        descriptionEncodingKey = key;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getDescriptionEncodingKey(){
        return descriptionEncodingKey;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setDescriptionEncoding(String encoding){
        descriptionEncoding = encoding;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getDescriptionEncoding(){
        return descriptionEncoding;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setDispositionKey(String key){
        dispositionKey = key;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getDispositionKey(){
        return dispositionKey;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setDisposition(String val){
        disposition = val;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getDisposition(){
        return disposition;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setFilePartKey(String key){
        filePartKey = key;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getFilePartKey(){
        return filePartKey;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setFileCharsetKey(String key){
        fileCharsetKey = key;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getFileCharsetKey(){
        return fileCharsetKey;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setFileCharset(String charset){
        fileCharset = charset;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getFileCharset(){
        return fileCharset;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setFileLanguageKey(String key){
        fileLanguageKey = key;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getFileLanguageKey(){
        return fileLanguageKey;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setFileLanguage(String lang){
        fileLanguage = lang;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getFileLanguage(){
        return fileLanguage;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setBodyIndex(int index){
        bodyIndex = index;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public int getBodyIndex(){
        return bodyIndex;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setBodyIndexKey(String key){
        bodyIndexKey = key;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getBodyIndexKey(){
        return bodyIndexKey;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setBodyText(String text){
        bodyText = text;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getBodyText(){
        return bodyText;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setBodyEncoding(String encoding){
        bodyEncoding = encoding;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getBodyEncoding(){
        return bodyEncoding;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setSmtpHostName(String name){
        smtpHostName = name;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getSmtpHostName(){
        return smtpHostName;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setSmtpPort(int port){
        smtpPort = port;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public int getSmtpPort(){
        return smtpPort;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setRetryCount(int count){
        retryCount = count;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public int getRetryCount(){
        return retryCount;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setRetryInterval(long millis){
        retryInterval = millis;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public long getRetryInterval(){
        return retryInterval;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setSmtpKeepAliveCheckerSelectorServiceName(ServiceName name){
        smtpKeepAliveCheckerSelectorServiceName = name;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public ServiceName getSmtpKeepAliveCheckerSelectorServiceName(){
        return smtpKeepAliveCheckerSelectorServiceName;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setJndiFinderServiceName(ServiceName name){
        jndiFinderServiceName = name;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public ServiceName getJndiFinderServiceName(){
        return jndiFinderServiceName;
    }
    
    // MailMessageWriterServiceMBeanのJavaDoc
    public void setMailSessionJndiName(String name){
        mailSessionJndiName = name;
    }
    // MailMessageWriterServiceMBeanのJavaDoc
    public String getMailSessionJndiName(){
        return mailSessionJndiName;
    }
    
    /**
     * サービスの開始処理を行う。<p>
     *
     * @exception Exception サービスの開始処理に失敗した場合
     */
    public void startService() throws Exception{
        if(authenticatorServiceName != null){
            authenticator = (Authenticator)ServiceManagerFactory
                .getServiceObject(authenticatorServiceName);
        }
        
        if(fromAddressKey == null && fromAddress == null){
            throw new IllegalArgumentException("It is necessary to set any of FromAddressKey and FromAddress.");
        }
        if(toAddress != null && toAddress.length != 0){
            if(toPersonals != null && toPersonals.length != toAddress.length){
                throw new IllegalArgumentException("It is necessary to set toAddress and toPersonals to the same length.");
            }
            if(toPersonalEncodings != null && toPersonalEncodings.length != toAddress.length){
                throw new IllegalArgumentException("It is necessary to set toAddress and toPersonalEncodings to the same length.");
            }
        }
        if(ccAddress != null && ccAddress.length != 0){
            if(ccPersonals != null && ccPersonals.length != ccAddress.length){
                throw new IllegalArgumentException("It is necessary to set ccAddress and ccPersonals to the same length.");
            }
            if(ccPersonalEncodings != null && ccPersonalEncodings.length != ccAddress.length){
                throw new IllegalArgumentException("It is necessary to set ccAddress and ccPersonalEncodings to the same length.");
            }
        }
        if(bccAddress != null && bccAddress.length != 0){
            if(bccPersonals != null && bccPersonals.length != bccAddress.length){
                throw new IllegalArgumentException("It is necessary to set bccAddress and bccPersonals to the same length.");
            }
            if(bccPersonalEncodings != null && bccPersonalEncodings.length != bccAddress.length){
                throw new IllegalArgumentException("It is necessary to set bccAddress and bccPersonalEncodings to the same length.");
            }
        }
        
        if(smtpKeepAliveCheckerSelectorServiceName != null){
            smtpKeepAliveCheckerSelector
                 = (KeepAliveCheckerSelector)ServiceManagerFactory
                    .getServiceObject(smtpKeepAliveCheckerSelectorServiceName);
        }
        if(smtpHostName == null && smtpKeepAliveCheckerSelector == null){
            throw new IllegalArgumentException("It is necessary to set any of SmtpHostName and SmtpKeepAliveCheckerSelectorServiceName.");
        }
        
        if(jndiFinderServiceName != null){
            jndiFinder = (JndiFinder)ServiceManagerFactory
                .getServiceObject(jndiFinderServiceName);
        }
    }
    
    /**
     * 指定されたレコードをメール送信する。<p>
     *
     * @param rec メール送信するレコード
     * @exception MessageWriteException メール送信に失敗した場合
     */
    public void write(WritableRecord rec) throws MessageWriteException{
        
        int maxCount = retryCount > 0 ? retryCount : 0;
        Exception exception = null;
        for(int count = 0; count <= maxCount; count++){
            try{
                sendMail(rec);
                return;
            }catch(NamingException e){
                exception = e;
            }catch(IllegalWriteException e){
                exception = e;
            }catch(MessagingException e){
                exception = e;
            }
            if(retryInterval > 0){
                try{
                    Thread.sleep(retryInterval);
                }catch(InterruptedException e){
                }
            }
        }
        throw new MessageWriteException(exception);
    }
    
    /**
     * 指定されたレコードをメール送信する。<p>
     *
     * @param rec メール送信するレコード
     * @exception MessageWriteException javax.mail.Messageに設定する値が不正な場合、またはSMTPサーバが死んでいる場合
     * @exception IllegalWriteException javax.mail.Messageの読み取り専用属性に書き込みが行われた場合
     * @exception MessagingException メール送信に失敗した場合
     * @exception NamingException javax.mail.SessionをJNDIからlookupするのに失敗した場合
     */
    public void sendMail(WritableRecord rec)
     throws MessageWriteException, IllegalWriteException, MessagingException,
            NamingException{
        
        final Map<Object, WritableElement> elementMap = rec.getElementMap();
        
        // Fromアドレスを作成する
        final InternetAddress fromInternetAddress = createAddress(
            elementMap,
            fromAddress,
            fromPersonal,
            fromPersonalEncoding,
            fromAddressKey,
            fromPersonalKey,
            fromPersonalEncodingKey,
            isFromAddressValidate
        );
        if(fromInternetAddress == null){
            throw new MessageWriteException("The From address is null.");
        }
        
        // Senderアドレスを作成する
        final InternetAddress senderInternetAddress = createAddress(
            elementMap,
            senderAddress,
            senderPersonal,
            senderPersonalEncoding,
            senderAddressKey,
            senderPersonalKey,
            senderPersonalEncodingKey,
            isSenderAddressValidate
        );
        
        // Toアドレスを作成する
        final InternetAddress[] toInternetAddress = createAddressArray(
            elementMap,
            toAddress,
            toPersonals,
            toPersonalEncodings,
            toPersonalEncoding,
            toAddressKey,
            toPersonalKey,
            toPersonalEncodingKey,
            isToAddressValidate
        );
        
        // Ccアドレスを作成する
        final InternetAddress[] ccInternetAddress = createAddressArray(
            elementMap,
            ccAddress,
            ccPersonals,
            ccPersonalEncodings,
            ccPersonalEncoding,
            ccAddressKey,
            ccPersonalKey,
            ccPersonalEncodingKey,
            isCcAddressValidate
        );
        
        // Bccアドレスを作成する
        final InternetAddress[] bccInternetAddress = createAddressArray(
            elementMap,
            bccAddress,
            bccPersonals,
            bccPersonalEncodings,
            bccPersonalEncoding,
            bccAddressKey,
            bccPersonalKey,
            bccPersonalEncodingKey,
            isBccAddressValidate
        );
        if((toInternetAddress == null || toInternetAddress.length == 0)
            && (ccInternetAddress == null || ccInternetAddress.length == 0)
            && (bccInternetAddress == null || bccInternetAddress.length == 0)
        ){
            throw new MessageWriteException("The destination address is null.");
        }
        
        // ReplyToアドレスを作成する
        final InternetAddress[] replyToInternetAddress = createAddressArray(
            elementMap,
            replyToAddress,
            replyToPersonals,
            replyToPersonalEncodings,
            replyToPersonalEncoding,
            replyToAddressKey,
            replyToPersonalKey,
            replyToPersonalEncodingKey,
            isReplyToAddressValidate
        );
        
        // 本文を作成する
        String body = bodyText;
        final int bIndex = getIntValue(elementMap, bodyIndex, bodyIndexKey);
        if(bIndex >= 0){
            List<WritableElement> elements = rec.getElements();
            if(elements.size() > bIndex){
                final StringBuilder buf = new StringBuilder();
                for(int i = bIndex, imax = elements.size(); i < imax; i++){
                    WritableElement element = elements.get(i);
                    if(element != null){
                        buf.append(element);
                    }
                }
                body = buf.toString();
            }
        }
        Session session = null;
        if(jndiFinder != null){
            session = (Session)jndiFinder.lookup(mailSessionJndiName);
        }else{
            
            final Properties props = new Properties(System.getProperties());
            
            if(smtpKeepAliveCheckerSelector != null){
                final SmtpKeepAliveChecker checker
                     = (SmtpKeepAliveChecker)smtpKeepAliveCheckerSelector
                        .selectChecker(rec);
                if(checker == null){
                    throw new MessageWriteException("All smtp server is dead.");
                }
                // SMTPサーバのホスト名を設定する
                props.setProperty(
                    SESSION_PROPERTY_NAME_HOST,
                    checker.getHostIp()
                );
                // SMTPサーバのポート番号を設定する
                props.setProperty(
                    SESSION_PROPERTY_NAME_PORT,
                    String.valueOf(checker.getHostPort())
                );
            }else{
                // SMTPサーバのホスト名を設定する
                props.setProperty(SESSION_PROPERTY_NAME_HOST, smtpHostName);
                // SMTPサーバのポート番号を設定する
                props.setProperty(
                    SESSION_PROPERTY_NAME_PORT,
                    String.valueOf(smtpPort)
                );
            }
            
            // 送信プロトコルを設定する
            props.setProperty(
                SESSION_PROPERTY_NAME_TRANSPORT_PROTOCOL,
                SESSION_PROPERTY_VALUE_TRANSPORT_PROTOCOL
            );
            
            // envelope-fromアドレスを作成する
            final InternetAddress envelopeFromInternetAddress = createAddress(
                elementMap,
                envelopeFromAddress,
                null,
                null,
                envelopeFromAddressKey,
                null,
                null,
                isEnvelopeFromAddressValidate
            );
            if(envelopeFromInternetAddress != null){
                // envelope-fromアドレスを設定する
                props.setProperty(
                    SESSION_PROPERTY_NAME_FROM,
                    envelopeFromInternetAddress.getAddress()
                );
            }
            
            if(sessionProperties != null){
                props.putAll(sessionProperties);
            }
            
            session = Session.getInstance(props, authenticator);
        }
        final MimeMessage message = new MimeMessage(session);
        
        // Content-IDを設定する
        String val = getStringValue(elementMap, contentID, contentIDKey);
        if(val != null){
            message.setContentID(val);
        }
        
        // Content-Languageを設定する
        String[] vals = getStringArrayValue(
            elementMap,
            contentLanguage,
            contentLanguageKey
        );
        if(vals != null){
            message.setContentLanguage(vals);
        }
        
        // Content-MD5を設定する
        val = getStringValue(elementMap, contentMD5, contentMD5Key);
        if(val != null){
            message.setContentMD5(val);
        }
        
        // Content-Descriptionを設定する
        val = getStringValue(elementMap, description, descriptionKey);
        if(val != null){
            final String enc = getStringValue(
                elementMap,
                descriptionEncoding,
                descriptionEncodingKey
            );
            if(enc == null){
                message.setDescription(val);
            }else{
                message.setDescription(val, enc);
            }
        }
        
        // Content-Dispositionを設定する
        val = getStringValue(elementMap, disposition, dispositionKey);
        if(val != null){
            message.setDisposition(val);
        }

        // Fromアドレスを設定する
        message.setFrom(fromInternetAddress);
        
        // Senderアドレスを設定する
        if(senderInternetAddress != null){
            message.setSender(senderInternetAddress);
        }
        
        // Toアドレスを設定する
        if(toInternetAddress != null){
            for(int i = 0; i < toInternetAddress.length; i++){
                message.addRecipient(
                    Message.RecipientType.TO,
                    toInternetAddress[i]
                );
            }
        }
        
        // Ccアドレスを設定する
        if(ccInternetAddress != null){
            for(int i = 0; i < ccInternetAddress.length; i++){
                message.addRecipient(
                    Message.RecipientType.CC,
                    ccInternetAddress[i]
                );
            }
        }
        
        // Bccアドレスを設定する
        if(bccInternetAddress != null){
            for(int i = 0; i < bccInternetAddress.length; i++){
                message.addRecipient(
                    Message.RecipientType.BCC,
                    bccInternetAddress[i]
                );
            }
        }
        
        // ReplyToアドレスを設定する
        if(replyToInternetAddress != null){
            message.setReplyTo(replyToInternetAddress);
        }
        
        // 題名を設定する
        String enc =
            getStringValue(elementMap, subjectEncoding, subjectEncodingKey);
        if(enc != null){
            message.setSubject(
                getStringValue(elementMap, subject, subjectKey),
                enc
            );
        }else{
            message.setSubject(
                getStringValue(elementMap, subject, subjectKey)
            );
        }
        
        File[] files = null;
        if(filePartKey != null){
            files = getFileArrayValue(elementMap, filePartKey);
        }
        if(files != null && files.length != 0){
            
            final MimeMultipart mp = new MimeMultipart();
            
            // 本文を設定する
            final MimeBodyPart textPart = new MimeBodyPart();
            if(bodyEncoding == null){
                textPart.setText(body);
            }else{
                textPart.setText(body, bodyEncoding);
            }
            mp.addBodyPart(textPart);
            
            // 添付ファイルを添付する
            for(int i = 0; i < files.length; i++){
                final MimeBodyPart filePart = new MimeBodyPart();
                filePart.setDataHandler(
                    new DataHandler(new FileDataSource(files[i]))
                );
                setFileName(
                    filePart,
                    files[i].getName(),
                    getStringValue(elementMap, fileCharset, fileCharsetKey),
                    getStringValue(elementMap, fileLanguage, fileLanguageKey)
                );
                mp.addBodyPart(filePart);
            }
            
            message.setContent(mp);
        }else{
            // 本文を設定する
            if(bodyEncoding == null){
                message.setText(body);
            }else{
                message.setText(body, bodyEncoding);
            }
        }
        
        // ヘッダを設定する
        if(headers != null){
            for(Map.Entry<Object, Object> entry : headers.entrySet()){
                message.setHeader((String)entry.getKey(), (String)entry.getValue());
            }
        }
        if(headerKeys != null){
            for(int i = 0; i < headerKeys.length; i++){
                final String header = getStringValue(
                    elementMap,
                    null,
                    headerKeys[i]
                );
                if(header != null){
                    message.setHeader(headerKeys[i], header);
                }
            }
        }

        // 送信時刻を設定する
        message.setSentDate(new Date());
        
        // 設定を有効にする
        message.saveChanges();
        
        // メールを送信する
        Transport.send(message);
    }
    
    private void setFileName(
        Part part,
        String filename,
        String charset,
        String lang
    ) throws MessagingException{
        
        ContentDisposition disposition;
        String[] strings = part.getHeader("Content-Disposition");
        if(strings == null || strings.length < 1){
            disposition = new ContentDisposition(Part.ATTACHMENT);
        }else{
            disposition = new ContentDisposition(strings[0]);
            disposition.getParameterList().remove("filename");
        }
        
        part.setHeader(
            "Content-Disposition",
            disposition.toString()
                + encodeParameter("filename", filename, charset, lang)
        );
        
        ContentType cType;
        strings = part.getHeader("Content-Type");
        if(strings == null || strings.length < 1){
            cType = new ContentType(part.getDataHandler().getContentType());
        }else{
            cType = new ContentType(strings[0]);
        }
        
        try{
            // I want to public the MimeUtility#doEncode()!!!
            String mimeString = MimeUtility.encodeWord(filename, charset, "B");
            // cut <CRLF>...
            StringBuilder sb = new StringBuilder();
            int i;
            while((i = mimeString.indexOf('\r')) != -1){
                sb.append(mimeString.substring(0, i));
                mimeString = mimeString.substring(i + 2);
            }
            sb.append(mimeString);
            
            cType.setParameter("name", new String(sb));
        }catch(UnsupportedEncodingException e){
            throw new MessagingException("Encoding error", e);
        }
        part.setHeader("Content-Type", cType.toString());
    }
    
    private String encodeParameter(
        String name,
        String value,
        String encoding,
        String lang
    ){
        StringBuilder result = new StringBuilder();
        StringBuilder encodedPart = new StringBuilder();
        
        boolean needWriteCES = !isAllAscii(value);
        boolean CESWasWritten = false;
        boolean encoded;
        boolean needFolding = false;
        int sequenceNo = 0;
        int column;
        
        while(value.length() > 0){
            // index of boundary of ascii/non ascii
            int lastIndex;
            boolean isAscii = value.charAt(0) < 0x80;
            for(lastIndex = 1; lastIndex < value.length(); lastIndex++){
                if(value.charAt(lastIndex) < 0x80){
                    if(!isAscii) break;
                }else{
                    if(isAscii) break;
                }
            }
            if(lastIndex != value.length()) needFolding = true;
            
            RETRY:
            while(true){
                encodedPart.delete(0, encodedPart.length());
                String target = value.substring(0, lastIndex);
                
                byte[] bytes;
                try{
                    if(isAscii){
                        bytes = target.getBytes("us-ascii");
                    }else{
                        bytes = target.getBytes(encoding);
                    }
                }catch(UnsupportedEncodingException e){
                    bytes = target.getBytes(); // use default encoding
                    encoding = MimeUtility.mimeCharset(
                        MimeUtility.getDefaultJavaCharset()
                    );
                }
                
                encoded = false;
                // It is not strict.
                column = name.length() + 7; // size of " " and "*nn*=" and ";"
                
                for(int i = 0; i < bytes.length; i++){
                    if(bytes[i] > ' ' && bytes[i] < 'z'
                         && HeaderTokenizer.MIME.indexOf((char)bytes[i]) < 0){
                        encodedPart.append((char)bytes[i]);
                        column++;
                    }else{
                        encoded = true;
                        encodedPart.append('%');
                        String hex  = Integer.toString(bytes[i] & 0xff, 16);
                        if(hex.length() == 1){
                            encodedPart.append('0');
                        }
                        encodedPart.append(hex);
                        column += 3;
                    }
                    if(column > 76){
                        needFolding = true;
                        lastIndex /= 2;
                        continue RETRY;
                    }
                }
                
                result.append(";\r\n ").append(name);
                if(needFolding){
                    result.append('*').append(sequenceNo);
                    sequenceNo++;
                }
                if(!CESWasWritten && needWriteCES){
                    result.append("*=");
                    CESWasWritten = true;
                    result.append(encoding).append('\'');
                    if(lang != null) result.append(lang);
                    result.append('\'');
                }else if(encoded){
                    result.append("*=");
                }else{
                    result.append('=');
                }
                result.append(new String(encodedPart));
                value = value.substring(lastIndex);
                break;
            }
        }
        return result.toString();
    }
    
    private boolean isAllAscii(String text){
        for(int i = 0; i < text.length(); i++){
            if(text.charAt(i) > 0x7f){ // non-ascii
                return false;
            }
        }
        return true;
    }
    
    @SuppressWarnings("unchecked")
    private String[] toStringArray(Object obj){
        if(obj == null){
            return null;
        }
        String[] result = null;
        if(obj instanceof String[]){
            result = (String[])obj;
        }else if(obj instanceof Collection){
            final Collection<Object> col = (Collection<Object>)obj;
            if(col.size() != 0){
                result = new String[col.size()];
                int index = 0;
                for(Object element : col){
                    result[index++] = element.toString();
                }
            }
        }else{
            result = CSVReader.toArray(
                obj.toString(),
                ',',
                '\\',
                '"',
                "",
                null,
                true,
                false,
                true,
                false
            );
        }
        return result;
    }
    
    private InternetAddress[] createAddressArray(
        Map<Object, WritableElement> elementMap,
        String[] defaultAddress,
        String[] defaultPersonals,
        String[] defaultPersonalEncodings,
        String defaultPersonalEncoding,
        String addressKey,
        String personalKey,
        String personalEncodingKey,
        boolean isAddressValidate
    ) throws MessageWriteException{
        final String[] address = getStringArrayValue(
            elementMap,
            defaultAddress,
            addressKey
        );
        if(address == null || address.length == 0){
            return null;
        }
        String[] personals = null;
        String[] personalEncodings = null;
        if(address == defaultAddress){
            personals = defaultPersonals;
            personalEncodings = defaultPersonalEncodings;
        }else{
            personals = getStringArrayValue(
                elementMap,
                null,
                personalKey
            );
            if(personals != null && personals.length != address.length){
                throw new MessageWriteException("It is necessary to set address and personals to the same length.");
            }
            
            personalEncodings = getStringArrayValue(
                elementMap,
                null,
                personalEncodingKey
            );
            if(personalEncodings != null && personalEncodings.length != address.length){
                throw new MessageWriteException("It is necessary to set address and personalEncodings to the same length.");
            }
        }
        final InternetAddress[] internetAddress
             = new InternetAddress[address.length];
        for(int i = 0; i < address.length; i++){
            internetAddress[i] = createAddress(
                address[i],
                personals == null ? null : personals[i],
                personalEncodings == null ? defaultPersonalEncoding : personalEncodings[i],
                isAddressValidate
            );
        }
        return internetAddress;
    }
    
    private InternetAddress createAddress(
        Map<Object, WritableElement> elementMap,
        String defaultAddress,
        String defaultPersonal,
        String defaultPersonalEncoding,
        String addressKey,
        String personalKey,
        String personalEncodingKey,
        boolean isAddressValidate
    ) throws MessageWriteException{
        final String address = getStringValue(
            elementMap,
            defaultAddress,
            addressKey
        );
        if(address == null){
            return null;
        }
        String personal = getStringValue(
            elementMap,
            defaultPersonal,
            personalKey
        );
        String personalEncoding = getStringValue(
            elementMap,
            defaultPersonalEncoding,
            personalEncodingKey
        );
        return createAddress(
            address,
            personal,
            personalEncoding,
            isAddressValidate
        );
    }
    
    private InternetAddress createAddress(
        String address,
        String personal,
        String personalEncoding,
        boolean isAddressValidate
    ) throws MessageWriteException{
        InternetAddress internetAddress = null;
        try{
            if(personal != null && personalEncoding != null){
                internetAddress = new InternetAddress(
                    address,
                    personal,
                    personalEncoding
                );
            }else if(personal != null){
                internetAddress = new InternetAddress(
                    address,
                    personal
                );
            }else{
                internetAddress = new InternetAddress(address);
            }
            if(isAddressValidate){
                internetAddress.validate();
            }
        }catch(UnsupportedEncodingException e){
            throw new MessageWriteException(e);
        }catch(AddressException e){
            throw new MessageWriteException(e);
        }
        return internetAddress;
    }
    
    private String[] getStringArrayValue(Map<Object, WritableElement> elementMap, String[] defaultVal, String key){
        String[] val = defaultVal;
        if(key != null){
            final WritableElement tmpElement
                 = (WritableElement)elementMap.get(key);
            if(tmpElement != null){
                Object tmpVal = tmpElement.toObject();
                if(tmpVal != null){
                    final String[] tmpArray = toStringArray(tmpVal);
                    if(tmpArray != null && tmpArray.length != 0){
                        val = tmpArray;
                    }
                }
            }
        }
        return val;
    }
    
    private String getStringValue(Map<Object, WritableElement> elementMap, String defaultVal, String key){
        Object val = getValue(elementMap, defaultVal, key);
        return val == null ? null : val.toString();
    }
    
    private int getIntValue(Map<Object, WritableElement> elementMap, int defaultVal, String key){
        Integer val = (Integer)getValue(elementMap, null, key);
        return val == null ? defaultVal : val.intValue();
    }
    
    private Object getValue(Map<Object, WritableElement> elementMap, Object defaultVal, String key){
        Object val = defaultVal;
        if(key != null){
            final WritableElement tmpElement
                 = (WritableElement)elementMap.get(key);
            if(tmpElement != null){
                Object tmpVal = tmpElement.toObject();
                if(tmpVal != null){
                    val = tmpVal;
                }
            }
        }
        return val;
    }
    
    @SuppressWarnings("unchecked")
    private File[] getFileArrayValue(Map<Object, WritableElement> elementMap, String key){
        File[] val = null;
        if(key != null){
            final WritableElement tmpElement
                 = (WritableElement)elementMap.get(key);
            if(tmpElement != null){
                Object tmpVal = tmpElement.toObject();
                if(tmpVal != null){
                    if(tmpVal instanceof File[]){
                        val = (File[])tmpVal;
                    }else if(tmpVal instanceof Collection){
                        final Collection<Object> col = (Collection<Object>)tmpVal;
                        if(col.size() != 0){
                            val = new File[col.size()];
                            int index = 0;
                            for(Object element : col){
                                val[index++] = (File)element;
                            }
                        }
                    }
                }
            }
        }
        return val;
    }
}
