![JAR search and dependency download from the Maven repository](/logo.png)
io.github.oliviercailloux.email.ImapSearchPredicate Maven / Gradle / Ivy
The newest version!
package io.github.oliviercailloux.email;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Verify.verify;
import static io.github.oliviercailloux.email.UncheckedMessagingException.MESSAGING_UNCHECKER;
import com.google.common.base.MoreObjects;
import com.google.common.base.MoreObjects.ToStringHelper;
import com.google.common.base.Predicates;
import com.google.common.base.VerifyException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Range;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import jakarta.mail.Message;
import jakarta.mail.Message.RecipientType;
import jakarta.mail.internet.InternetAddress;
import jakarta.mail.search.AndTerm;
import jakarta.mail.search.ComparisonTerm;
import jakarta.mail.search.FromStringTerm;
import jakarta.mail.search.FromTerm;
import jakarta.mail.search.OrTerm;
import jakarta.mail.search.RecipientStringTerm;
import jakarta.mail.search.RecipientTerm;
import jakarta.mail.search.SearchTerm;
import jakarta.mail.search.SentDateTerm;
import jakarta.mail.search.StringTerm;
import jakarta.mail.search.SubjectTerm;
import java.time.Instant;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Predicate;
/**
*
* Either is a {@link ImapSearchPredicate#FALSE}, or contains no FALSE. This permits to ensure that
* the Javamail implementation is not at a loss with such thing.
*
*/
public class ImapSearchPredicate implements Predicate {
@SuppressWarnings("serial")
private static class TrueSearchTerm extends SearchTerm {
@Override
public boolean match(Message msg) {
return true;
}
}
@SuppressWarnings("serial")
private static class FalseSearchTerm extends SearchTerm {
@Override
public boolean match(Message msg) {
return false;
}
}
public static ImapSearchPredicate TRUE =
new ImapSearchPredicate(new TrueSearchTerm(), Predicates.alwaysTrue());
public static ImapSearchPredicate FALSE =
new ImapSearchPredicate(new FalseSearchTerm(), Predicates.alwaysFalse());
public static ImapSearchPredicate recipientAddressEquals(RecipientType recipientType,
String address) {
checkArgument(address.toLowerCase(Locale.ROOT).equals(address));
final EmailAddress emailAddress = EmailAddress.given(address);
return new ImapSearchPredicate(
new RecipientTerm(recipientType, emailAddress.asInternetAddress()),
(m) -> recipientAddressEquals(recipientType, address, m));
}
private static boolean recipientAddressEquals(RecipientType recipientType, String address,
Message m) {
return Arrays.stream(MESSAGING_UNCHECKER.getUsing(() -> m.getRecipients(recipientType)))
.map(a -> (InternetAddress) a)
.anyMatch(a -> a.getAddress().toLowerCase(Locale.ROOT).equals(address));
}
public static ImapSearchPredicate fromAddressEquals(String address) {
checkArgument(address.toLowerCase(Locale.ROOT).equals(address));
final EmailAddress emailAddress = EmailAddress.given(address);
return new ImapSearchPredicate(new FromTerm(emailAddress.asInternetAddress()),
(m) -> fromAddressEquals(address, m));
}
private static boolean fromAddressEquals(String address, Message m) {
return Arrays.stream(MESSAGING_UNCHECKER.getUsing(m::getFrom)).map(a -> (InternetAddress) a)
.anyMatch(a -> a.getAddress().toLowerCase(Locale.ROOT).equals(address));
}
public static ImapSearchPredicate recipientFullAddressContains(RecipientType recipientType,
String subString) {
/*
* “In all search keys that use strings, a message matches the key if the string is a substring
* of the field. The matching is case-insensitive.” -- https://tools.ietf.org/html/rfc3501
*/
checkArgument(subString.toLowerCase(Locale.ROOT).equals(subString));
return new ImapSearchPredicate(new RecipientStringTerm(recipientType, subString),
(m) -> recipientFullAddressContains(recipientType, subString, m));
}
private static boolean recipientFullAddressContains(RecipientType recipientType, String subString,
Message m) {
return Arrays.stream(MESSAGING_UNCHECKER.getUsing(() -> m.getRecipients(recipientType)))
.map(a -> (InternetAddress) a).map(InternetAddress::toUnicodeString)
.anyMatch(s -> s.toLowerCase(Locale.ROOT).contains(subString));
}
public static ImapSearchPredicate fromFullAddressContains(String subString) {
checkArgument(subString.toLowerCase(Locale.ROOT).equals(subString));
return new ImapSearchPredicate(new FromStringTerm(subString),
(m) -> fromFullAddressContains(subString, m));
}
private static boolean fromFullAddressContains(String subString, Message m) {
return Arrays.stream(MESSAGING_UNCHECKER.getUsing(() -> m.getFrom()))
.map(a -> (InternetAddress) a)
.anyMatch(a -> a.toUnicodeString().toLowerCase(Locale.ROOT).contains(subString));
}
public static ImapSearchPredicate subjectContains(String subString) {
checkArgument(subString.toLowerCase(Locale.ROOT).equals(subString));
return new ImapSearchPredicate(new SubjectTerm(subString), m -> subjectContains(subString, m));
}
private static boolean subjectContains(String subString, Message m) {
return MESSAGING_UNCHECKER.getUsing(() -> m.getSubject()).toLowerCase(Locale.ROOT)
.contains(subString);
}
public static ImapSearchPredicate sentWithin(Range range) {
final ImapSearchPredicate lowerPredicate;
if (range.hasLowerBound()) {
final Instant min = range.lowerEndpoint();
switch (range.lowerBoundType()) {
case OPEN:
lowerPredicate =
new ImapSearchPredicate(new SentDateTerm(ComparisonTerm.GT, Date.from(min)),
m -> getSentDate(m).isAfter(min));
break;
case CLOSED:
lowerPredicate =
new ImapSearchPredicate(new SentDateTerm(ComparisonTerm.GE, Date.from(min)),
m -> !getSentDate(m).isBefore(min));
break;
default:
throw new VerifyException();
}
} else {
lowerPredicate = TRUE;
}
final ImapSearchPredicate upperPredicate;
if (range.hasUpperBound()) {
final Instant max = range.upperEndpoint();
switch (range.upperBoundType()) {
case OPEN:
upperPredicate =
new ImapSearchPredicate(new SentDateTerm(ComparisonTerm.LT, Date.from(max)),
m -> getSentDate(m).isBefore(max));
break;
case CLOSED:
upperPredicate =
new ImapSearchPredicate(new SentDateTerm(ComparisonTerm.LE, Date.from(max)),
m -> !getSentDate(m).isAfter(max));
break;
default:
throw new VerifyException();
}
} else {
upperPredicate = TRUE;
}
return lowerPredicate.andSatisfy(upperPredicate);
}
public static ImapSearchPredicate orList(Iterable predicates) {
final Iterator iterator = predicates.iterator();
if (!iterator.hasNext()) {
return FALSE;
}
final ImmutableList realPredicates = Streams.stream(predicates)
.filter(p -> !p.equals(FALSE)).collect(ImmutableList.toImmutableList());
if (realPredicates.contains(TRUE)) {
return TRUE;
}
final ImmutableList terms =
realPredicates.stream().map(p -> p.term).collect(ImmutableList.toImmutableList());
final OrTerm orTerm = new OrTerm(terms.toArray(new SearchTerm[terms.size()]));
final Predicate predicate = Streams.stream(predicates).map(p -> p.predicate)
.reduce(Predicates.alwaysFalse(), Predicate::or);
return new ImapSearchPredicate(orTerm, predicate);
}
private static Instant getSentDate(Message m) {
return MESSAGING_UNCHECKER.getUsing(() -> m.getSentDate().toInstant());
}
private final SearchTerm term;
private final Predicate predicate;
private ImapSearchPredicate(SearchTerm term, Predicate predicate) {
this.term = checkNotNull(term);
this.predicate = checkNotNull(predicate);
}
public SearchTerm getTerm() {
return term;
}
public Predicate getPredicate() {
return predicate;
}
@Override
public boolean test(Message m) {
final boolean test = predicate.test(m);
verify(term.match(m) == test);
return test;
}
public ImapSearchPredicate andSatisfy(ImapSearchPredicate other) {
if (this.equals(FALSE) || other.equals(FALSE)) {
return FALSE;
}
if (this.equals(TRUE)) {
return other;
}
if (other.equals(TRUE)) {
return this;
}
final ImmutableSet theseTerms;
if (term instanceof AndTerm) {
final AndTerm thisAndTerm = (AndTerm) term;
theseTerms = ImmutableSet.copyOf(thisAndTerm.getTerms());
} else {
theseTerms = ImmutableSet.of(term);
}
final ImmutableSet otherTerms;
if (other.term instanceof AndTerm) {
final AndTerm otherAndTerm = (AndTerm) other.term;
otherTerms = ImmutableSet.copyOf(otherAndTerm.getTerms());
} else {
otherTerms = ImmutableSet.of(other.term);
}
final ImmutableSet allTerms = Sets.union(theseTerms, otherTerms).immutableCopy();
final AndTerm andTerm = new AndTerm(allTerms.toArray(new SearchTerm[allTerms.size()]));
return new ImapSearchPredicate(andTerm, predicate.and(other.predicate));
}
public ImapSearchPredicate orSatisfy(ImapSearchPredicate other) {
if (this.equals(FALSE)) {
return other;
}
if (other.equals(FALSE)) {
return this;
}
if (this.equals(TRUE) || other.equals(TRUE)) {
return TRUE;
}
return new ImapSearchPredicate(new OrTerm(term, other.term), predicate.or(other.predicate));
}
@Override
public boolean equals(Object o2) {
if (!(o2 instanceof ImapSearchPredicate)) {
return false;
}
final ImapSearchPredicate t2 = (ImapSearchPredicate) o2;
return term.equals(t2.term);
}
@Override
public int hashCode() {
return Objects.hash(term);
}
@Override
public String toString() {
final ToStringHelper helper = MoreObjects.toStringHelper(this);
helper.add("Term", term);
if (term instanceof StringTerm s) {
helper.add("Pattern", s.getPattern());
}
return helper.toString();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy