/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.java.sip.communicator.impl.protocol.jabber.extensions.mailnotification; import java.io.*; import java.text.*; import java.util.*; import org.xmlpull.v1.*; /** * This class represents the "mail-thread-info" element the Google use in their * mail notifications to deliver detailed thread information. * * @author Emil Ivov */ public class MailThreadInfo { /** * The name of the "mail-thread-info" element. */ public static final String ELEMENT_NAME = "mail-thread-info"; /** * The name of the XML tag element containing the list of all senders. */ public static final String SENDERS_ELEMENT_NAME = "senders"; /** * The name of the XML tag element containing a pipe separated list of * labels assigned to this thread. */ public static final String LABELS_ELEMENT_NAME = "labels"; /** * The name of the XML tag element containing the thread subject. */ public static final String SUBJECT_ELEMENT_NAME = "subject"; /** * The name of the XML tag element containing a snippet of the thread. */ public static final String SNIPPET_ELEMENT_NAME = "snippet"; /** * Contains the list of senders that have participated in this thread. */ private List<Sender> senders = new LinkedList<Sender>(); /** * The format that we are using to display dates when generating html. */ private String formattedDate = null; /** * The thread id of this thread. */ private String tid = null; /** * Indicates that the local user has not participated in this thread. */ public static final int PARTICIPATION_NONE = 0; /** * Indicates that the user is one of many recipients listed in the thread. */ public static final int PARTICIPATION_ONE_OF_MANY = 1; /** * PARTICIPATION_SOLE_RECIPIENT indicates that the user is the sole * recipient for messages in this thread. */ public static final int PARTICIPATION_SOLE_RECIPIENT = 2; /** * A number indicating the local user's participation level in this thread. * One of the PARTICIPATION_XXX constants defined above. */ private int participation = -1; /** * The number of messages in the thread. */ private int messages; /** * A timestamp of the most recent message, in milliseconds since the UNIX * epoch. */ private long date = -1; /** * The URL linking to this thread (as opposed to the URL delivered in the * <tt>MailboxIQ</tt> that links to the mailbox itself). */ private String url = null; /** * A tag that contains a pipe ('|') delimited list of labels applied to * this thread. */ private String labels = null; /** * The subject of this e-mail thread. */ private String subject = null; /** * An html-encoded snippet from the body of the email. */ private String snippet = null; /** * The class describes a single participant in this email thread. */ public class Sender { /** * The name of the XML tag element containing information for an * individual sender and represented by this class. */ public static final String ELEMENT_NAME = "sender"; /** * The email address of the sender. */ public String address = null; /** * The display name of the sender. */ public String name = null; /** * Indicates whether this sender originated this thread. */ public boolean originator = false; /** * Indicates whether or not the thread contains an unread message from * this sender. */ public boolean unread = false; /** * Tries to parse and return the first name of this sender. * * @return the first name of this sender. */ public String getFirstName() { if(name == null || name.trim().length() == 0) { return null; } String[] names = name.split("\\s"); String result = names[0]; //return 14 chars max if(result.length() > 14) result = result.substring(0, 14); return result; } } /** * Returns the participation index for this thread. The participation index * is a number indicating the local user's participation level in this * thread: PARTICIPATION_NONE indicates that the user has not participated; * PARTICIPATION_ONE_OF_MANY indicates that the user is one of many * recipients listed in the thread; PARTICIPATION_SOLE_RECIPIENT indicates * that the user is the sole recipient for messages in this thread. * * @return one of the PARTICIPATION_XXX values defines in this class and * indicating whether the local is the sole, one of many or not a * participant of this thread. */ public int getParticipation() { return participation; } /** * Specifies the participation index for this thread. The participation * index is a number indicating the local user's participation level in * this thread: PARTICIPATION_NONE indicates that the user has not * participated; PARTICIPATION_ONE_OF_MANY indicates that the user is one of * many recipients listed in the thread; PARTICIPATION_SOLE_RECIPIENT * indicates that the user is the sole recipient for messages in this * thread. * * @param participation one of the PARTICIPATION_XXX values defines in this * class and indicating whether the local is the sole, one of many or not a * participant of this thread. */ protected void setParticipation(int participation) { this.participation = participation; } /** * Returns an iterator over a list of one or more sender instances, each of * which describes a participant in this thread. * * @return an iterator over a list of one or more sender instances, each of * which describes a participant in this thread. */ public Iterator<Sender> senders() { return senders.iterator(); } /** * Returns the number of people that have been posting in this thread. * * @return the number of people that have been posting in this thread. */ public int getSenderCount() { return senders.size(); } /** * Returns the number of people that have been posting in this thread and * that we have unread messages from. * * @return the number of people that have been posting in this thread and * that we have unread messages from. */ public int getUnreadSenderCount() { Iterator<Sender> senders = senders(); int count = 0; while(senders.hasNext()) { if(senders.next().unread) count ++; } return count; } /** * Returns the sender that initiated the thread or the first sender in the * list if for some reason we couldn't determine the originator. * * @param firstNameOnly use only first name * @return the sender that initiated the thread or the first sender in the * list if for some reason we couldn't determine the originator. */ public String findOriginator(boolean firstNameOnly) { return null; } /** * Adds <tt>sender</tt> to the list of senders in this thread. * * @param sender the sender that we are adding. */ protected void addSender(Sender sender) { senders.add(sender); } /** * Returns the number of messages in this thread. * * @return the number of messages in this thread. */ public int getMessageCount() { return messages; } /** * Sets the number of messages in this thread. * * @param messageCount the number of messages in this thread. */ protected void setMessageCount(int messageCount) { this.messages = messageCount; } /** * Returns the date of the most recent message in this thread. * * @return a timestamp of the most recent message, in milliseconds since * the UNIX epoch. */ public long getDate() { return date; } /** * Returns a human readable date. * * @return a human readable date */ private String getFormattedDate() { if (formattedDate != null) return formattedDate; StringBuffer dateBuff = new StringBuffer(); Calendar now = Calendar.getInstance(); Date threadDate = new Date(getDate()); Calendar threadDateCal = Calendar.getInstance(); threadDateCal.setTime(new Date(getDate())); DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.DEFAULT); DateFormat timeFormat = DateFormat.getTimeInstance(DateFormat.SHORT); if (now.get(Calendar.YEAR) != threadDateCal.get(Calendar.YEAR) || now.get(Calendar.MONTH) != threadDateCal.get(Calendar.MONTH) || now.get(Calendar.DAY_OF_MONTH) != threadDateCal.get(Calendar.DAY_OF_MONTH)) { //the message is not from today so include the full date. dateBuff.append(dateFormat.format(threadDate)); } dateBuff.append(" ").append(timeFormat.format(threadDate)); return dateBuff.toString(); } /** * Sets the date of the most recent message in this thread. * * @param date a timestamp of the most recent message in this thread. */ protected void setDate(long date) { this.date = date; } /** * Returns an URL linking to this thread. It is important to distinguish * between this URL and the one returned by the <tt>MailboxIQ</tt> which * points to the whole mailbox. * * @return the URL linking to this particular thread. */ public String getURL() { return url; } /** * Sets an URL linking to this thread. It is important to distinguish * between this URL and the one returned by the <tt>MailboxIQ</tt> which * points to the whole mailbox. * * @param url the URL linking to this particular thread. */ protected void setURL(String url) { this.url = url; } /** * Returns a pipe ('|') delimited list of labels applied to this thread. * * @return a pipe ('|') delimited list of labels applied to this thread. */ public String getLabels() { return labels; } /** * Sets a pipe ('|') delimited list of labels that apply to this thread. * * @param labels a pipe ('|') delimited list of labels that apply to this * thread. */ protected void setLabels(String labels) { this.labels = labels; } /** * Returns the ID of this thread. * * @return the ID of this thread. */ public String getTid() { return tid; } /** * Specifies the ID of this thread. * * @param tid the ID of this thread. */ protected void setTid(String tid) { this.tid = tid; } /** * Returns the subject of this e-mail thread. * * @return the subject of this e-mail thread. */ public String getSubject() { return subject; } /** * Sets the subject of this e-mail thread. * * @param subject the subject of this e-mail thread. */ protected void setSubject(String subject) { this.subject = subject; } /** * Returns an html-encoded snippet from the body of the email. * * @return an html-encoded snippet from the body of the email. */ public String getSnippet() { return snippet; } /** *Sets an html-encoded snippet from the body of the email. * * @param snippet an html-encoded snippet from the body of the email. */ protected void setSnippet(String snippet) { this.snippet = snippet; } /** * Creates and initializes a <tt>MailThreadInfo</tt> instance according to * the details that come with the parser. * * @param parser the parse that we are to read the <tt>MailThreadInfo</tt> * from. * * @return the newly created <tt>MailThreadInfo</tt> instance. * * @throws XmlPullParserException if something goes wrong while parsing * the document. * @throws NumberFormatException in case we fail to parse any of the * elements that we expect to be numerical. * @throws IOException in case reading the input xml fails. */ public static MailThreadInfo parse(XmlPullParser parser) throws XmlPullParserException, NumberFormatException, IOException { MailThreadInfo info = new MailThreadInfo(); //we start by parsing the thread tag itself which should look something //like this: // <mail-thread-info tid='1172320964060972012' participation='1' // messages='28' date='1118012394209' // url='http://mail.google.com/mail?view=cv'> info.setTid(parser.getAttributeValue("", "tid")); String participationStr = parser.getAttributeValue("", "participation"); if(participationStr != null) info.setParticipation( Integer.parseInt( participationStr )); String messagesStr = parser.getAttributeValue("", "messages"); if( messagesStr != null ) info.setMessageCount( Integer.parseInt( messagesStr )); String dateStr = parser.getAttributeValue("", "date"); if(dateStr != null) info.setDate( Long.parseLong( dateStr )); info.setURL( parser.getAttributeValue("", "url")); //now parse the rest of the message int eventType = parser.next(); while(eventType != XmlPullParser.END_TAG) { if (eventType == XmlPullParser.START_TAG) { String name = parser.getName(); if(SENDERS_ELEMENT_NAME.equals(name)) { //senders info.parseSenders(parser); } else if( LABELS_ELEMENT_NAME.equals(name)) { //labels info.setLabels(parser.nextText()); } else if( SUBJECT_ELEMENT_NAME.equals(name)) { //subject info.setSubject(parser.nextText()); } else if( SNIPPET_ELEMENT_NAME.equals(name)) { //snippet info.setSnippet(parser.nextText()); } } eventType = parser.next(); } return info; } /** * Parses a list of senders for this thread. * * @param parser the parser containing the sender list. * * @throws XmlPullParserException if something goes wrong while parsing * the document. * @throws NumberFormatException in case we fail to parse any of the * elements that we expect to be numerical. * @throws IOException in case reading the input xml fails. */ private void parseSenders(XmlPullParser parser) throws XmlPullParserException, NumberFormatException, IOException { //This method parses the list of senders in google mail notifications //following is an example of what such a list could look like: // //<senders> // <sender name='Me' address='romeo@gmail.com' originator='1' /> // <sender name='Benvolio' address='benvolio@gmail.com' /> // <sender name='Mercutio' address='mercutio@gmail.com' unread='1'/> //</senders> int eventType = parser.next(); //looping all senders while(eventType != XmlPullParser.END_TAG) { //looping a single sender ... or in other words not really looping //but just making sure that we consume the end tag. while(eventType != XmlPullParser.END_TAG) { String name = parser.getName(); if(Sender.ELEMENT_NAME.equals(name)) { Sender sender = new Sender(); sender.address = parser.getAttributeValue("", "address"); sender.name = parser.getAttributeValue("", "name"); String originatorStr = parser.getAttributeValue("", "originator"); if(originatorStr != null) sender.originator = (Integer.parseInt(originatorStr) == 1); String unreadStr = parser.getAttributeValue("", "unread"); if(unreadStr !=null) sender.unread = Integer.parseInt(unreadStr) == 1; addSender(sender); } eventType = parser.next(); } eventType = parser.next(); } } /** * Creates an html description of all participant names in the thread. * We try to do this in a Gmail-like (although quite simplified) way:<br/> * We print the whole name for a sole participant. <br/> * We print only the first names for more than one participant. <br/> * We print up to three names max. <br/> * We show in bold people that we have unread messages for <br/>. * * @return an html description of <tt>thread</tt> */ private String createParticipantNames() { StringBuffer participantNames = new StringBuffer(); //if we have more than one sender we only show first names boolean firstNamesOnly = getSenderCount() > 1; int unreadSenderCount = getUnreadSenderCount(); int maximumSndrsAllowed = 3; int remainingSndrsAllowed = maximumSndrsAllowed; int maximumUnreadAllowed = Math.min( remainingSndrsAllowed, unreadSenderCount); int maximumReadAllowed = remainingSndrsAllowed - maximumUnreadAllowed; //we now iterate over all senders and include as many unread and read //participants as possible. Iterator<MailThreadInfo.Sender> senders = senders(); while(senders.hasNext()) { if(remainingSndrsAllowed == 0 ) break; MailThreadInfo.Sender sender = senders.next(); String name = firstNamesOnly? sender.getFirstName() : sender.name; if (name == null) { //if there's no name then use the user part of the address if (sender.address != null) { int atIndex = sender.address.indexOf("@"); if(atIndex != -1) return sender.address.substring(0, atIndex); else name = sender.address; } else name = "unknown"; } if (!sender.unread && maximumReadAllowed == 0) continue; if(remainingSndrsAllowed < maximumSndrsAllowed) { //this is not the first name we add so add a coma participantNames.append(", "); } remainingSndrsAllowed--; if(sender.unread) { participantNames.append("<b>").append(name).append("</b>"); maximumUnreadAllowed --; } else { participantNames.append(name); maximumReadAllowed--; } } //if we don't show all the senders, then show total number of messages int messageCount = getMessageCount(); if(messageCount > 1) participantNames.append(" (").append(messageCount).append(")"); return participantNames.toString(); } /** * Creates an html description (table rows) of the specified thread. * * @return an html description of <tt>thread</tt> */ public String createHtmlDescription() { StringBuffer threadBuff = new StringBuffer(); threadBuff.append("<tr bgcolor=\"#ffffff\">"); //first get the names of the participants threadBuff.append("<td>"); threadBuff.append(createParticipantNames()); threadBuff.append("</td>"); //start a new cell for labels, subject, snippet and thread link threadBuff.append("<td>"); //labels threadBuff.append(createLabelList()).append(" "); //add the subject threadBuff.append("<a href=\""); threadBuff.append(getURL()).append("\"><b>"); threadBuff.append(getSubject()).append("</b></a>"); //add mail snippet threadBuff.append("<font color=#7777CC> - "); threadBuff.append("<a href=\""); threadBuff.append(getURL()); threadBuff.append("\" style=\"text-decoration:none\">"); threadBuff.append(getSnippet()).append("</a></font>"); //end thread link threadBuff.append("</td>"); //time and date threadBuff.append("<td nowrap>"); threadBuff.append( getFormattedDate() ); threadBuff.append("</td></tr>"); //and we're done return threadBuff.toString(); } /** * Creates a formatted list of labels. * * @return a <tt>String</tt> containing a formatted list of labels. */ private String createLabelList() { String[] labelsArray = labels.split("\\|"); StringBuffer labelsList = new StringBuffer(); //get rid of the system labels that start with "^" for (int i = 0; i < labelsArray.length; i++) { String label = labelsArray[i]; if(label.startsWith("^")) continue; labelsList.append("<font color=#006633>"); labelsList.append(label); labelsList.append("</font>"); if(i < labelsArray.length -1) labelsList.append(", "); } return labelsList.toString(); } }