/**
* GmailAssistant 2.0 (2008-09-07)
* Copyright 2008 Zach Scrivena
* zachscrivena@gmail.com
* http://gmailassistant.sourceforge.net/
*
* Notifier for multiple Gmail and Google Apps email accounts.
*
* TERMS AND CONDITIONS:
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.freeshell.zs.gmailassistant;
import java.util.ArrayDeque;
import java.util.Date;
import java.util.Deque;
import java.util.regex.Pattern;
import javax.mail.Address;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Part;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.NewsAddress;
import org.freeshell.zs.common.HtmlManipulator;
/**
* <p>Represent an email message retrieved from a mail server.</p>
*
* <p>Instances of this class are <i>naturally ordered</i> by their <code>account</code>
* and <code>sequenceNumber</code> fields, in that order.</p>
*
* <p>Instances of this class are immutable.</p>
*/
class Mail
implements Comparable<Mail>
{
/** maximum length of the email text snippet */
private static final int SNIPPET_MAX_LENGTH = 500;
/** account to which this mail belongs */
final Account account;
/** mail sequence number */
final int sequenceNumber;
/** "from" addresses (senders) */
final String from;
/** "to" addresses (recipients) */
final String to;
/** email sent date */
final Date date;
/** email subject */
final String subject;
/** email text snippet */
final String snippet;
/**
* Constructor.
*
* @param account
* account to which this mail belongs
* @param msg
* Message object corresponding to this mail
* @param sequenceNumber
* sequence number of this mail
* @throws javax.mail.MessagingException
* if thrown by the specified Message object when accessing it
*/
Mail(
final Account account,
final Message msg,
final int sequenceNumber)
throws MessagingException
{
final StringBuilder sb = new StringBuilder();
/* account to which this mail belongs */
this.account = account;
/* mail sequence number */
this.sequenceNumber = sequenceNumber;
/* "from" addresses (senders) */
final Address[] fromAddresses = msg.getFrom();
sb.setLength(0);
if (fromAddresses != null)
{
for (int i = 0; i < fromAddresses.length; i++)
{
if (i > 0)
{
sb.append("; ");
}
sb.append(addressToString(fromAddresses[i]));
}
}
from = sb.toString();
/* "to" addresses (recipients) */
final Address[] toAddresses = msg.getAllRecipients();
sb.setLength(0);
if (toAddresses != null)
{
for (int i = 0; i < toAddresses.length; i++)
{
if (i > 0)
{
sb.append("; ");
}
sb.append(addressToString(toAddresses[i]));
}
}
to = sb.toString();
/* email sent date */
final Date msgDate = msg.getSentDate();
date = (msgDate == null) ? new Date() : msgDate;
/* email subject */
final String msgSubject = msg.getSubject();
subject = (msgSubject == null) ? "(no subject)" : msgSubject;
/* email text snippet */
if (msg instanceof MimeMessage)
{
snippet = getEmailTextSnippet((MimeMessage) msg);
}
else
{
snippet = "";
}
}
/**
* Compare this mail to the specified mail.
* This comparison uses the <code>account</code> and <code>sequenceNumber</code> fields,
* in that order.
*/
public int compareTo(
final Mail m)
{
final int i = account.compareTo(m.account);
if (i != 0) return i;
if (sequenceNumber < m.sequenceNumber) return -1;
if (sequenceNumber > m.sequenceNumber) return 1;
return 0;
}
/**
* Is this mail equal to the specified mail?
* This comparison uses the <code>account</code> and <code>sequenceNumber</code> fields.
*/
@Override
public boolean equals(
final Object o)
{
if (o instanceof Mail)
{
final Mail m = (Mail) o;
return (account.equals(m.account) &&
(sequenceNumber == m.sequenceNumber));
}
return false;
}
/**
* Generate a hashcode for this mail.
* This method uses the <code>account</code> and <code>sequenceNumber</code> fields.
*/
@Override
public int hashCode()
{
int hash = 7;
hash = 31 * hash + (this.account != null ? this.account.hashCode() : 0);
hash = 31 * hash + this.sequenceNumber;
return hash;
}
/**
* Return a string representation of this mail item.
*/
@Override
public String toString()
{
final StringBuilder sb = new StringBuilder();
sb.append("[FROM] ");
sb.append(from);
sb.append(" [TO] ");
sb.append(to);
sb.append(" [SUBJECT] ");
sb.append(subject);
sb.append(" [DATE] ");
sb.append(date);
sb.append(" [SNIPPET] ");
sb.append(snippet);
return sb.toString();
}
/**
* Return an email text snippet for a MIME message.
*
* @param mm
* MIME message
* @return
* text snippet
*/
private static String getEmailTextSnippet(
final MimeMessage mm)
{
/* text snippet */
final StringBuilder sb = new StringBuilder();
/* stack of message parts to be processed */
final Deque<Part> stack = new ArrayDeque<Part>();
stack.push(mm);
while (!stack.isEmpty())
{
/* process a message part */
final Part p = stack.pop();
try
{
if (p.isMimeType("text/plain"))
{
final Object o = p.getContent();
if (o instanceof String)
{
sb.append(' ');
sb.append((String) o);
}
}
else if (p.isMimeType("text/html"))
{
final Object o = p.getContent();
if (o instanceof String)
{
sb.append(' ');
sb.append(HtmlManipulator.replaceHtmlEntities((String) o));
}
}
else if (p.isMimeType("multipart/*"))
{
final Object o = p.getContent();
if (o instanceof Multipart)
{
final Multipart mp = (Multipart) o;
final int n = mp.getCount();
for (int i = n - 1; i >= 0; i--)
{
stack.push(mp.getBodyPart(i));
}
}
}
else if (p.isMimeType("message/rfc822"))
{
final Object o = p.getContent();
if (o instanceof Part)
{
stack.push((Part) o);
}
}
}
catch (Exception e)
{
/* ignore */
}
if (sb.length() >= SNIPPET_MAX_LENGTH)
{
break;
}
}
return sb.toString()
.replaceAll("(?s)<head.*</head>", "") /* ignore HTML header */
.replaceAll("(?s)<[^>]+>", "") /* ignore HTML tags */
.replaceAll("[" + Pattern.quote("~`!@#$%^&*()_-+={[}]|\\:;\"'<,>.?/") + "]{2,}", " ") /* ignore "lines" */
.replaceAll("[\\s\\xA0]++", " ") /* replace consecutive whitespace */
.trim();
}
/**
* Return a suitable string representation of an address.
*
* @param a
* address
* @return
* string representation
*/
private static String addressToString(
final Address a)
{
String s;
if (a instanceof InternetAddress)
{
final InternetAddress ia = (InternetAddress) a;
s = ia.getPersonal();
if ((s == null) || s.isEmpty())
{
s = ia.getAddress();
if ((s == null) || s.isEmpty())
{
s = a.toString();
}
}
}
else if (a instanceof NewsAddress)
{
final NewsAddress na = (NewsAddress) a;
s = na.getNewsgroup();
if ((s == null) || s.isEmpty())
{
s = na.getHost();
if ((s == null) || s.isEmpty())
{
s = a.toString();
}
}
}
else
{
s = a.toString();
}
return s;
}
}