StoredMessageSorter.java
package com.icegreen.greenmail.imap.commands;
import com.icegreen.greenmail.store.StoredMessage;
import jakarta.mail.Address;
import jakarta.mail.Message;
import jakarta.mail.MessagingException;
import java.util.Comparator;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Sorts messages by given sort term.
* <p>
* Created on 10/03/2016.
*
* @author Reda.Housni-Alaoui
*/
class StoredMessageSorter implements Comparator<StoredMessage> {
private static final java.util.regex.Pattern LEADER_PATTERN =
java.util.regex.Pattern.compile("^(?:\\[[^\\[\\]]*\\]\\s*)*(?:re|fwd|fw)\\s*(?:\\[[^\\[\\]]*\\]\\s*)?:", java.util.regex.Pattern.CASE_INSENSITIVE);
private static final java.util.regex.Pattern BLOB_PATTERN =
java.util.regex.Pattern.compile("^\\[[^\\[\\]]*\\]\\s*");
private final SortTerm sortTerm;
private final AtomicBoolean reverse = new AtomicBoolean();
StoredMessageSorter(SortTerm sortTerm) {
this.sortTerm = sortTerm;
}
@Override
public int compare(StoredMessage m1, StoredMessage m2) {
try {
int compareResult = 0;
for (SortKey sortKey : sortTerm.getSortCriteria()) {
switch (sortKey) {
case REVERSE:
reverse.set(true);
break;
case ARRIVAL:
compareResult = doCompare(m1.getReceivedDate(), m2.getReceivedDate());
break;
case CC:
compareResult = doCompare(getCc(m1), getCc(m2));
break;
case DATE:
compareResult = doCompare(m1.getMimeMessage().getSentDate(), m2.getMimeMessage().getSentDate());
break;
case FROM:
compareResult = doCompare(getFrom(m1), getFrom(m2));
break;
case SIZE:
compareResult = doCompare(m1.getMimeMessage().getSize(), m2.getMimeMessage().getSize());
break;
case SUBJECT:
compareResult = doCompare(
getBaseSubject(m1.getMimeMessage().getSubject()).toLowerCase(java.util.Locale.ENGLISH),
getBaseSubject(m2.getMimeMessage().getSubject()).toLowerCase(java.util.Locale.ENGLISH)
);
break;
case TO:
compareResult = doCompare(getTo(m1), getTo(m2));
break;
default:
break;
}
if (compareResult != 0) {
break;
}
}
return compareResult;
} catch (MessagingException e) {
throw new IllegalArgumentException(e.getMessage(), e);
}
}
private String getFrom(StoredMessage message) throws MessagingException {
Address[] addresses = message.getMimeMessage().getFrom();
if (addresses == null || addresses.length == 0) {
return null;
}
return String.valueOf(addresses[0]);
}
private String getTo(StoredMessage message) throws MessagingException {
Address[] addresses = message.getMimeMessage().getRecipients(Message.RecipientType.TO);
if (addresses == null || addresses.length == 0) {
return null;
}
return String.valueOf(addresses[0]);
}
private String getCc(StoredMessage message) throws MessagingException {
Address[] addresses = message.getMimeMessage().getRecipients(Message.RecipientType.CC);
if (addresses == null || addresses.length == 0) {
return null;
}
return String.valueOf(addresses[0]);
}
private <T extends Comparable<T>> int doCompare(T c1, T c2) {
int multiplier = reverse.getAndSet(false) ? -1 : 1;
if (c1 == c2) {
return 0;
} else if (c1 == null) {
return multiplier;
} else if (c2 == null) {
return multiplier * -1;
}
return multiplier * c1.compareTo(c2);
}
private String getBaseSubject(String subject) {
if (subject == null) {
return "";
}
String s = subject.replaceAll("[\\t\\r\\n]+", " ");
s = s.replaceAll(" {2,}", " ");
while (true) {
// Step 2: Remove Trailers
while (true) {
if (s.endsWith(" ")) {
s = s.substring(0, s.length() - 1);
} else if (s.toLowerCase(java.util.Locale.ENGLISH).endsWith("(fwd)")) {
s = s.substring(0, s.length() - 5);
} else {
break;
}
}
// Steps 3, 4, 5: Remove Leaders / Blobs / Repeat
while (true) {
if (s.startsWith(" ")) {
s = s.substring(1);
continue;
}
java.util.regex.Matcher lm = LEADER_PATTERN.matcher(s);
if (lm.find()) {
s = s.substring(lm.end());
continue;
}
java.util.regex.Matcher bm = BLOB_PATTERN.matcher(s);
if (bm.find()) {
String remaining = s.substring(bm.end());
if (!remaining.isEmpty()) {
s = remaining;
continue;
}
}
break;
}
// Step 6: Handle Forwarding Headers
if (s.toLowerCase(java.util.Locale.ENGLISH).startsWith("[fwd:") && s.endsWith("]")) {
s = s.substring(5, s.length() - 1);
continue;
}
break;
}
return s;
}
}