├── .github └── workflows │ └── maven.yml ├── .gitignore ├── .travis.yml ├── .travis ├── deploy.sh └── settings.xml ├── LICENSE ├── README.md ├── pom.xml └── src ├── assembly ├── javadoc.xml └── sources.xml ├── main └── kotlin │ └── io │ └── github │ └── slothLabs │ └── mail │ ├── imap │ ├── Flags.kt │ ├── Folder.kt │ ├── Imap.kt │ ├── Message.kt │ ├── SearchBuilder.kt │ ├── SearchTerms.kt │ └── SortBuilder.kt │ └── util │ └── Result.kt └── test └── kotlin └── io └── github └── slothLabs └── mail └── imap ├── ImapConnectionTest.kt ├── SearchBuilderTest.kt └── Utils.kt /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Java CI with Maven 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up JDK 11 20 | uses: actions/setup-java@v1 21 | with: 22 | java-version: 11 23 | - name: Build with Maven 24 | run: mvn -B package --file pom.xml 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/java,osx,maven,intellij 3 | 4 | ### Java ### 5 | *.class 6 | 7 | # Mobile Tools for Java (J2ME) 8 | .mtj.tmp/ 9 | 10 | # Package Files # 11 | *.jar 12 | *.war 13 | *.ear 14 | 15 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 16 | hs_err_pid* 17 | 18 | 19 | ### OSX ### 20 | .DS_Store 21 | .AppleDouble 22 | .LSOverride 23 | 24 | # Icon must end with two \r 25 | Icon 26 | 27 | 28 | # Thumbnails 29 | ._* 30 | 31 | # Files that might appear in the root of a volume 32 | .DocumentRevisions-V100 33 | .fseventsd 34 | .Spotlight-V100 35 | .TemporaryItems 36 | .Trashes 37 | .VolumeIcon.icns 38 | 39 | # Directories potentially created on remote AFP share 40 | .AppleDB 41 | .AppleDesktop 42 | Network Trash Folder 43 | Temporary Items 44 | .apdisk 45 | 46 | 47 | ### Maven ### 48 | target/ 49 | pom.xml.tag 50 | pom.xml.releaseBackup 51 | pom.xml.versionsBackup 52 | pom.xml.next 53 | release.properties 54 | dependency-reduced-pom.xml 55 | buildNumber.properties 56 | .mvn/timing.properties 57 | 58 | 59 | ### Intellij ### 60 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 61 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 62 | 63 | .idea/ 64 | 65 | # User-specific stuff: 66 | .idea/workspace.xml 67 | .idea/tasks.xml 68 | .idea/dictionaries 69 | .idea/vcs.xml 70 | .idea/jsLibraryMappings.xml 71 | 72 | # Sensitive or high-churn files: 73 | .idea/dataSources.ids 74 | .idea/dataSources.xml 75 | .idea/dataSources.local.xml 76 | .idea/sqlDataSources.xml 77 | .idea/dynamic.xml 78 | .idea/uiDesigner.xml 79 | 80 | # Gradle: 81 | .idea/gradle.xml 82 | .idea/libraries 83 | 84 | # Mongo Explorer plugin: 85 | .idea/mongoSettings.xml 86 | 87 | ## File-based project format: 88 | *.iws 89 | 90 | ## Plugin-specific files: 91 | 92 | # IntelliJ 93 | /out/ 94 | 95 | # mpeltonen/sbt-idea plugin 96 | .idea_modules/ 97 | 98 | # JIRA plugin 99 | atlassian-ide-plugin.xml 100 | 101 | # Crashlytics plugin (for Android Studio and IntelliJ) 102 | com_crashlytics_export_strings.xml 103 | crashlytics.properties 104 | crashlytics-build.properties 105 | fabric.properties 106 | 107 | ### Intellij Patch ### 108 | *.iml 109 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - openjdk11 4 | install: 5 | - mvn --settings .travis/settings.xml install -DskipTests=true -Dmaven.javadoc.skip=true -B -V 6 | before_install: 7 | - echo $GPG_SECRET_KEYS | base64 --decode | $GPG_EXECUTABLE --import 8 | - echo $GPG_OWNERTRUST | base64 --decode | $GPG_EXECUTABLE --import-ownertrust 9 | deploy: 10 | - provider: script 11 | script: sh $TRAVIS_BUILD_DIR/.travis/deploy.sh 12 | skip_cleanup: true 13 | on: 14 | repo: SlothLabs/kotlin-mail 15 | branch: master 16 | jdk: openjdk11 17 | - provider: script 18 | script: sh $TRAVIS_BUILD_DIR/.travis/deploy.sh 19 | skip_cleanup: true 20 | on: 21 | repo: SlothLabs/kotlin-mail 22 | tags: true 23 | jdk: openjdk11 24 | - provider: releases 25 | api_key: $GITHUB_OAUTH_TOKEN 26 | skip_cleanup: true 27 | 28 | after_success: 29 | - bash <(curl -s https://codecov.io/bash) 30 | -------------------------------------------------------------------------------- /.travis/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd `dirname $0`/.. 3 | 4 | if [ ! -z "$TRAVIS_TAG" ] 5 | then 6 | echo "on a tag -> set pom.xml to $TRAVIS_TAG" 7 | mvn --settings .travis/settings.xml org.codehaus.mojo:versions-maven-plugin:2.1:set -DnewVersion=$TRAVIS_TAG 1>/dev/null 2>/dev/null 8 | else 9 | echo "not on a tag -> keep snapshot version in pom.xml" 10 | fi 11 | 12 | mvn clean deploy --settings .travis/settings.xml -DskipTests=true -B -U -------------------------------------------------------------------------------- /.travis/settings.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | ossrh 9 | ${env.SONATYPE_USERNAME} 10 | ${env.SONATYPE_PASSWORD} 11 | 12 | 13 | 14 | 15 | ossrh 16 | 17 | true 18 | 19 | 20 | ${env.GPG_EXECUTABLE} 21 | ${env.GPG_PASSPHRASE} 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Sloth Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kotlin-mail [![Build Status](https://travis-ci.org/SlothLabs/kotlin-mail.svg?branch=master)](https://travis-ci.org/SlothLabs/kotlin-mail) [![codecov.io](https://codecov.io/github/SlothLabs/kotlin-mail/coverage.svg?branch=master)](https://codecov.io/github/SlothLabs/kotlin-mail?branch=master)# 2 | 3 | ## A kotlin-esque wrapper for JavaMail. ## 4 | 5 | [JavaMail](https://javamail.java.net/) is the defacto standard library for interacting with email from the Java 6 | platform. It's been around for ages (at least 2002, based on what I see in 7 | the [JSR docs](https://jcp.org/en/jsr/detail?id=919)), has been used in virtually every Java project requiring email 8 | access since then, and is about as bullet proof as you can get by now. 9 | 10 | Because of how old it is, it doesn't make any use of any modern Java features (generics, enums, etc.). And because it's 11 | so widely used - and used by legacy projects that may not run on more modern JVMs - it isn't likely to get a major 12 | facelift any time soon. 13 | 14 | Besides, it's *Java*, which if you're looking here is most importantly *not Kotlin*. 15 | 16 | (If you some how got here without knowing what Kotlin is, I beseech you to venture to http://kotlinlang.org/. It's a 17 | new-ish JVM language from JetBrains, the company who makes the IntelliJ line of IDE's. It's got a lot of awesome 18 | features that will make going back to "regular" Java depressing.) 19 | 20 | ## QuickStart and Early-WIP Caveat ## 21 | 22 | ### Maven 23 | 24 | Kotlin-mail is available in Maven Central; to get started with it, add the following dependency to your pom.xml file: 25 | 26 | ```xml 27 | 28 | 29 | io.github.slothLabs 30 | kotlin-mail 31 | 0.1.0 32 | 33 | ``` 34 | 35 | ### Gradle 36 | 37 | When using Gradle, just add the following to your `build.gradle.kts`: 38 | 39 | ```kotlin 40 | repositories { 41 | mavenCentral() 42 | } 43 | 44 | dependencies { 45 | implementation("io.github.slothLabs", "kotlin-mail", "0.1.0") 46 | } 47 | ``` 48 | 49 | ### Others 50 | 51 | If you use some other build tool that connects to Maven Central, I'm sure you can translate that into your language of 52 | choice. Or just harass me in the issues and I'll update it when I get annoyed enough. 53 | 54 | ### Caveat 55 | 56 | If it's not immediately apparent from the sub-1.0 version number, this is still in extremely early alpha/beta/WIP 57 | stages. I'll do my damndest to make sure that every release is as stable as I can, but until this gets up to 1.0.0 I 58 | make no promises about correctness or backwards compatibility. I'm planning on keeping with a quasi-semantic versioning 59 | scheme as much as possible, with the caveat that until 1.0.0 minor version changes can (and probably will) break the 60 | API. Anyways, on with the show... 61 | 62 | ## A Better JavaMail (for now) ## 63 | 64 | Because JavaMail is so thoroughly tested, it'd be foolish to just scrap it completely. Right now, the main goal of 65 | kotlin-mail is to supplement the JavaMail functionality with stuff that makes it easier to use - at least, from a kotlin 66 | perspective. Stuff like: 67 | 68 | ```kotlin 69 | val connectionInfo = ConnectionInformation( 70 | host = "my.imap.host", 71 | port = 143, 72 | username = "test@drive.com", 73 | password = "12345", 74 | debug = true, // default: false 75 | sslEnabled = true // default: false 76 | ) 77 | 78 | imap(connectionInfo) { 79 | folder("INBOX", FolderModes.ReadOnly) { 80 | val results = search { 81 | +from("someone@example.com") 82 | +subject("Only a Test") 83 | +(header("Content-Type", "text/html") or sizeIsAtLeast(1024)) 84 | +sentOnOrBefore(Date()) 85 | markAsRead = true // notice the lack of unary plus, default: false 86 | } 87 | 88 | results.forEach { 89 | myEmailProcessor.process(it) 90 | } 91 | } 92 | } 93 | ``` 94 | 95 | Which, in good ol' fashioned JavaMail, looks like: 96 | 97 | ```java 98 | class Example { 99 | public static void main(String[] args) { 100 | final String host = "my.imap.host"; 101 | final String user = "test@drive.com"; 102 | final String password = "12345"; 103 | final int port = 143; 104 | 105 | final Properties properties = System.getProperties(); 106 | final Session session = Session.getInstance(properties); 107 | 108 | Store store; 109 | Folder inbox; 110 | try { 111 | store = session.getStore("imap"); 112 | store.connect(host, port, user, password); 113 | 114 | inbox = store.getFolder("inbox"); 115 | inbox.open(Folder.READ_ONLY); 116 | 117 | final FromStringTerm from = new FromStringTerm("someone@example.com"); 118 | final SubjectTerm subject = new SubjectTerm("Only a Test"); 119 | 120 | final HeaderTerm header = new HeaderTerm("Content-Type", "text/html"); 121 | final SizeTerm size = new SizeTerm(Comparisons.GE, 1024); 122 | final OrTerm headerOrSize = new OrTerm(header, size); 123 | 124 | final DateTerm date = new DateTerm(Comparisons.LE, new Date()); 125 | 126 | final AndTerm searchTerm = new AndTerm( 127 | from, 128 | new AndTerm( 129 | subject, 130 | new AndTerm( 131 | headerOrSize, 132 | date 133 | ) 134 | ) 135 | ); 136 | 137 | final Message[] messages = inbox.search(searchTerm); 138 | 139 | for (final Message msg : messages) { 140 | myEmailProcessor.process(msg); 141 | } 142 | } finally { 143 | if (inbox != null) { 144 | try { 145 | inbox.close(false); 146 | } catch (MessagingException ex) { 147 | // we have to catch it, otherwise 148 | // we won't get to close the 149 | // store. 150 | } 151 | } 152 | 153 | if (store != null) { 154 | try { 155 | store.close(); 156 | } catch (MessagingException ex) { 157 | // don't really have to catch this one, 158 | // but whatevs. 159 | } 160 | } 161 | } 162 | } 163 | } 164 | ``` 165 | 166 | Yeah, not really great :/ Even if you don't take into consideration the exception handling and closing at the end (and 167 | neither Store nor Folder implement Closeable/AutoCloseable, so you can't even wrap those in `try-with-resource` blocks), 168 | it's still pretty verbose. I mean, it's fine if you're used to working with Java, because that's really the only way to 169 | work with it. And you can always roll your own wrappers to make it a little more bearable, like your own connection 170 | handling and search building stuff. 171 | 172 | But it's still *Java*. We've got Kotlin now, so we get to do cool stuff like type-safe builders, infix functions, and 173 | operator overloading, and those can get us some much cleaner code. 174 | 175 | ## kotlin-mail Goals ## 176 | 177 | The first major release - 1.0.0 - is planned to be a full wrapper around the JavaMail library, without really any new 178 | bells and whistles applied to it (other than kotlin-izing it). From there, there's at least two main targets in mind. 179 | 180 | ### Async/Reactive Email ### 181 | 182 | It'd be nice to allow a more asynchronous approach to email, where it can handle callbacks or Futures/Promises or 183 | whatever as part of the library instead of the standard approach of wrapping it in a general purpose async library. 184 | 185 | ### Replace JavaMail ### 186 | 187 | Initially this is just going to wrap JavaMail. And maybe it'll just stay that way, but it might be more efficient in the 188 | future to replace it completely. In the example above, there's a total of 9 objects created just to handle the search 189 | terms. That's not counting anything behind the scenes, any of the overhead of the Folder or Store, any of the messages, 190 | etc. I'm sure it'd be possible to somehow represent a complex search term in fewer objects (and I may well be wrong - 191 | this is just offhand thoughts as I write this). If we're just wrapping the JavaMail library, we're stuck with doing it 192 | the way JavaMail does it. 193 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | io.github.slothLabs 8 | kotlin-mail 9 | 0.1.0-SNAPSHOT 10 | 11 | kotlin-mail 12 | 13 | A kotlin-esque wrapper/replacement for JavaMail. 14 | 15 | https://github.com/SlothLabs/kotlin-mail 16 | 17 | 18 | MIT License 19 | http://www.opensource.org/licenses/mit-license.php 20 | repo 21 | 22 | 23 | 24 | 25 | https://github.com/SlothLabs/kotlin-mail 26 | scm:git:git://github.com:SlothLabs/kotlin-mail.git 27 | scm:git:git@github.com:SlothLabs/kotlin-mail.git 28 | 29 | 30 | 31 | 32 | SkittishSloth 33 | Matthew Cory 34 | sloth.labs@gmail.com 35 | https://github.com/SlothLabs 36 | 37 | 38 | 39 | 40 | https://github.com/SlothLabs/kotlin-mail/issues 41 | GitHub Issues 42 | 43 | 44 | 45 | 46 | jetbrains-all 47 | http://repository.jetbrains.com/all 48 | 49 | 50 | 51 | jcenter 52 | http://jcenter.bintray.com 53 | 54 | 55 | 56 | 57 | 58 | jcenter 59 | JCenter 60 | https://jcenter.bintray.com 61 | 62 | true 63 | 64 | 65 | 66 | 67 | 68 | UTF-8 69 | 1.4.31 70 | 4.4.1 71 | 72 | 73 | 74 | 75 | org.jetbrains.kotlin 76 | kotlin-stdlib 77 | ${kotlin.version} 78 | 79 | 80 | 81 | com.sun.mail 82 | jakarta.mail 83 | 1.6.5 84 | 85 | 86 | 87 | org.slf4j 88 | slf4j-api 89 | 1.7.30 90 | 91 | 92 | 93 | org.slf4j 94 | slf4j-simple 95 | 1.7.18 96 | test 97 | 98 | 99 | 100 | com.icegreen 101 | greenmail 102 | 1.6.2 103 | test 104 | 105 | 106 | junit 107 | junit 108 | 109 | 110 | 111 | 112 | 113 | io.kotest 114 | kotest-runner-junit5-jvm 115 | ${kotest.version} 116 | test 117 | 118 | 119 | 120 | io.kotest 121 | kotest-assertions-core-jvm 122 | ${kotest.version} 123 | test 124 | 125 | 126 | 127 | 128 | 129 | ossrh 130 | https://oss.sonatype.org/content/repositories/snapshots 131 | 132 | 133 | ossrh 134 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 135 | 136 | 137 | 138 | 139 | src/main/kotlin 140 | ${project.basedir}/src/test/kotlin 141 | 142 | 143 | 144 | org.jetbrains.kotlin 145 | kotlin-maven-plugin 146 | ${kotlin.version} 147 | 148 | 149 | compile 150 | compile 151 | 152 | compile 153 | 154 | 155 | 156 | 157 | test-compile 158 | test-compile 159 | 160 | test-compile 161 | 162 | 163 | 164 | 165 | 166 | 167 | external.atlassian.jgitflow 168 | jgitflow-maven-plugin 169 | 1.0-m5.1 170 | 171 | 172 | 173 | org.apache.maven.plugins 174 | maven-project-info-reports-plugin 175 | 3.1.1 176 | 177 | 178 | 179 | org.jacoco 180 | jacoco-maven-plugin 181 | 0.8.6 182 | 183 | 184 | 185 | prepare-agent 186 | 187 | 188 | 189 | report 190 | test 191 | 192 | report 193 | 194 | 195 | 196 | 197 | 198 | 199 | org.sonatype.plugins 200 | nexus-staging-maven-plugin 201 | 1.6.8 202 | true 203 | 204 | ossrh 205 | https://oss.sonatype.org/ 206 | true 207 | 208 | 209 | 210 | 211 | org.apache.maven.plugins 212 | maven-source-plugin 213 | 3.2.1 214 | 215 | 216 | package 217 | attach-sources 218 | 219 | jar-no-fork 220 | 221 | 222 | 223 | 224 | 225 | 226 | maven-assembly-plugin 227 | 3.3.0 228 | 229 | 230 | package 231 | 232 | single 233 | 234 | 235 | 236 | 237 | 238 | src/assembly/sources.xml 239 | src/assembly/javadoc.xml 240 | 241 | 242 | 243 | 244 | 245 | org.jetbrains.dokka 246 | dokka-maven-plugin 247 | 1.4.20 248 | 249 | 250 | prepare-package 251 | 252 | dokka 253 | 254 | 255 | 256 | 257 | 258 | src/main/kotlin 259 | 260 | 261 | 262 | 263 | 264 | org.apache.maven.plugins 265 | maven-gpg-plugin 266 | 1.6 267 | 268 | 269 | sign-artifacts 270 | verify 271 | 272 | sign 273 | 274 | 275 | 276 | 277 | 278 | 279 | org.apache.maven.plugins 280 | maven-surefire-plugin 281 | 2.22.2 282 | 283 | 284 | 285 | 286 | -------------------------------------------------------------------------------- /src/assembly/javadoc.xml: -------------------------------------------------------------------------------- 1 | 3 | javadoc 4 | 5 | jar 6 | 7 | 8 | / 9 | 10 | 11 | 12 | true 13 | unix 14 | 15 | target/dokka 16 | false 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/assembly/sources.xml: -------------------------------------------------------------------------------- 1 | 3 | sources 4 | 5 | jar 6 | 7 | 8 | / 9 | 10 | 11 | 12 | true 13 | unix 14 | 15 | src/main/kotlin 16 | 17 | 18 | 19 | **/*.kt 20 | 21 | 22 | 23 | true 24 | unix 25 | 26 | src/main/kotlin 27 | 28 | 29 | 30 | **/*.kt 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/slothLabs/mail/imap/Flags.kt: -------------------------------------------------------------------------------- 1 | package io.github.slothLabs.mail.imap 2 | 3 | import javax.mail.Flags as JavaMailFlags 4 | 5 | enum class Flag(var javaMailFlag: JavaMailFlags.Flag) { 6 | Answered(JavaMailFlags.Flag.ANSWERED), 7 | Deleted(JavaMailFlags.Flag.DELETED), 8 | Draft(JavaMailFlags.Flag.DRAFT), 9 | Flagged(JavaMailFlags.Flag.FLAGGED), 10 | Recent(JavaMailFlags.Flag.RECENT), 11 | Seen(JavaMailFlags.Flag.SEEN), 12 | User(JavaMailFlags.Flag.USER); 13 | } 14 | 15 | class Flags(vararg flags: Flag) : HashSet() { 16 | private val flagItems = mutableSetOf() 17 | 18 | val javaMailFlags: JavaMailFlags 19 | get() = flagItems.fold(JavaMailFlags()) { acc: JavaMailFlags, flag: Flag -> 20 | acc.add(flag.javaMailFlag) 21 | acc 22 | } 23 | 24 | init { 25 | flags.forEach { flagItems.add(it) } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/slothLabs/mail/imap/Folder.kt: -------------------------------------------------------------------------------- 1 | package io.github.slothLabs.mail.imap 2 | 3 | import com.sun.mail.imap.IMAPFolder 4 | import com.sun.mail.imap.IMAPMessage 5 | import javax.mail.FetchProfile 6 | import javax.mail.Folder as JavaMailFolder 7 | 8 | /** 9 | * A statically-typed `enum` to wrap the JavaMail Folder constants. 10 | */ 11 | enum class FolderModes(private val javaMailMode: Int) { 12 | /** 13 | * Indicates to open the folder in read-only mode. 14 | */ 15 | ReadOnly(JavaMailFolder.READ_ONLY), 16 | 17 | /** 18 | * Indicates to open the folder in read/write mode. 19 | */ 20 | ReadWrite(JavaMailFolder.READ_WRITE); 21 | 22 | internal fun toJavaMailMode() = javaMailMode 23 | } 24 | 25 | enum class FolderTypes(private val javaMailFolderType: Int) { 26 | HoldsFolders(JavaMailFolder.HOLDS_FOLDERS), 27 | HoldsMessages(JavaMailFolder.HOLDS_MESSAGES), 28 | HoldsBoth(JavaMailFolder.HOLDS_FOLDERS and JavaMailFolder.HOLDS_MESSAGES); 29 | 30 | internal fun toJavaMailFolderType() = javaMailFolderType 31 | 32 | companion object { 33 | fun fromJavaMailFolderType(javaMailFolderType: Int): FolderTypes { 34 | val holdsFolders = javaMailFolderType and JavaMailFolder.HOLDS_FOLDERS 35 | val holdsMessages = javaMailFolderType and JavaMailFolder.HOLDS_MESSAGES 36 | 37 | if ((holdsFolders != 0) && (holdsMessages != 0)) return HoldsBoth 38 | else if (holdsFolders != 0) return HoldsFolders 39 | return HoldsMessages 40 | } 41 | } 42 | } 43 | 44 | /** 45 | * Wrapper class to provide additional functionality over the JavaMail `IMAPFolder` 46 | * class. 47 | */ 48 | class Folder(private val javaMailFolder: IMAPFolder) { 49 | 50 | private var preFetchInfo = FetchProfile() 51 | 52 | private val seenFlags = Flags(Flag.Seen) 53 | 54 | /** 55 | * Search this folder using the [SearchBuilder] initialized within 56 | * the given block, using any pre-fetch information set via either 57 | * [preFetchBy] method. Any sorting applied to the `SearchBuilder` 58 | * within the given block is applied to the returned results. 59 | * 60 | * @param block the block to use to initialize the [SearchBuilder]. 61 | * 62 | * @return a `List` of [Message] instances that match the search, pre-fetched 63 | * if applicable, and sorted if applicable. 64 | */ 65 | fun search(block: SearchBuilder.() -> Unit): List { 66 | val builder = SearchBuilder() 67 | builder.block() 68 | val searchTerm = builder.build() 69 | 70 | val javaMailMessages = if (builder.hasSortTerms()) { 71 | val sortTerms = builder.getSortTerms().map { it.toSortTerm() }.toTypedArray() 72 | searchTerm?.let { javaMailFolder.getSortedMessages(sortTerms, it) } 73 | } else { 74 | searchTerm?.let { javaMailFolder.search(it) } 75 | } 76 | 77 | javaMailMessages?.let { javaMailFolder.fetch(it, preFetchInfo) } 78 | val messages = javaMailMessages?.map { Message(it as IMAPMessage) } ?: mutableListOf() 79 | 80 | if (builder.markAsRead) { 81 | javaMailMessages?.forEach { 82 | javaMailFolder.setFlags(arrayOf(it), seenFlags.javaMailFlags, true) 83 | } 84 | } 85 | 86 | return messages 87 | } 88 | 89 | /** 90 | * Specifies a `FetchProfile` to use when fetching messages. 91 | * 92 | * @param fetchProfile the `FetchProfile` to fetch messages with. 93 | */ 94 | fun preFetchBy(fetchProfile: FetchProfile) { 95 | preFetchInfo = fetchProfile 96 | } 97 | 98 | /** 99 | * Allows applying multiple `FetchProfileItem`s a little easier than 100 | * the standard `FetchProfile`-construction-then-add means. 101 | * 102 | * @param test the `FetchProfileItem`(s) to build the new prefetch information with. 103 | */ 104 | fun preFetchBy(vararg test: FetchProfile.Item) { 105 | preFetchInfo = FetchProfile() 106 | test.forEach { preFetchInfo.add(it) } 107 | } 108 | 109 | /** 110 | * Closes the underlying JavaMail folder, expunging deleted messages or not depending 111 | * on the value of `expunge`. 112 | * 113 | * @param expunge whether or not to expunge the underlying messages. 114 | */ 115 | fun close(expunge: Boolean) { 116 | javaMailFolder.close(expunge) 117 | } 118 | 119 | /** 120 | * Sorts the messages in the folder using the [SortBuilder] initialized 121 | * within the given block, using any pre-fetch information set via either 122 | * [preFetchBy] method. Note that this only sorts the messages as returned 123 | * from this method; the ordering within the IMAP server itself remains 124 | * unchanged. 125 | * 126 | * @param block the block to use to initialize the [SortBuilder] 127 | * 128 | * @return a `List` of [Message] instances, sorted per the terms applied 129 | * via the [SortBuilder], and pre-fetched if applicable. 130 | */ 131 | fun sortedBy(block: SortBuilder.() -> Unit): List { 132 | val sortBuilder = SortBuilder() 133 | sortBuilder.block() 134 | 135 | val sortTerms = sortBuilder.build().map { it.toSortTerm() }.toTypedArray() 136 | val javaMailMessages = javaMailFolder.getSortedMessages(sortTerms) 137 | 138 | javaMailFolder.fetch(javaMailMessages, preFetchInfo) 139 | 140 | return javaMailMessages.map { Message(it as IMAPMessage) } 141 | } 142 | 143 | fun getMessageCount(): Int = javaMailFolder.messageCount 144 | 145 | fun getUnreadMessageCount(): Int = javaMailFolder.unreadMessageCount 146 | 147 | fun getNewMessageCount(): Int = javaMailFolder.newMessageCount 148 | 149 | fun hasNewMessages(): Boolean = javaMailFolder.hasNewMessages() 150 | 151 | fun getFolderType(): FolderTypes = FolderTypes.fromJavaMailFolderType(javaMailFolder.type) 152 | 153 | /** 154 | * Operator to allow accessing messages in this folder via bracket syntax. The supplied 155 | * index is expected to be a valid message number within this folder. 156 | */ 157 | operator fun get(i: Int): Message? = javaMailFolder[i] 158 | 159 | fun messagesIn(range: ClosedRange, prefetch: Boolean = true): List { 160 | val jmmMessages = javaMailFolder.getMessages(range.start, range.endInclusive) 161 | if (prefetch) { 162 | javaMailFolder.fetch(jmmMessages, preFetchInfo) 163 | } 164 | return jmmMessages.map { Message(it as IMAPMessage) } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/slothLabs/mail/imap/Imap.kt: -------------------------------------------------------------------------------- 1 | package io.github.slothLabs.mail.imap 2 | 3 | import com.sun.mail.imap.IMAPFolder 4 | import javax.mail.Session 5 | import javax.mail.Store 6 | import java.io.Closeable 7 | import java.util.Properties 8 | 9 | /** 10 | * Class for performing basic IMAP access functionality. 11 | */ 12 | class Imap internal constructor(private val store: Store) : Closeable, AutoCloseable { 13 | 14 | /** 15 | * Closes the session and disconnects from the IMAP server. 16 | */ 17 | override fun close() { 18 | store.close() 19 | } 20 | 21 | /** 22 | * Opens the specified folder, with the specified mode, applies the given 23 | * action to it, and closes the folder on exit. Two key things to note: 24 | * 25 | * 0. The folder is closed without expunging any deleted messages. If this is needed, 26 | * you must manually expunge deleted messages in your action. 27 | * 0. Unless you explicitly pre-fetched any messages retrieved, they will not be 28 | * accessible outside of your action method (i.e. requesting any property on 29 | * them will throw a `FolderClosedException`). This is inherent in JavaMail; 30 | * most message properties are lazy-loaded, meaning they make an additional 31 | * call to the IMAP server the first time they're requested. When the folder 32 | * is closed, they can't make that call, and blow up on you. You can either 33 | * ensure you do everything you need to do with in your action, and/or use 34 | * [Folder.preFetchBy] to pre-fetch message data (using both is highly recommended). 35 | * 36 | * @param name the name of the folder to open. 37 | * @param mode the mode to open the folder in. 38 | * @param action the action to perform against the folder. 39 | */ 40 | fun folder(name: String, mode: FolderModes, action: Folder.() -> Unit) { 41 | val imapFolder = store.getFolder(name) as IMAPFolder 42 | imapFolder.open(mode.toJavaMailMode()) 43 | val theFolder = Folder(imapFolder) 44 | theFolder.action() 45 | theFolder.close(false) 46 | } 47 | } 48 | 49 | /** 50 | * Class for basic IMAP connectivity information. 51 | */ 52 | data class ConnectionInformation( 53 | /** 54 | * The host to connect to. 55 | */ 56 | val host: String, 57 | 58 | /** 59 | * The port to connect to. 60 | */ 61 | val port: Int, 62 | 63 | /** 64 | * The user name to connect with. 65 | */ 66 | val user: String, 67 | 68 | /** 69 | * The password to connect with. 70 | */ 71 | val password: String, 72 | 73 | /** 74 | * The debug mode flag. 75 | */ 76 | val debug: Boolean = false, 77 | 78 | /** 79 | * The ssl enabled flag. 80 | */ 81 | val sslEnabled: Boolean = false 82 | ) 83 | 84 | /** 85 | * Connects to an IMAP server using the specified connection information and properties, and performs the give action 86 | * against it. General usage would be: 87 | * 88 | * ``` 89 | * val host = "my.imap.host" 90 | * val port = 143 91 | * val user = "someone@somewhere.net" 92 | * val password = "hackmeifyoucan" 93 | * imap(host, port, user, password) { 94 | * folder("INBOX", FolderModes.ReadOnly) { 95 | * // do something awesomely emailish here. 96 | * } 97 | * } 98 | * ``` 99 | * 100 | * @param host the host to connect to 101 | * @param port the port to connect to 102 | * @param user the user name to connect with 103 | * @param password the password to connect with 104 | * @param properties the properties to pass to the JavaMail store 105 | * @param action the action to perform against the IMAP server. 106 | */ 107 | fun imap( 108 | host: String, 109 | port: Int, 110 | user: String, 111 | password: String, 112 | debug: Boolean = false, 113 | sslEnabled: Boolean = false, 114 | properties: Properties = Properties(), 115 | action: Imap.() -> Unit 116 | ) { 117 | val connectionInformation = ConnectionInformation(host, port, user, password, debug, sslEnabled) 118 | imap(connectionInformation, properties, action) 119 | } 120 | 121 | /** 122 | * Connects to an IMAP server using the specified connection information and properties, and performs the give action 123 | * against it. General usage would be: 124 | * 125 | * ``` 126 | * val host = "my.imap.host" 127 | * val port = 143 128 | * val user = "someone@somewhere.net" 129 | * val password = "hackmeifyoucan" 130 | * val connectionInfo = ConnectionInformation(host, port, user, password) 131 | * imap(connectionInfo) { 132 | * folder("INBOX", FolderModes.ReadOnly) { 133 | * // do something awesomely emailish here. 134 | * } 135 | * } 136 | * ``` 137 | * 138 | * @param connectionInformation the [ConnectionInformation] to connect with 139 | * @param properties the properties to pass to the JavaMail store 140 | * @param action the action to perform against the IMAP server. 141 | */ 142 | fun imap(connectionInformation: ConnectionInformation, properties: Properties = Properties(), action: Imap.() -> Unit) { 143 | properties.setPropertiesFrom(connectionInformation) 144 | val session = Session.getInstance(properties) 145 | session.debug = connectionInformation.debug 146 | val store = session.getStore("imap") 147 | store.connect(connectionInformation) 148 | Imap(store).use { 149 | it.action() 150 | } 151 | } 152 | 153 | /** 154 | * Utility extension to allow connecting a JavaMail store instance using a [ConnectionInformation] 155 | * instance. 156 | * 157 | * @param connectionInformation the [ConnectionInformation] to connect with. 158 | */ 159 | fun Store.connect(connectionInformation: ConnectionInformation) = this.connect( 160 | connectionInformation.host, 161 | connectionInformation.port, 162 | connectionInformation.user, 163 | connectionInformation.password 164 | ) 165 | 166 | private fun Properties.setPropertiesFrom(connectionInformation: ConnectionInformation) { 167 | this["mail.imap.ssl.enable"] = connectionInformation.sslEnabled 168 | } 169 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/slothLabs/mail/imap/Message.kt: -------------------------------------------------------------------------------- 1 | package io.github.slothLabs.mail.imap 2 | 3 | import com.sun.mail.imap.IMAPFolder 4 | 5 | /** 6 | * Wrapper class for working with message headers. 7 | */ 8 | data class MessageHeader( 9 | /** 10 | * The name of the message header. 11 | */ 12 | val name: String, 13 | 14 | /** 15 | * The value of the message header. 16 | */ 17 | val value: String 18 | ) 19 | 20 | /** 21 | * Wrapper around the standard JavaMail `IMAPMessage` class to make 22 | * things a little easier to work with. 23 | */ 24 | class Message(private val mailMessage: com.sun.mail.imap.IMAPMessage) { 25 | 26 | /** 27 | * Gets the first "from" address in the message. 28 | */ 29 | val from: String by lazy { 30 | mailMessage.from[0].toString() 31 | } 32 | 33 | /** 34 | * Gets the message content from the message as a String. 35 | */ 36 | val bodyText: String by lazy { 37 | mailMessage.content as String 38 | } 39 | 40 | /** 41 | * Gets the message's UID value. 42 | */ 43 | val uid: Long = mailMessage.getUID() 44 | 45 | /** 46 | * Gets the collection of headers from the message (as an immutable `List`). 47 | */ 48 | val headers: List by lazy { 49 | val res = mutableListOf() 50 | val headersEnum = mailMessage.allHeaders 51 | headersEnum.iterator().forEach { 52 | val messageHeader = MessageHeader(it.name, it.value) 53 | res.add(messageHeader) 54 | } 55 | 56 | res 57 | } 58 | } 59 | 60 | /** 61 | * Operator to allow working with a message in a function block. 62 | */ 63 | operator fun Message.invoke(action: Message.() -> Unit) { 64 | action() 65 | } 66 | 67 | /** 68 | * Extension function to allow getting a UID from a message directly, instead of 69 | * having to remember to get it from the folder first. 70 | */ 71 | fun com.sun.mail.imap.IMAPMessage.getUID() = (folder as IMAPFolder).getUID(this) 72 | 73 | /** 74 | * Operator to allow accessing messages in an `IMAPFolder` via bracket syntax. The supplied 75 | * index is expected to be a valid message number within this folder. 76 | */ 77 | operator fun IMAPFolder.get(i: Int): Message? { 78 | val msg = this.getMessage(i) as com.sun.mail.imap.IMAPMessage? 79 | return msg?.let { Message(it) } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/slothLabs/mail/imap/SearchBuilder.kt: -------------------------------------------------------------------------------- 1 | package io.github.slothLabs.mail.imap 2 | 3 | import com.sun.mail.imap.ModifiedSinceTerm 4 | import com.sun.mail.imap.OlderTerm 5 | import com.sun.mail.imap.YoungerTerm 6 | import java.util.Date 7 | import javax.mail.Message 8 | import javax.mail.internet.InternetAddress 9 | import javax.mail.internet.MimeMessage.RecipientType 10 | import javax.mail.search.AndTerm 11 | import javax.mail.search.BodyTerm 12 | import javax.mail.search.FlagTerm 13 | import javax.mail.search.FromStringTerm 14 | import javax.mail.search.FromTerm 15 | import javax.mail.search.HeaderTerm 16 | import javax.mail.search.MessageIDTerm 17 | import javax.mail.search.MessageNumberTerm 18 | import javax.mail.search.ReceivedDateTerm 19 | import javax.mail.search.RecipientStringTerm 20 | import javax.mail.search.RecipientTerm 21 | import javax.mail.search.SearchTerm 22 | import javax.mail.search.SentDateTerm 23 | import javax.mail.search.SizeTerm 24 | import javax.mail.search.SubjectTerm 25 | 26 | /** 27 | * Builder class to make search terms a little easier to work with. It's 28 | * geared mainly towards use in tandem with the [Folder.search] method: 29 | * 30 | * ``` 31 | * folder("INBOX", FolderModes.ReadOnly) { 32 | * preFetchBy(FetchProfileItem.MESSAGE) 33 | * val results = search { 34 | * withFrom(fromAddress) 35 | * withSentOnOrBefore(Date()) 36 | * } 37 | * 38 | * msgList.addAll(results) 39 | * } 40 | * ``` 41 | * 42 | * There are two variations of methods used to work with search terms. The first 43 | * set of methods are prefixed by `with` (i.e. `withFrom` as above). These methods 44 | * create the relevant `SearchTerm` instance and store it internally in the `SearchBuilder`. 45 | * They return nothing to the caller. 46 | * 47 | * The second set of methods are not prefixed by `with`, such as `from`. These return the 48 | * relevant `SearchTerm` instance, and do nothing inside of the the `SearchBuilder`. They're 49 | * mainly intended for use with the overloaded operators `+` and `-`. An equivalent version 50 | * of the above sample using those operators would be: 51 | * 52 | * ``` 53 | * folder("INBOX", FolderModes.ReadOnly) { 54 | * preFetchBy(FetchProfileItem.MESSAGE) 55 | * val results = search { 56 | * +from(fromAddress) 57 | * +sentOnOrBefore(Date()) 58 | * } 59 | * 60 | * msgList.addAll(results) 61 | * } 62 | * ``` 63 | * 64 | * Functionally, there's no difference between the two; it's more a matter of developer preference. 65 | * 66 | * Various members build comparison-based search terms; for now, these take an `Int` value, defined 67 | * in `javax.mail.search.ComparisonTerm`. For quick reference, the terms are: 68 | * 69 | * * `ComparisonTerm.LE`: less than or equal 70 | * * `ComparisonTerm.LT`: less than 71 | * * `ComparisonTerm.EQ`: equal 72 | * * `ComparisonTerm.NE`: not equal 73 | * * `ComparisonTerm.GT`: greater than 74 | * * `ComparisonTerm.GE`: greater than or equal 75 | * 76 | */ 77 | class SearchBuilder { 78 | 79 | private val terms = mutableListOf() 80 | 81 | private val sortedBy = mutableListOf() 82 | 83 | /** 84 | * Whether search results should be marked as read. 85 | */ 86 | var markAsRead = false 87 | 88 | /** 89 | * Creates an `Option` containing either `None` or the combined 90 | * `SearchTerm` instance that results from merging all of the terms 91 | * created via the various build methods. 92 | * 93 | * @return an `Option` that's `None` if there were no search terms added, 94 | * `Some` containing a single 'regular' search term if only one search term 95 | * was added, or `Some` containing an `AndTerm` built from merging all of 96 | * the added search terms. 97 | */ 98 | fun build(): SearchTerm? = 99 | when (terms.size) { 100 | 0 -> null 101 | 1 -> terms[0] 102 | else -> terms.reduce { first, second -> AndTerm(first, second) } 103 | } 104 | 105 | /** 106 | * Whether or not sort terms have been applied to the search. 107 | * 108 | * @return `true` if a sort has been applied; `false` otherwise. 109 | */ 110 | fun hasSortTerms() = sortedBy.isNotEmpty() 111 | 112 | /** 113 | * Gets the sort terms (if any) that have been applied to the search. 114 | * 115 | * @return a `List` of [Sort] instances applied to the search, or an 116 | * empty list if no sort has been applied. 117 | */ 118 | fun getSortTerms(): List = sortedBy 119 | 120 | /** 121 | * Creates and returns a `FromTerm` given an `InternetAddress` instance. 122 | * 123 | * @param address the `InternetAddress` to generate the `FromTerm` with. 124 | * 125 | * @return a `FromTerm` based on the specified address. 126 | */ 127 | fun from(address: InternetAddress) = FromTerm(address) 128 | 129 | /** 130 | * Creates and returns a `FromStringTerm` given a string pattern. 131 | * 132 | * @param str the string pattern to generate the `FromStringTerm` with. 133 | * 134 | * @return a `FromStringTerm` based on the given string. 135 | */ 136 | fun from(str: String) = FromStringTerm(str) 137 | 138 | /** 139 | * Creates a `FromTerm` given an `InternetAddress` and stores it in this `SearchBuilder`. 140 | * 141 | * @param address the `InternetAddress` to generate the `FromTerm` with. 142 | */ 143 | fun withFrom(address: InternetAddress) = with(from(address)) 144 | 145 | /** 146 | * Creates a `FromStringTerm` given a string pattern and stores it in this `SearchBuilder`. 147 | * 148 | * @param str the string pattern to generate the `FromStringTerm` with. 149 | */ 150 | fun withFrom(str: String) = with(from(str)) 151 | 152 | /** 153 | * Creates and returns a `RecipientTerm` with the specified `RecipientType` and `InternetAddress`. 154 | * 155 | * @param recipientType the `RecipientType` for the generated term. 156 | * @param address the `InternetAddress` for the generated term. 157 | * 158 | * @return a `RecipientTerm` based on the given `recipientType` and `address`. 159 | */ 160 | fun recipient(recipientType: Message.RecipientType, address: InternetAddress) = 161 | RecipientTerm(recipientType, address) 162 | 163 | /** 164 | * Creates and returns a `RecipientTerm` with the specified `RecipientType` and string pattern. 165 | * 166 | * @param recipientType the `RecipientType` for the generated term. 167 | * @param str the string pattern for the generated term. 168 | * 169 | * @return a `RecipientTerm` based on the given `recipientType` and string pattern. 170 | */ 171 | fun recipient(recipientType: Message.RecipientType, str: String) = RecipientStringTerm(recipientType, str) 172 | 173 | /** 174 | * Creates a `RecipientTerm` with the specified `RecipientType` and `InternetAddress` and stores it in this `SearchBuilder`. 175 | * 176 | * @param recipientType the `RecipientType` for the generated term. 177 | * @param address the `InternetAddress` for the generated term. 178 | */ 179 | fun withRecipient(recipientType: Message.RecipientType, address: InternetAddress) = 180 | with(recipient(recipientType, address)) 181 | 182 | /** 183 | * Creates a `RecipientTerm` with the specified `RecipientType` and string pattern and stores it in this `SearchBuilder`. 184 | * 185 | * @param recipientType the `RecipientType` for the generated term. 186 | * @param str the string pattern for the generated term. 187 | */ 188 | fun withRecipient(recipientType: Message.RecipientType, str: String) = with(recipient(recipientType, str)) 189 | 190 | /** 191 | * Creates and returns a `RecipientTerm` specified as `RecipientType.TO`, using the given `InternetAddress`. 192 | * 193 | * @param address the `InternetAddress` for the generated term. 194 | * 195 | * @return a `RecipientType.TO`-based `RecipientTerm` for the given `address`. 196 | */ 197 | fun to(address: InternetAddress) = recipient(RecipientType.TO, address) 198 | 199 | /** 200 | * Creates and returns a `RecipientTerm` specified as `RecipientType.TO`, using the given string pattern. 201 | * 202 | * @param str the string pattern for the generated term. 203 | * 204 | * @return a `RecipientType.TO`-based `RecipientTerm` for the given string pattern. 205 | */ 206 | fun to(str: String) = recipient(RecipientType.TO, str) 207 | 208 | /** 209 | * Creates a `RecipientTerm` specified as `RecipientType.TO`, using the given `InternetAddress`, and stores it in this `SearchBuilder`. 210 | * 211 | * @param address the `InternetAddress` for the generated term. 212 | */ 213 | fun withTo(address: InternetAddress) = with(to(address)) 214 | 215 | /** 216 | * Creates a `RecipientTerm` specified as `RecipientType.TO`, using the given string pattern, and stores it in this `SearchBuilder`. 217 | * 218 | * @param str the string pattern for the generated term. 219 | */ 220 | fun withTo(str: String) = with(to(str)) 221 | 222 | /** 223 | * Creates and returns a `RecipientTerm` specified as `RecipientType.CC`, using the given `InternetAddress`. 224 | * 225 | * @param address the `InternetAddress` for the generated term. 226 | * 227 | * @return a `RecipientType.CC`-based `RecipientTerm` for the given `address`. 228 | */ 229 | fun cc(address: InternetAddress) = recipient(RecipientType.CC, address) 230 | 231 | /** 232 | * Creates and returns a `RecipientTerm` specified as `RecipientType.CC`, using the given string pattern. 233 | * 234 | * @param str the string pattern for the generated term. 235 | * 236 | * @return a `RecipientType.CC`-based `RecipientTerm` for the given string pattern. 237 | */ 238 | fun cc(str: String) = recipient(RecipientType.CC, str) 239 | 240 | /** 241 | * Creates a `RecipientTerm` specified as `RecipientType.CC`, using the given `InternetAddress`, and stores it in this `SearchBuilder`. 242 | * 243 | * @param address the `InternetAddress` for the generated term. 244 | */ 245 | fun withCC(address: InternetAddress) = with(cc(address)) 246 | 247 | /** 248 | * Creates a `RecipientTerm` specified as `RecipientType.CC`, using the given string pattern, and stores it in this `SearchBuilder`. 249 | * 250 | * @param str the string pattern for the generated term. 251 | */ 252 | fun withCC(str: String) = with(cc(str)) 253 | 254 | /** 255 | * Creates and returns a `RecipientTerm` specified as `RecipientType.BCC`, using the given `InternetAddress`. 256 | * 257 | * @param address the `InternetAddress` for the generated term. 258 | * 259 | * @return a `RecipientType.BCC`-based `RecipientTerm` for the given `address`. 260 | */ 261 | fun bcc(address: InternetAddress) = recipient(RecipientType.BCC, address) 262 | 263 | /** 264 | * Creates and returns a `RecipientTerm` specified as `RecipientType.BCC`, using the given string pattern. 265 | * 266 | * @param str the string pattern for the generated term. 267 | * 268 | * @return a `RecipientType.BCC`-based `RecipientTerm` for the given string pattern. 269 | */ 270 | fun bcc(str: String) = recipient(RecipientType.BCC, str) 271 | 272 | /** 273 | * Creates a `RecipientTerm` specified as `RecipientType.BCC`, using the given `InternetAddress`, and stores it in this `SearchBuilder`. 274 | * 275 | * @param address the `InternetAddress` for the generated term. 276 | */ 277 | fun withBCC(address: InternetAddress) = with(bcc(address)) 278 | 279 | /** 280 | * Creates a `RecipientTerm` specified as `RecipientType.BCC`, using the given string pattern, and stores it in this `SearchBuilder`. 281 | * 282 | * @param str the string pattern for the generated term. 283 | */ 284 | fun withBCC(str: String) = with(bcc(str)) 285 | 286 | /** 287 | * Creates and returns a `SubjectTerm` using the given string pattern. 288 | * 289 | * @param str the string pattern for the generated term. 290 | * 291 | * @return a `SubjectTerm` for the given string pattern. 292 | */ 293 | fun subject(str: String) = SubjectTerm(str) 294 | 295 | /** 296 | * Creates a `SubjectTerm` using the given string pattern and stores it in this `SearchBuilder`. 297 | * 298 | * @param str the string pattern for the generated term. 299 | */ 300 | fun withSubject(str: String) = with(subject(str)) 301 | 302 | /** 303 | * Creates and returns a `BodyTerm` using the given string pattern. 304 | * 305 | * @param str the string pattern for the generated term. 306 | * 307 | * @return a `BodyTerm` for the given string pattern. 308 | */ 309 | fun body(str: String) = BodyTerm(str) 310 | 311 | /** 312 | * Creates a `BodyTerm` using the given string pattern and stores it in this `SearchBuilder`. 313 | * 314 | * @param str the string pattern for the generated term. 315 | */ 316 | fun withBody(str: String) = with(body(str)) 317 | 318 | /** 319 | * Creates and returns a `HeaderTerm` using the given header name and string pattern. 320 | * 321 | * @param headerName the header name to search for. 322 | * @param str the string pattern for the generated term. 323 | * 324 | * @return a `HeaderTerm` for the given header name and string pattern. 325 | */ 326 | fun header(headerName: String, str: String) = HeaderTerm(headerName, str) 327 | 328 | /** 329 | * Creates a `HeaderTerm` using the given header name and string pattern, and stores it in this `SearchBuilder`. 330 | * 331 | * @param headerName the header name to search for. 332 | * @param str the string pattern for the generated term. 333 | */ 334 | fun withHeader(headerName: String, str: String) = with(header(headerName, str)) 335 | 336 | /** 337 | * Creates and returns an `OrTerm` using the given `SearchTerm`s. 338 | * 339 | * @param first the first `SearchTerm` for the generated term. 340 | * @param second the second `SearchTerm` for the generated term. 341 | * 342 | * @return an `OrTerm` for the given `SearchTerm`s. 343 | */ 344 | fun or(first: SearchTerm, second: SearchTerm) = first or second 345 | 346 | /** 347 | * Creates an `OrTerm` using the given `SearchTerm`s, and stores it in this `SearchBuilder`. 348 | * 349 | * @param first the first `SearchTerm` for the generated term. 350 | * @param second the second `SearchTerm` for the generated term. 351 | */ 352 | fun withOr(first: SearchTerm, second: SearchTerm) = with(or(first, second)) 353 | 354 | /** 355 | * Creates and returns an `AndTerm` using the given `SearchTerm`s. 356 | * 357 | * @param first the first `SearchTerm` for the generated term. 358 | * @param second the second `SearchTerm` for the generated term. 359 | * 360 | * @return an `AndTerm` for the given `SearchTerm`s. 361 | */ 362 | fun and(first: SearchTerm, second: SearchTerm) = first + second 363 | 364 | /** 365 | * Creates an `AndTerm` using the given `SearchTerm`s, and stores it in this `SearchBuilder`. 366 | * 367 | * @param first the first `SearchTerm` for the generated term. 368 | * @param second the second `SearchTerm` for the generated term. 369 | */ 370 | fun withAnd(first: SearchTerm, second: SearchTerm) = with(and(first, second)) 371 | 372 | /** 373 | * Creates and returns a `ReceivedDateTerm` using the given comparison type and the specified `Date`. 374 | * 375 | * @param comp the comparison type for the search term. 376 | * @param date the date for comparison 377 | * 378 | * @return a `ReceivedDateTerm` for the given comparison and `Date`. 379 | */ 380 | fun received(comp: Int, date: Date) = ReceivedDateTerm(comp, date) 381 | 382 | /** 383 | * Creates a `ReceivedDateTerm` using the given comparison type and the specified `Date`, 384 | * and stores it within this `SearchBuilder`. 385 | * 386 | * @param comp the comparison type for the search term. 387 | * @param date the date for comparison 388 | */ 389 | fun withReceived(comp: Int, date: Date) = with(received(comp, date)) 390 | 391 | /** 392 | * Creates and returns a `ReceivedDateTerm` for items with a received date equal to the specified `Date`. 393 | * 394 | * @param date the date for comparison 395 | * 396 | * @return a `ReceivedDateTerm` for items equal to the given `Date`. 397 | */ 398 | fun receivedOn(date: Date) = ReceivedDate eq date 399 | 400 | /** 401 | * Creates a `ReceivedDateTerm` for items with a received date equal to the specified `Date`, and stores it 402 | * in this `SearchBuilder`. 403 | * 404 | * @param date the date for comparison 405 | */ 406 | fun withReceivedOn(date: Date) = with(receivedOn(date)) 407 | 408 | /** 409 | * Creates and returns a `ReceivedDateTerm` for items with a received date greater than or equal to the 410 | * specified `Date`. 411 | * 412 | * @param date the date for comparison 413 | * 414 | * @return a `ReceivedDateTerm` for items greater than or equal to the given `Date`. 415 | */ 416 | fun receivedOnOrAfter(date: Date) = ReceivedDate ge date 417 | 418 | /** 419 | * Creates a `ReceivedDateTerm` for items with a received date greater than or equal to the specified `Date`, 420 | * and stores it in this `SearchBuilder`. 421 | * 422 | * @param date the date for comparison 423 | */ 424 | fun withReceivedOnOrAfter(date: Date) = with(receivedOnOrAfter(date)) 425 | 426 | /** 427 | * Creates and returns a `ReceivedDateTerm` for items with a received date greater than the specified `Date`. 428 | * 429 | * @param date the date for comparison 430 | * 431 | * @return a `ReceivedDateTerm` for items greater than the given `Date`. 432 | */ 433 | fun receivedAfter(date: Date) = ReceivedDate gt date 434 | 435 | /** 436 | * Creates a `ReceivedDateTerm` for items with a received date greater than the specified `Date`, and stores it in 437 | * this `SearchBuilder`. 438 | * 439 | * @param date the date for comparison 440 | */ 441 | fun withReceivedAfter(date: Date) = with(receivedAfter(date)) 442 | 443 | /** 444 | * Creates and returns a `ReceivedDateTerm` for items with a received date less than or equal to the specified 445 | * `Date`. 446 | * 447 | * @param date the date for comparison 448 | * 449 | * @return a `ReceivedDateTerm` for items less than or equal to the given `Date`. 450 | */ 451 | fun receivedOnOrBefore(date: Date) = ReceivedDate le date 452 | 453 | /** 454 | * Creates a `ReceivedDateTerm` for items with a received date less than or equal to the specified `Date`, and 455 | * stores it in this `SearchBuilder`. 456 | * 457 | * @param date the date for comparison 458 | */ 459 | fun withReceivedOnOrBefore(date: Date) = with(receivedOnOrBefore(date)) 460 | 461 | /** 462 | * Creates and returns a `ReceivedDateTerm` for items with a received date less than the specified `Date`. 463 | * 464 | * @param date the date for comparison 465 | * 466 | * @return a `ReceivedDateTerm` for items less than the given `Date`. 467 | */ 468 | fun receivedBefore(date: Date) = ReceivedDate lt date 469 | 470 | /** 471 | * Creates a `ReceivedDateTerm` for items with a received date less than the specified `Date`, and stores it in 472 | * this `SearchBuilder`. 473 | * 474 | * @param date the date for comparison 475 | */ 476 | fun withReceivedBefore(date: Date) = with(receivedBefore(date)) 477 | 478 | /** 479 | * Creates and returns a `ReceivedDateTerm` for items with a received date not equal to the specified `Date`. 480 | * 481 | * @param date the date for comparison 482 | * 483 | * @return a `ReceivedDateTerm` for items not equal to the given `Date`. 484 | */ 485 | fun notReceivedOn(date: Date) = ReceivedDate ne date 486 | 487 | /** 488 | * Creates a `ReceivedDateTerm` for items with a received date not equal to the specified `Date`, and stores it 489 | * in this `SearchBuilder`. 490 | * 491 | * @param date the date for comparison 492 | */ 493 | fun withNotReceivedOn(date: Date) = with(notReceivedOn(date)) 494 | 495 | /** 496 | * Creates and returns an `AndTerm` for items with a received date greater than or equal to the starting value 497 | * of the date range, and less than or equal to the end value of the date range. 498 | * 499 | * @param dateRange the date range to check. 500 | * 501 | * @return an `AndTerm` applied to the start and end values of the date range. 502 | */ 503 | fun receivedBetween(dateRange: ClosedRange) = ReceivedDate between dateRange 504 | 505 | /** 506 | * Creates and returns an `AndTerm` for items with a received date greater than or equal to `earliest`, and less 507 | * than or equal to `latest`. 508 | * 509 | * @param earliest the start value of the date range to check. 510 | * @param latest the end value of the date range to check. 511 | * 512 | * @return an `AndTerm` applied to the start and end values of the date range. 513 | */ 514 | fun receivedBetween(earliest: Date, latest: Date) = ReceivedDate between earliest..latest 515 | 516 | /** 517 | * Creates an `AndTerm` for items with a received date greater than or equal to the starting value of the 518 | * date range, and less than or equal to the end value of the date range, and stores it in this `SearchBuilder`. 519 | * 520 | * @param dateRange the date range to check. 521 | */ 522 | fun withReceivedBetween(dateRange: ClosedRange) = with(receivedBetween(dateRange)) 523 | 524 | /** 525 | * Creates an `AndTerm` for items with a received date greater than or equal to `earliest`, and less than or equal to 526 | * `latest`, and stores it in this `SearchBuilder`. 527 | * 528 | * @param earliest the start value of the date range to check. 529 | * @param latest the end value of the date range to check. 530 | */ 531 | fun withReceivedBetween(earliest: Date, latest: Date) = with(receivedBetween(earliest, latest)) 532 | 533 | /** 534 | * Creates and returns a `SentDateTerm` using the given comparison type and the specified `Date`. 535 | * 536 | * @param comp the comparison type for the search term. 537 | * @param date the date for comparison 538 | * 539 | * @return a `SentDateTerm` for the given comparison and `Date`. 540 | */ 541 | fun sent(comp: Int, date: Date) = SentDateTerm(comp, date) 542 | 543 | /** 544 | * Creates a `SentDateTerm` using the given comparison type and the specified `Date`, and stores 545 | * is within this `SearchBuilder`. 546 | * 547 | * @param comp the comparison type for the search term. 548 | * @param date the date for comparison 549 | */ 550 | fun withSent(comp: Int, date: Date) = with(sent(comp, date)) 551 | 552 | /** 553 | * Creates and returns a `SentDateTerm` for items with a sent date equal to the specified `Date`. 554 | * 555 | * @param date the date for comparison 556 | * 557 | * @return a `SentDateTerm` for items equal to the given `Date`. 558 | */ 559 | fun sentOn(date: Date) = SentDate eq date 560 | 561 | /** 562 | * Creates a `SentDateTerm` for items with a sent date equal to the specified `Date`, and stores it 563 | * within this `SearchBuilder`. 564 | * 565 | * @param date the date for comparison 566 | */ 567 | fun withSentOn(date: Date) = with(sentOn(date)) 568 | 569 | /** 570 | * Creates and returns a `SentDateTerm` for items with a sent date greater than or equal to the specified `Date`. 571 | * 572 | * @param date the date for comparison 573 | * 574 | * @return a `SentDateTerm` for items greater than or equal to the given `Date`. 575 | */ 576 | fun sentOnOrAfter(date: Date) = SentDate ge date 577 | 578 | /** 579 | * Creates a `SentDateTerm` for items with a sent date greater than or equal to the specified `Date`, and stores 580 | * it within this `SearchBuilder`. 581 | * 582 | * @param date the date for comparison 583 | */ 584 | fun withSentOnOrAfter(date: Date) = with(sentOnOrAfter(date)) 585 | 586 | /** 587 | * Creates and returns a `SentDateTerm` for items with a sent date greater than the specified `Date`. 588 | * 589 | * @param date the date for comparison 590 | * 591 | * @return a `SentDateTerm` for items greater than to the given `Date`. 592 | */ 593 | fun sentAfter(date: Date) = SentDate gt date 594 | 595 | /** 596 | * Creates a `SentDateTerm` for items with a sent date greater than the specified `Date`, and stores it within 597 | * this `SearchBuilder`. 598 | * 599 | * @param date the date for comparison 600 | */ 601 | fun withSentAfter(date: Date) = with(sentAfter(date)) 602 | 603 | /** 604 | * Creates and returns a `SentDateTerm` for items with a sent date less than or equal to the specified `Date`. 605 | * 606 | * @param date the date for comparison 607 | * 608 | * @return a `SentDateTerm` for items less than or equal to the given `Date`. 609 | */ 610 | fun sentOnOrBefore(date: Date) = SentDate le date 611 | 612 | /** 613 | * Creates a `SentDateTerm` for items with a sent date less than or equal to the specified `Date`, and stores it 614 | * within this `SearchBuilder`. 615 | * 616 | * @param date the date for comparison 617 | */ 618 | fun withSentOnOrBefore(date: Date) = with(sentOnOrBefore(date)) 619 | 620 | /** 621 | * Creates and returns a `SentDateTerm` for items with a sent date less than the specified `Date`. 622 | * 623 | * @param date the date for comparison 624 | * 625 | * @return a `SentDateTerm` for items less than the given `Date`. 626 | */ 627 | fun sentBefore(date: Date) = SentDate lt date 628 | 629 | /** 630 | * Creates a `SentDateTerm` for items with a sent date less than the specified `Date`, and stores it within 631 | * this `SearchBuilder`. 632 | * 633 | * @param date the date for comparison 634 | */ 635 | fun withSentBefore(date: Date) = with(sentBefore(date)) 636 | 637 | /** 638 | * Creates and returns a `SentDateTerm` for items with a sent date not equal to the specified `Date`. 639 | * 640 | * @param date the date for comparison 641 | * 642 | * @return a `SentDateTerm` for items not equal to the given `Date`. 643 | */ 644 | fun notSentOn(date: Date) = SentDate ne date 645 | 646 | /** 647 | * Creates a `SentDateTerm` for items with a sent date not equal to the specified `Date`, and stores it within 648 | * this `SearchBuilder`. 649 | * 650 | * @param date the date for comparison 651 | */ 652 | fun withNotSentOn(date: Date) = with(notSentOn(date)) 653 | 654 | /** 655 | * Creates and returns an `AndTerm` for items with a sent date greater than or equal to the starting value of the 656 | * date range, and less than or equal to the end value of the date range. 657 | * 658 | * @param dateRange the date range to check. 659 | * 660 | * @return an `AndTerm` applied to the start and end values of the date range. 661 | */ 662 | fun sentBetween(dateRange: ClosedRange) = SentDate between dateRange 663 | 664 | /** 665 | * Creates and returns an `AndTerm` for items with a received date greater than or equal to `earliest`, and less 666 | * than or equal to `latest`. 667 | * 668 | * @param earliest the start value of the date range to check. 669 | * @param latest the end value of the date range to check. 670 | * 671 | * @return an `AndTerm` applied to the start and end values of the date range. 672 | */ 673 | fun sentBetween(earliest: Date, latest: Date) = SentDate between earliest..latest 674 | 675 | /** 676 | * Creates an `AndTerm` for items with a sent date greater than or equal to the starting value of the 677 | * date range, and less than or equal to the end value of the date range, and stores it in this `SearchBuilder`. 678 | * 679 | * @param dateRange the date range to check. 680 | */ 681 | fun withSentBetween(dateRange: ClosedRange) = with(sentBetween(dateRange)) 682 | 683 | /** 684 | * Creates an `AndTerm` for items with a sent date greater than or equal to `earliest`, and less than or equal to 685 | * `latest`, and stores it in this `SearchBuilder`. 686 | * 687 | * @param earliest the start value of the date range to check. 688 | * @param latest the end value of the date range to check. 689 | */ 690 | fun withSentBetween(earliest: Date, latest: Date) = with(sentBetween(earliest, latest)) 691 | 692 | /** 693 | * Creates and returns a `MessageIDTerm` using the given message id string. 694 | * 695 | * @param msgId the message id to search. 696 | * 697 | * @return a `MessageIDTerm` for the given message id. 698 | */ 699 | fun messageId(msgId: String) = MessageIDTerm(msgId) 700 | 701 | /** 702 | * Creates a `MessageIDTerm` using the given message id string, and stores it within 703 | * this `SearchBuilder`. 704 | * 705 | * @param msgId the message id to search. 706 | */ 707 | fun withMessageId(msgId: String) = with(messageId(msgId)) 708 | 709 | /** 710 | * Creates and returns a `MessageNumberTerm` using the given message number. 711 | * 712 | * @param number the message number to search. 713 | * 714 | * @return a `MessageNumberTerm` for the given message number. 715 | */ 716 | fun messageNumber(number: Int) = MessageNumberTerm(number) 717 | 718 | /** 719 | * Creates a `MessageNumberTerm` using the given message number, and stores it within 720 | * this `SearchBuilder`. 721 | * 722 | * @param number the message id to search. 723 | */ 724 | fun withMessageNumber(number: Int) = with(messageNumber(number)) 725 | 726 | /** 727 | * Creates and returns a `SizeTerm` using the given comparison type and the specified size. 728 | * 729 | * @param comp the comparison type for the search term. 730 | * @param size the size for comparison 731 | * 732 | * @return a `SizeTerm` for the given comparison and size. 733 | */ 734 | fun size(comp: Int, size: Int) = SizeTerm(comp, size) 735 | 736 | /** 737 | * Creates a `SizeTerm` using the given comparison type and the specified size, and stores it 738 | * within this `SearchBuilder`. 739 | * 740 | * @param comp the comparison type for the search term. 741 | * @param size the size for comparison 742 | * 743 | * @return a `SizeTerm` for the given comparison and size. 744 | */ 745 | fun withSize(comp: Int, size: Int) = with(size(comp, size)) 746 | 747 | /** 748 | * Creates and returns a `SizeTerm` for items with a size equal to the specified size. 749 | * 750 | * @param size the size for comparison 751 | * 752 | * @return a `SizeTerm` for items equal to the given size. 753 | */ 754 | fun sizeIs(size: Int) = Size eq size 755 | 756 | /** 757 | * Creates a `SizeTerm` for items with a size equal to the specified size, and stores it within this 758 | * `SearchBuilder`. 759 | * 760 | * @param size the size for comparison 761 | */ 762 | fun withSizeIs(size: Int) = with(sizeIs(size)) 763 | 764 | /** 765 | * Creates and returns a `SizeTerm` for items with a size greater than or equal to the specified size. 766 | * 767 | * @param size the size for comparison 768 | * 769 | * @return a `SizeTerm` for items greater than or equal to the given size. 770 | */ 771 | fun sizeIsAtLeast(size: Int) = Size ge size 772 | 773 | /** 774 | * Creates a `SizeTerm` for items with a size greater than or equal to the specified size, and stores it within 775 | * this `SearchBuilder`. 776 | * 777 | * @param size the size for comparison 778 | */ 779 | fun withSizeIsAtLeast(size: Int) = with(sizeIsAtLeast(size)) 780 | 781 | /** 782 | * Creates and returns a `SizeTerm` for items with a size greater than the specified size. 783 | * 784 | * @param size the size for comparison 785 | * 786 | * @return a `SizeTerm` for items greater than the given size. 787 | */ 788 | fun sizeIsGreaterThan(size: Int) = Size gt size 789 | 790 | /** 791 | * Creates a `SizeTerm` for items with a size greater than the specified size, and stores it within this 792 | * `SearchBuilder`. 793 | * 794 | * @param size the size for comparison 795 | */ 796 | fun withSizeIsGreaterThan(size: Int) = with(sizeIsGreaterThan(size)) 797 | 798 | /** 799 | * Creates and returns a `SizeTerm` for items with a size less than or equal to the specified size. 800 | * 801 | * @param size the size for comparison 802 | * 803 | * @return a `SizeTerm` for items less than or equal to the given size. 804 | */ 805 | fun sizeIsNoMoreThan(size: Int) = Size le size 806 | 807 | /** 808 | * Creates a `SizeTerm` for items with a size less than or equal to the specified size, and stores it within 809 | * this `SearchBuilder`. 810 | * 811 | * @param size the size for comparison 812 | */ 813 | fun withSizeIsNoMoreThan(size: Int) = with(sizeIsNoMoreThan(size)) 814 | 815 | /** 816 | * Creates and returns a `SizeTerm` for items with a size less than the specified size. 817 | * 818 | * @param size the size for comparison 819 | * 820 | * @return a `SizeTerm` for items less than the given size. 821 | */ 822 | fun sizeIsLessThan(size: Int) = Size lt size 823 | 824 | /** 825 | * Creates a `SizeTerm` for items with a size less than the specified size, and stores it within this 826 | * `SearchBuilder`. 827 | * 828 | * @param size the size for comparison 829 | */ 830 | fun withSizeIsLessThan(size: Int) = with(sizeIsLessThan(size)) 831 | 832 | /** 833 | * Creates and returns a `SizeTerm` for items with a size not equal to the specified size. 834 | * 835 | * @param size the size for comparison 836 | * 837 | * @return a `SizeTerm` for items not equal to the given size. 838 | */ 839 | fun sizeIsNot(size: Int) = Size ne size 840 | 841 | /** 842 | * Creates a `SizeTerm` for items with a size not equal to the specified size, and stores it within this 843 | * `SearchBuilder`. 844 | * 845 | * @param size the size for comparison 846 | */ 847 | fun withSizeIsNot(size: Int) = with(sizeIsNot(size)) 848 | 849 | /** 850 | * Creates and returns an `AndTerm` for items with a size greater than or equal to the start value of the size range, 851 | * and less than or equal to the end value of the size range. 852 | * 853 | * @param sizeRange the size range to check. 854 | * 855 | * @return an `AndTerm` applied to the start and end values of the size range. 856 | */ 857 | fun sizeBetween(sizeRange: IntRange) = Size between sizeRange 858 | 859 | /** 860 | * Creates and returns an `AndTerm` for items with a size greater than or equal to `smallest`, and less 861 | * than or equal to `largest`. 862 | * 863 | * @param smallest the start value of the size range to check. 864 | * @param largest the end value of the size range to check. 865 | * 866 | * @return an `AndTerm` applied to the start and end values of the size range. 867 | */ 868 | fun sizeBetween(smallest: Int, largest: Int) = Size between smallest..largest 869 | 870 | /** 871 | * Creates an `AndTerm` for items with a size greater than or equal to the start value of the size range, and less 872 | * than or equal to the end value of the size range; the created term is stored within this `SearchBuilder`. 873 | * 874 | * @param sizeRange the size range to check. 875 | */ 876 | fun withSizeBetween(sizeRange: IntRange) = with(sizeBetween(sizeRange)) 877 | 878 | /** 879 | * Creates an `AndTerm` for items with a size greater than or equal to `smallest`, and less than or equal to 880 | * `largest`; the created term is stored within this `SearchBuilder`. 881 | * 882 | * @param smallest the start value of the size range to check. 883 | * @param largest the end value of the size range to check. 884 | */ 885 | fun withSizeBetween(smallest: Int, largest: Int) = with(sizeBetween(smallest, largest)) 886 | 887 | /** 888 | * Creates and returns a `FlagTerm` using the given flags and set value. For example, searching with a `Flags` 889 | * object containing `Flags.Flag.DRAFT` and a `set` value of `true` would return everything with the `DRAFT` flag 890 | * set to `true`; searching with the `set` value of `false` instead would return everything with the `DRAFT` flag 891 | * set to `false`. 892 | * 893 | * @param flags the flags to search 894 | * @param set the set value to search 895 | * 896 | * @return a `FlagTerm` for the given flags and set value. 897 | */ 898 | fun flags(flags: Flags, set: Boolean) = FlagTerm(flags.javaMailFlags, set) 899 | 900 | /** 901 | * Creates a `FlagTerm` using the given flags and set value, and stores it within this `SearchBuilder`. For example, 902 | * searching with a `Flags` object containing `Flags.Flag.DRAFT` and a `set` value of `true` would return 903 | * everything with the `DRAFT` flag set to `true`; searching with the `set` value of `false` instead would return 904 | * everything with the `DRAFT` flag set to `false`. 905 | * 906 | * @param flags the flags to search 907 | * @param set the set value to search 908 | */ 909 | fun withFlags(flags: Flags, set: Boolean) = with(flags(flags, set)) 910 | 911 | /** 912 | * Creates and returns a `ModifiedSinceTerm` using the given modification sequence. 913 | * 914 | * @param modSequence the modification sequence to search. 915 | * 916 | * @return a `ModifiedSinceTerm` for the given modification sequence. 917 | */ 918 | fun modifiedSince(modSequence: Long) = ModifiedSinceTerm(modSequence) 919 | 920 | /** 921 | * Creates a `ModifiedSinceTerm` using the given modification sequence, and stores it within this `SearchBuilder`. 922 | * 923 | * @param modSequence the modification sequence to search. 924 | */ 925 | fun withModifiedSince(modSequence: Long) = with(modifiedSince(modSequence)) 926 | 927 | /** 928 | * Creates and returns an `OlderTerm` using the given interval. 929 | * 930 | * @param interval the message number to search. 931 | * 932 | * @return a `YoungerTerm` for the given interval. 933 | */ 934 | fun older(interval: Int) = OlderTerm(interval) 935 | 936 | /** 937 | * Creates an `OlderTerm` using the given interval, and stores it within 938 | * this `SearchBuilder`. 939 | * 940 | * @param interval the interval to search. 941 | */ 942 | fun withOlder(interval: Int) = with(older(interval)) 943 | 944 | /** 945 | * Creates and returns a `YoungerTerm` using the given interval. 946 | * 947 | * @param interval the message number to search. 948 | * 949 | * @return a `YoungerTerm` for the given interval. 950 | */ 951 | fun younger(interval: Int) = YoungerTerm(interval) 952 | 953 | /** 954 | * Creates a `YoungerTerm` using the given interval, and stores it within 955 | * this `SearchBuilder`. 956 | * 957 | * @param interval the interval to search. 958 | */ 959 | fun withYounger(interval: Int) = with(younger(interval)) 960 | 961 | /** 962 | * Adds the specified `SearchTerm` to this `SearchBuilder`. 963 | * 964 | * @param term the `SearchTerm` to add. 965 | */ 966 | fun with(term: SearchTerm) = terms.add(term) 967 | 968 | /** 969 | * Sorts the search results by the terms applied in the block. 970 | * 971 | * @param block the block to use to initialize the [SortBuilder]. 972 | */ 973 | fun sortedBy(block: SortBuilder.() -> Unit) { 974 | val sortBuilder = SortBuilder() 975 | 976 | sortBuilder.block() 977 | 978 | sortedBy.addAll(sortBuilder.build()) 979 | } 980 | 981 | /** 982 | * Adds the `SearchTerm` operand to this `SearchBuilder`. 983 | */ 984 | operator fun SearchTerm.unaryPlus() = terms.add(this) 985 | 986 | /** 987 | * Adds a `NotTerm` for the `SearchTerm` operand to this `SearchBuilder`. 988 | */ 989 | operator fun SearchTerm.unaryMinus() = terms.add(!this) 990 | } 991 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/slothLabs/mail/imap/SearchTerms.kt: -------------------------------------------------------------------------------- 1 | package io.github.slothLabs.mail.imap 2 | 3 | import java.util.Date 4 | import javax.mail.search.AndTerm 5 | import javax.mail.search.ComparisonTerm 6 | import javax.mail.search.NotTerm 7 | import javax.mail.search.OrTerm 8 | import javax.mail.search.ReceivedDateTerm 9 | import javax.mail.search.SearchTerm 10 | import javax.mail.search.SentDateTerm 11 | import javax.mail.search.SizeTerm 12 | 13 | /** 14 | * Utilities to make building `ReceivedDateTerm` instances fun and exciting. Kinda. 15 | */ 16 | object ReceivedDate { 17 | /** 18 | * Creates a new `ReceivedDateTerm` for dates equal to the specified date. Basic usage would 19 | * be: 20 | * 21 | * ``` 22 | * val theDate = Date() 23 | * val term = ReceivedDate eq theDate 24 | * ``` 25 | * 26 | * @param date the date for comparison 27 | * 28 | * @return a new `ReceivedDateTerm` for dates equal to the specified date. 29 | */ 30 | infix fun eq(date: Date) = ReceivedDateTerm(ComparisonTerm.EQ, date) 31 | 32 | /** 33 | * Creates a new `ReceivedDateTerm` for dates not equal to the specified date. Basic usage would 34 | * be: 35 | * 36 | * ``` 37 | * val theDate = Date() 38 | * val term = ReceivedDate ne theDate 39 | * ``` 40 | * 41 | * @param date the date for comparison 42 | * 43 | * @return a new `ReceivedDateTerm` for dates not equal to the specified date. 44 | */ 45 | infix fun ne(date: Date) = ReceivedDateTerm(ComparisonTerm.NE, date) 46 | 47 | /** 48 | * Creates a new `ReceivedDateTerm` for dates less than the specified date. Basic usage would 49 | * be: 50 | * 51 | * ``` 52 | * val theDate = Date() 53 | * val term = ReceivedDate lt theDate 54 | * ``` 55 | * 56 | * @param date the date for comparison 57 | * 58 | * @return a new `ReceivedDateTerm` for dates less than the specified date. 59 | */ 60 | infix fun lt(date: Date) = ReceivedDateTerm(ComparisonTerm.LT, date) 61 | 62 | /** 63 | * Creates a new `ReceivedDateTerm` for dates less than or equal to the specified date. Basic usage would 64 | * be: 65 | * 66 | * ``` 67 | * val theDate = Date() 68 | * val term = ReceivedDate le theDate 69 | * ``` 70 | * 71 | * @param date the date for comparison 72 | * 73 | * @return a new `ReceivedDateTerm` for dates less than or equal to the specified date. 74 | */ 75 | infix fun le(date: Date) = ReceivedDateTerm(ComparisonTerm.LE, date) 76 | 77 | /** 78 | * Creates a new `ReceivedDateTerm` for dates greater than the specified date. Basic usage would 79 | * be: 80 | * 81 | * ``` 82 | * val theDate = Date() 83 | * val term = ReceivedDate gt theDate 84 | * ``` 85 | * 86 | * @param date the date for comparison 87 | * 88 | * @return a new `ReceivedDateTerm` for dates greater than the specified date. 89 | */ 90 | infix fun gt(date: Date) = ReceivedDateTerm(ComparisonTerm.GT, date) 91 | 92 | /** 93 | * Creates a new `ReceivedDateTerm` for dates greater than or equal to the specified date. Basic usage would 94 | * be: 95 | * 96 | * ``` 97 | * val theDate = Date() 98 | * val term = ReceivedDate ge theDate 99 | * ``` 100 | * 101 | * @param date the date for comparison 102 | * 103 | * @return a new `ReceivedDateTerm` for dates greater than or equal to the specified date. 104 | */ 105 | infix fun ge(date: Date) = ReceivedDateTerm(ComparisonTerm.GE, date) 106 | 107 | /** 108 | * Creates an `AndTerm` for items with a received date greater than or equal to the starting value 109 | * of the date range, and less than or equal to the end value of the date range. Basic usage would be: 110 | * 111 | * ``` 112 | * val theDates = getSomeStartDate() .. getSomeEndDate() 113 | * val term = ReceivedDate between theDates 114 | * 115 | * // or even... 116 | * val term = ReceivedDate between getSomeStartDate() .. getSomeEndDate() 117 | * ``` 118 | * 119 | * @param range the date range to check. 120 | * 121 | * @return an `AndTerm` applied to the start and end values of the date range. 122 | */ 123 | infix fun between(range: ClosedRange) = (this ge range.start) and (this le range.endInclusive) 124 | } 125 | 126 | /** 127 | * Utilities to make building `SentDateTerm` instances fun and exciting. Kinda. 128 | */ 129 | object SentDate { 130 | /** 131 | * Creates a new `SentDateTerm` for dates equal to the specified date. Basic usage would 132 | * be: 133 | * 134 | * ``` 135 | * val theDate = Date() 136 | * val term = SentDate eq theDate 137 | * ``` 138 | * 139 | * @param date the date for comparison 140 | * 141 | * @return a new `SentDateTerm` for dates equal to the specified date. 142 | */ 143 | infix fun eq(date: Date) = SentDateTerm(ComparisonTerm.EQ, date) 144 | 145 | /** 146 | * Creates a new `SentDateTerm` for dates not equal to the specified date. Basic usage would 147 | * be: 148 | * 149 | * ``` 150 | * val theDate = Date() 151 | * val term = SentDate ne theDate 152 | * ``` 153 | * 154 | * @param date the date for comparison 155 | * 156 | * @return a new `SentDateTerm` for dates not equal to the specified date. 157 | */ 158 | infix fun ne(date: Date) = SentDateTerm(ComparisonTerm.NE, date) 159 | 160 | /** 161 | * Creates a new `SentDateTerm` for dates less than the specified date. Basic usage would 162 | * be: 163 | * 164 | * ``` 165 | * val theDate = Date() 166 | * val term = SentDate lt theDate 167 | * ``` 168 | * 169 | * @param date the date for comparison 170 | * 171 | * @return a new `SentDateTerm` for dates less than the specified date. 172 | */ 173 | infix fun lt(date: Date) = SentDateTerm(ComparisonTerm.LT, date) 174 | 175 | /** 176 | * Creates a new `SentDateTerm` for dates less than or equal to the specified date. Basic usage would 177 | * be: 178 | * 179 | * ``` 180 | * val theDate = Date() 181 | * val term = SentDate le theDate 182 | * ``` 183 | * 184 | * @param date the date for comparison 185 | * 186 | * @return a new `SentDateTerm` for dates less than or equal to the specified date. 187 | */ 188 | infix fun le(date: Date) = SentDateTerm(ComparisonTerm.LE, date) 189 | 190 | /** 191 | * Creates a new `SentDateTerm` for dates greater than the specified date. Basic usage would 192 | * be: 193 | * 194 | * ``` 195 | * val theDate = Date() 196 | * val term = SentDate gt theDate 197 | * ``` 198 | * 199 | * @param date the date for comparison 200 | * 201 | * @return a new `SentDateTerm` for dates greater than the specified date. 202 | */ 203 | infix fun gt(date: Date) = SentDateTerm(ComparisonTerm.GT, date) 204 | 205 | /** 206 | * Creates a new `SentDateTerm` for dates greater than or equal to the specified date. Basic usage would 207 | * be: 208 | * 209 | * ``` 210 | * val theDate = Date() 211 | * val term = SentDate ge theDate 212 | * ``` 213 | * 214 | * @param date the date for comparison 215 | * 216 | * @return a new `SentDateTerm` for dates greater than or equal to the specified date. 217 | */ 218 | infix fun ge(date: Date) = SentDateTerm(ComparisonTerm.GE, date) 219 | 220 | /** 221 | * Creates an `AndTerm` for items with a received date greater than or equal to the starting value 222 | * of the date range, and less than or equal to the end value of the date range. Basic usage would be: 223 | * 224 | * ``` 225 | * val theDates = getSomeStartDate() .. getSomeEndDate() 226 | * val term = SentDate between theDates 227 | * 228 | * // or even... 229 | * val term = SentDate between getSomeStartDate() .. getSomeEndDate() 230 | * ``` 231 | * 232 | * @param range the date range to check. 233 | * 234 | * @return an `AndTerm` applied to the start and end values of the date range. 235 | */ 236 | infix fun between(range: ClosedRange) = (this ge range.start) and (this le range.endInclusive) 237 | } 238 | 239 | /** 240 | * Utilities to make building `SizeTerm` instances fun and exciting. Kinda. 241 | */ 242 | object Size { 243 | /** 244 | * Creates a new `SizeTerm` for sizes equal to the specified size. Basic usage would 245 | * be: 246 | * 247 | * ``` 248 | * val theSize = Size() 249 | * val term = Size eq theSize 250 | * ``` 251 | * 252 | * @param size the size for comparison 253 | * 254 | * @return a new `SizeTerm` for sizes equal to the specified size. 255 | */ 256 | infix fun eq(size: Int) = SizeTerm(ComparisonTerm.EQ, size) 257 | 258 | /** 259 | * Creates a new `SizeTerm` for sizes not equal to the specified size. Basic usage would 260 | * be: 261 | * 262 | * ``` 263 | * val theSize = Size() 264 | * val term = Size ne theSize 265 | * ``` 266 | * 267 | * @param size the size for comparison 268 | * 269 | * @return a new `SizeTerm` for sizes not equal to the specified size. 270 | */ 271 | infix fun ne(size: Int) = SizeTerm(ComparisonTerm.NE, size) 272 | 273 | /** 274 | * Creates a new `SizeTerm` for sizes less than the specified size. Basic usage would 275 | * be: 276 | * 277 | * ``` 278 | * val theSize = Size() 279 | * val term = Size lt theSize 280 | * ``` 281 | * 282 | * @param size the size for comparison 283 | * 284 | * @return a new `SizeTerm` for sizes less than the specified size. 285 | */ 286 | infix fun lt(size: Int) = SizeTerm(ComparisonTerm.LT, size) 287 | 288 | /** 289 | * Creates a new `SizeTerm` for sizes less than or equal to the specified size. Basic usage would 290 | * be: 291 | * 292 | * ``` 293 | * val theSize = Size() 294 | * val term = Size le theSize 295 | * ``` 296 | * 297 | * @param size the size for comparison 298 | * 299 | * @return a new `SizeTerm` for sizes less than or equal to the specified size. 300 | */ 301 | infix fun le(size: Int) = SizeTerm(ComparisonTerm.LE, size) 302 | 303 | /** 304 | * Creates a new `SizeTerm` for sizes greater than the specified size. Basic usage would 305 | * be: 306 | * 307 | * ``` 308 | * val theSize = Size() 309 | * val term = Size gt theSize 310 | * ``` 311 | * 312 | * @param size the size for comparison 313 | * 314 | * @return a new `SizeTerm` for sizes greater than the specified size. 315 | */ 316 | infix fun gt(size: Int) = SizeTerm(ComparisonTerm.GT, size) 317 | 318 | /** 319 | * Creates a new `SizeTerm` for sizes greater than or equal to the specified size. Basic usage would 320 | * be: 321 | * 322 | * ``` 323 | * val theSize = Size() 324 | * val term = Size ge theSize 325 | * ``` 326 | * 327 | * @param size the size for comparison 328 | * 329 | * @return a new `SizeTerm` for sizes greater than or equal to the specified size. 330 | */ 331 | infix fun ge(size: Int) = SizeTerm(ComparisonTerm.GE, size) 332 | 333 | /** 334 | * Creates an `AndTerm` for items with a received size greater than or equal to the starting value 335 | * of the size range, and less than or equal to the end value of the size range. Basic usage would be: 336 | * 337 | * ``` 338 | * val theSizes = getSomeStartSize() .. getSomeEndSize() 339 | * val term = Size between theSizes 340 | * 341 | * // or even... 342 | * val term = Size between getSomeStartSize() .. getSomeEndSize() 343 | * ``` 344 | * 345 | * @param range the size range to check. 346 | * 347 | * @return an `AndTerm` applied to the start and end values of the size range. 348 | */ 349 | infix fun between(range: ClosedRange) = (this ge range.start) and (this le range.endInclusive) 350 | } 351 | 352 | /** 353 | * Creates a new `AndTerm` applied to the `this` term and the parameter term. 354 | * 355 | * @param other the other `SearchTerm` to "and" against. 356 | * 357 | * @return an `AndTerm` referring to `this` and `other`. 358 | */ 359 | infix fun SearchTerm.and(other: SearchTerm): SearchTerm = AndTerm(this, other) 360 | 361 | /** 362 | * Creates a new `OrTerm` applied to the `this` term and the parameter term. 363 | * 364 | * @param other the other `SearchTerm` to "or" against. 365 | * 366 | * @return an `OrTerm` referring to `this` and `other`. 367 | */ 368 | infix fun SearchTerm.or(other: SearchTerm): SearchTerm = OrTerm(this, other) 369 | 370 | /** 371 | * Creates a new `NotTerm` applied to `this` term. 372 | * 373 | * @return a `NotTerm` referring to `this`. 374 | */ 375 | operator fun SearchTerm.not(): SearchTerm = if (this is NotTerm) term else NotTerm(this) 376 | 377 | /** 378 | * Creates a new `AndTerm` applied to the `this` term and the parameter term. 379 | * 380 | * @param other the other `SearchTerm` to "and" against. 381 | * 382 | * @return an `AndTerm` referring to `this` and `other`. 383 | */ 384 | operator fun SearchTerm.plus(other: SearchTerm) = AndTerm(this, other) 385 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/slothLabs/mail/imap/SortBuilder.kt: -------------------------------------------------------------------------------- 1 | package io.github.slothLabs.mail.imap 2 | 3 | import com.sun.mail.imap.SortTerm 4 | 5 | /** 6 | * Builder class to make sorting messages a little easier to work with. 7 | */ 8 | class SortBuilder { 9 | 10 | private val sortTerms = mutableListOf() 11 | 12 | /** 13 | * Gets all of the sort terms built with this instance. 14 | * 15 | * @return a `List` of [Sort] instances built with this builder. 16 | */ 17 | fun build(): List = sortTerms 18 | 19 | /** 20 | * Adds the specified [Sort] term to this builder. 21 | * 22 | * @param sort the [Sort] term to add. 23 | */ 24 | fun add(sort: Sort) = sortTerms.add(sort) 25 | 26 | /** 27 | * Adds the specified `SortTerm` to this builder. 28 | * 29 | * @param sort the `SortTerm` to add. 30 | */ 31 | fun add(sort: SortTerm) = Sort.fromSortTerm(sort)?.let { add(it) } 32 | 33 | /** 34 | * Shorthand to add a `SortTerm` to this builder. 35 | */ 36 | operator fun SortTerm.unaryPlus() { 37 | add(this) 38 | } 39 | 40 | /** 41 | * Shorthand to add a Reversed `SortTerm` to this builder. 42 | */ 43 | operator fun SortTerm.unaryMinus() { 44 | add(Sort.Reverse) 45 | add(this) 46 | } 47 | 48 | /** 49 | * Shorthand to add a [Sort] to this builder. 50 | */ 51 | operator fun Sort.unaryPlus() { 52 | add(this) 53 | } 54 | 55 | /** 56 | * Shorthand to add a reversed [Sort] to this builder. 57 | */ 58 | operator fun Sort.unaryMinus() { 59 | add(Sort.Reverse) 60 | add(this) 61 | } 62 | } 63 | 64 | /** 65 | * Enum to wrap the standard JavaMail `SortTerm` constants. 66 | */ 67 | enum class Sort(private val javaMailSortTerm: SortTerm) { 68 | /** 69 | * Indicates sorting by the first from address. 70 | */ 71 | From(SortTerm.FROM), 72 | 73 | /** 74 | * Indicates sorting by the first TO recipient address. 75 | */ 76 | To(SortTerm.TO), 77 | 78 | /** 79 | * Indicates sorting by the message subject. 80 | */ 81 | Subject(SortTerm.SUBJECT), 82 | 83 | /** 84 | * Indicates sorting by the message arrival date/time. 85 | */ 86 | Arrival(SortTerm.ARRIVAL), 87 | 88 | /** 89 | * Indicates sorting by the message sent date/time (analogous with `SortTerm.DATE`). 90 | */ 91 | Sent(SortTerm.DATE), 92 | 93 | /** 94 | * Indicates sorting by the first CC recipient address. 95 | */ 96 | CC(SortTerm.CC), 97 | 98 | /** 99 | * Indicates to reverse the sort order of the next term in 100 | * the sort terms. 101 | */ 102 | Reverse(SortTerm.REVERSE), 103 | 104 | /** 105 | * Indicates sorting by message size. 106 | */ 107 | Size(SortTerm.SIZE); 108 | 109 | /** 110 | * Gets the associated JavaMail `SortTerm` for this value. 111 | */ 112 | fun toSortTerm() = this.javaMailSortTerm 113 | 114 | companion object { 115 | /** 116 | * Creates an `Option` from the given JavaMail `SortTerm`. This method *should* 117 | * always return a `Some` instance; if it returns `None`, that would likely indicate 118 | * an unexpected (read: new) `SortTerm` was supplied and the [Sort] enum hasn't been updated 119 | * to reflect the change. 120 | * 121 | * @param javaMailSortTerm the `SortTerm` to find the `Sort` value for. 122 | * 123 | * @return an `Option` that matches the specified sort term. 124 | */ 125 | fun fromSortTerm(javaMailSortTerm: SortTerm): Sort? = values().find { it.javaMailSortTerm == javaMailSortTerm } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/main/kotlin/io/github/slothLabs/mail/util/Result.kt: -------------------------------------------------------------------------------- 1 | package io.github.slothLabs.mail.util 2 | 3 | sealed class Result { 4 | abstract val success: Boolean 5 | 6 | abstract val failure: Boolean 7 | 8 | abstract fun ifPresent(action: T.() -> Unit) 9 | 10 | companion object { 11 | fun success(value: T): Result { 12 | return Success(value) 13 | } 14 | 15 | fun failure(message: String): Result { 16 | return Failure(message) 17 | } 18 | } 19 | 20 | private class Success(private val value: T) : Result() { 21 | override val success: Boolean 22 | get() = true 23 | 24 | override val failure: Boolean 25 | get() = false 26 | 27 | override fun ifPresent(action: T.() -> Unit) { 28 | value.action() 29 | } 30 | } 31 | 32 | open class Failure(private val message: String) : Result() { 33 | override fun ifPresent(action: T.() -> Unit) {} 34 | 35 | override val success: Boolean 36 | get() = false 37 | 38 | override val failure: Boolean 39 | get() = true 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/kotlin/io/github/slothLabs/mail/imap/ImapConnectionTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.slothLabs.mail.imap 2 | 3 | import com.icegreen.greenmail.util.GreenMail 4 | import com.icegreen.greenmail.util.GreenMailUtil 5 | import com.icegreen.greenmail.util.ServerSetupTest 6 | import com.sun.mail.imap.IMAPFolder.FetchProfileItem 7 | import com.sun.mail.imap.SortTerm 8 | import io.github.slothLabs.mail.imap.Sort.From 9 | import io.github.slothLabs.mail.imap.Sort.Subject 10 | import io.github.slothLabs.mail.imap.Sort.To 11 | import io.kotest.core.spec.Spec 12 | import io.kotest.core.spec.style.AnnotationSpec 13 | import io.kotest.core.test.TestCase 14 | import io.kotest.matchers.nulls.shouldNotBeNull 15 | import io.kotest.matchers.shouldBe 16 | import java.time.temporal.ChronoUnit 17 | 18 | class ImapConnectionTest : AnnotationSpec() { 19 | 20 | private val greenMail = GreenMail(ServerSetupTest.SMTP_IMAP) 21 | 22 | private val host: String 23 | get() = greenMail.imap.bindTo 24 | 25 | private val port: Int 26 | get() = greenMail.imap.port 27 | 28 | override fun beforeTest(testCase: TestCase) { 29 | super.beforeTest(testCase) 30 | greenMail.reset() 31 | } 32 | 33 | override fun afterSpec(spec: Spec) { 34 | super.afterSpec(spec) 35 | greenMail.stop() 36 | } 37 | 38 | @Test 39 | fun shouldHandlePlainTextBody() { 40 | val emailAddress = "test@localhost.com" 41 | val fromAddress = "from@localhost.com" 42 | val password = "password" 43 | val subject = "Test Email" 44 | val testBodyText = "Body Text goes here!" 45 | 46 | greenMail.setUser(emailAddress, password) 47 | 48 | GreenMailUtil.sendTextEmailTest(emailAddress, fromAddress, subject, testBodyText) 49 | 50 | var processed = false 51 | imap(host, port, emailAddress, password) { 52 | folder("INBOX", FolderModes.ReadOnly) { 53 | val msg = this[1] 54 | msg.shouldNotBeNull() 55 | msg { 56 | testBodyText shouldBe bodyText 57 | headers.isEmpty() shouldBe false 58 | 59 | processed = true 60 | } 61 | } 62 | } 63 | 64 | processed shouldBe true 65 | } 66 | 67 | @Test 68 | fun shouldHandlePlainTextBodyWithConnectionInformationObject() { 69 | val emailAddress = "test@localhost.com" 70 | val fromAddress = "from@localhost.com" 71 | val password = "password" 72 | val subject = "Test Email" 73 | val testBodyText = "Body Text goes here!" 74 | 75 | greenMail.setUser(emailAddress, password) 76 | 77 | GreenMailUtil.sendTextEmailTest(emailAddress, fromAddress, subject, testBodyText) 78 | 79 | var processed = false 80 | 81 | val conInfo = ConnectionInformation(host, port, emailAddress, password) 82 | imap(conInfo) { 83 | folder("INBOX", FolderModes.ReadOnly) { 84 | val msg = this[1] 85 | msg.shouldNotBeNull() 86 | msg { 87 | testBodyText shouldBe bodyText 88 | headers.isEmpty() shouldBe false 89 | 90 | processed = true 91 | } 92 | } 93 | } 94 | 95 | processed shouldBe true 96 | } 97 | 98 | @Test 99 | fun searchStuff() { 100 | val emailAddress = "test@localhost.com" 101 | val fromAddress = "from@localhost.com" 102 | val password = "password" 103 | val subject = "Test Email" 104 | val testBodyText = "Body Text goes here!" 105 | 106 | greenMail.setUser(emailAddress, password) 107 | 108 | GreenMailUtil.sendTextEmailTest(emailAddress, fromAddress, subject, testBodyText) 109 | val mailSentDatePlusOneDay = greenMail.firstReceivedMailSentDatePlusOffset(1, ChronoUnit.DAYS) 110 | 111 | var processed = false 112 | 113 | val conInfo = ConnectionInformation(host, port, emailAddress, password) 114 | val msgList = mutableListOf() 115 | imap(conInfo) { 116 | folder("INBOX", FolderModes.ReadOnly) { 117 | preFetchBy(FetchProfileItem.MESSAGE) 118 | val results = search { 119 | withFrom(fromAddress) 120 | withSentOnOrBefore(mailSentDatePlusOneDay) 121 | } 122 | msgList.addAll(results) 123 | 124 | processed = true 125 | } 126 | } 127 | 128 | msgList.isNotEmpty() shouldBe true 129 | 130 | val first = msgList[0] 131 | fromAddress shouldBe first.from 132 | testBodyText.trim() shouldBe first.bodyText.trim() 133 | first.headers.isEmpty() shouldBe false 134 | 135 | processed shouldBe true 136 | } 137 | 138 | @Test 139 | fun otherSearchStuff() { 140 | val emailAddress = "test@localhost.com" 141 | val fromAddress = "from@localhost.com" 142 | val password = "password" 143 | val subject = "Test Email" 144 | val testBodyText = "Body Text goes here!" 145 | 146 | greenMail.setUser(emailAddress, password) 147 | 148 | GreenMailUtil.sendTextEmailTest(emailAddress, fromAddress, subject, testBodyText) 149 | val mailSentDatePlusOneDay = greenMail.firstReceivedMailSentDatePlusOffset(1, ChronoUnit.DAYS) 150 | 151 | var processed = false 152 | 153 | val conInfo = ConnectionInformation(host, port, emailAddress, password) 154 | val msgList = mutableListOf() 155 | imap(conInfo) { 156 | folder("INBOX", FolderModes.ReadOnly) { 157 | preFetchBy(FetchProfileItem.MESSAGE) 158 | val results = search { 159 | +from(fromAddress) 160 | +to(emailAddress) 161 | -subject("Testing") 162 | 163 | +sentOnOrBefore(mailSentDatePlusOneDay) 164 | } 165 | msgList.addAll(results) 166 | 167 | processed = true 168 | } 169 | } 170 | 171 | msgList.isNotEmpty() shouldBe true 172 | 173 | val first = msgList[0] 174 | fromAddress shouldBe first.from 175 | testBodyText.trim() shouldBe first.bodyText.trim() 176 | first.headers.isEmpty() shouldBe false 177 | 178 | processed shouldBe true 179 | } 180 | 181 | @Test 182 | fun searchStuffWithSorting() { 183 | val emailAddress = "test@localhost.com" 184 | val fromAddress = "from@localhost.com" 185 | val password = "password" 186 | val subject = "Test Email" 187 | val testBodyText = "Body Text goes here!" 188 | 189 | greenMail.setUser(emailAddress, password) 190 | 191 | GreenMailUtil.sendTextEmailTest(emailAddress, fromAddress, subject, testBodyText) 192 | val mailSentDatePlusOneDay = greenMail.firstReceivedMailSentDatePlusOffset(1, ChronoUnit.DAYS) 193 | 194 | var processed = false 195 | 196 | val conInfo = ConnectionInformation(host, port, emailAddress, password) 197 | val msgList = mutableListOf() 198 | imap(conInfo) { 199 | folder("INBOX", FolderModes.ReadOnly) { 200 | preFetchBy(FetchProfileItem.MESSAGE) 201 | val results = search { 202 | +from(fromAddress) 203 | +to(emailAddress) 204 | -subject("Testing") 205 | 206 | +sentOnOrBefore(mailSentDatePlusOneDay) 207 | 208 | sortedBy { 209 | +From 210 | +To 211 | -Sort.Size 212 | -Subject 213 | } 214 | } 215 | msgList.addAll(results) 216 | 217 | 218 | processed = true 219 | } 220 | } 221 | 222 | msgList.isNotEmpty() shouldBe true 223 | 224 | val first = msgList[0] 225 | fromAddress shouldBe first.from 226 | testBodyText.trim() shouldBe first.bodyText.trim() 227 | first.headers.isEmpty() shouldBe false 228 | 229 | processed shouldBe true 230 | } 231 | 232 | @Test 233 | fun sortingNoSearch() { 234 | val emailAddress = "test@localhost.com" 235 | val fromAddress = "from@localhost.com" 236 | val password = "password" 237 | val subject = "Test Email" 238 | val testBodyText = "Body Text goes here!" 239 | 240 | greenMail.setUser(emailAddress, password) 241 | 242 | GreenMailUtil.sendTextEmailTest(emailAddress, fromAddress, subject, testBodyText) 243 | 244 | var processed = false 245 | 246 | val conInfo = ConnectionInformation(host, port, emailAddress, password) 247 | val msgList = mutableListOf() 248 | imap(conInfo) { 249 | folder("INBOX", FolderModes.ReadOnly) { 250 | preFetchBy(FetchProfileItem.MESSAGE) 251 | val results = sortedBy { 252 | +From 253 | +To 254 | -SortTerm.SIZE 255 | -Subject 256 | +SortTerm.ARRIVAL 257 | -Sort.fromSortTerm(SortTerm.CC)!! 258 | } 259 | 260 | msgList.addAll(results) 261 | 262 | processed = true 263 | } 264 | } 265 | 266 | msgList.isNotEmpty() shouldBe true 267 | 268 | val first = msgList[0] 269 | fromAddress shouldBe first.from 270 | testBodyText.trim() shouldBe first.bodyText.trim() 271 | first.headers.isEmpty() shouldBe false 272 | 273 | processed shouldBe true 274 | } 275 | 276 | @Test 277 | fun shouldMarkAsRead() { 278 | val emailAddress = "test@localhost.com" 279 | val fromAddress = "from@localhost.com" 280 | val password = "password" 281 | val subject = "Test Email" 282 | val testBodyText = "Body Text goes here!" 283 | 284 | greenMail.setUser(emailAddress, password) 285 | 286 | GreenMailUtil.sendTextEmailTest(emailAddress, fromAddress, subject, testBodyText) 287 | 288 | var processed = false 289 | 290 | imap(host, port, emailAddress, password) { 291 | folder("INBOX", FolderModes.ReadWrite) { 292 | preFetchBy(FetchProfileItem.MESSAGE) 293 | val results = search { 294 | +from(fromAddress) 295 | markAsRead = true 296 | } 297 | 298 | results.isEmpty() shouldBe false 299 | 300 | processed = true 301 | } 302 | } 303 | 304 | val firstMessageFlags = greenMail.receivedMessages.first().flags 305 | val firstMessageContainsSeenFlag = firstMessageFlags.contains(Flag.Seen.javaMailFlag) 306 | firstMessageContainsSeenFlag shouldBe true 307 | processed shouldBe true 308 | } 309 | 310 | @Test 311 | fun shouldNotMarkAsRead() { 312 | val emailAddress = "test@localhost.com" 313 | val fromAddress = "from@localhost.com" 314 | val password = "password" 315 | val subject = "Test Email" 316 | val testBodyText = "Body Text goes here!" 317 | 318 | greenMail.setUser(emailAddress, password) 319 | 320 | GreenMailUtil.sendTextEmailTest(emailAddress, fromAddress, subject, testBodyText) 321 | 322 | var processed = false 323 | 324 | imap(host, port, emailAddress, password) { 325 | folder("INBOX", FolderModes.ReadOnly) { 326 | preFetchBy(FetchProfileItem.MESSAGE) 327 | val results = search { 328 | +from(fromAddress) 329 | markAsRead = false 330 | } 331 | 332 | results.isEmpty() shouldBe false 333 | 334 | processed = true 335 | } 336 | } 337 | 338 | val firstMessageFlags = greenMail.receivedMessages.first().flags 339 | val firstMessageContainsSeenFlag = firstMessageFlags.contains(Flag.Seen.javaMailFlag) 340 | firstMessageContainsSeenFlag shouldBe false 341 | processed shouldBe true 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /src/test/kotlin/io/github/slothLabs/mail/imap/SearchBuilderTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.slothLabs.mail.imap 2 | 3 | import com.sun.mail.imap.ModifiedSinceTerm 4 | import com.sun.mail.imap.OlderTerm 5 | import com.sun.mail.imap.YoungerTerm 6 | import io.kotest.core.spec.style.AnnotationSpec 7 | import io.kotest.core.test.TestCase 8 | import io.kotest.matchers.nulls.shouldBeNull 9 | import io.kotest.matchers.nulls.shouldNotBeNull 10 | import io.kotest.matchers.shouldBe 11 | import org.junit.jupiter.api.Assertions.assertNotSame 12 | import java.util.Date 13 | import javax.mail.internet.InternetAddress 14 | import javax.mail.internet.MimeMessage 15 | import javax.mail.search.AndTerm 16 | import javax.mail.search.BodyTerm 17 | import javax.mail.search.ComparisonTerm 18 | import javax.mail.search.FlagTerm 19 | import javax.mail.search.FromTerm 20 | import javax.mail.search.HeaderTerm 21 | import javax.mail.search.MessageIDTerm 22 | import javax.mail.search.MessageNumberTerm 23 | import javax.mail.search.NotTerm 24 | import javax.mail.search.OrTerm 25 | import javax.mail.search.ReceivedDateTerm 26 | import javax.mail.search.RecipientStringTerm 27 | import javax.mail.search.RecipientTerm 28 | import javax.mail.search.SentDateTerm 29 | import javax.mail.search.SizeTerm 30 | import javax.mail.search.SubjectTerm 31 | 32 | class SearchBuilderTest : AnnotationSpec() { 33 | 34 | private var sb = SearchBuilder() 35 | 36 | override fun beforeTest(testCase: TestCase) { 37 | super.beforeTest(testCase) 38 | sb = SearchBuilder() 39 | } 40 | 41 | @Test 42 | fun searchBuilderBuildWithoutTermsShouldReturnNull() { 43 | val res = sb.build() 44 | res.shouldBeNull() 45 | } 46 | 47 | @Test 48 | fun searchBuilderBuildWithTermsShouldReturnNotNull() { 49 | sb.withFrom("test@drive.com") 50 | val res = sb.build() 51 | res.shouldNotBeNull() 52 | } 53 | 54 | @Test 55 | fun fromShouldConstructAppropriateInternetAddressInstance() { 56 | val address = InternetAddress("test@drive.com") 57 | val fromInternetAddress = sb.from(address) 58 | 59 | fromInternetAddress.address shouldBe address 60 | } 61 | 62 | @Test 63 | fun fromShouldConstructAppropriateStringInstance() { 64 | val str = "another@example.com" 65 | val fromString = sb.from(str) 66 | 67 | fromString.pattern shouldBe str 68 | } 69 | 70 | @Test 71 | fun withFromShouldProperlyFillWithInternetAddressBasedTerm() { 72 | val address = InternetAddress("test@drive.com") 73 | sb.withFrom(address) 74 | 75 | val terms = sb.build() 76 | terms.shouldNotBeNull() 77 | 78 | val fromTerm = terms as FromTerm 79 | fromTerm.address shouldBe address 80 | } 81 | 82 | @Test 83 | fun recipientShouldConstructAppropriateInternetAddressInstance() { 84 | val recipientType = MimeMessage.RecipientType.TO 85 | 86 | val address = InternetAddress("test@drive.com") 87 | val recipientInternetAddress = sb.recipient(recipientType, address) 88 | 89 | recipientInternetAddress.recipientType shouldBe recipientType 90 | recipientInternetAddress.address shouldBe address 91 | } 92 | 93 | @Test 94 | fun recipientShouldConstructAppropriateStringBasedTerm() { 95 | val recipientType = MimeMessage.RecipientType.TO 96 | 97 | val str = "another@example.com" 98 | val recipientString = sb.recipient(recipientType, str) 99 | 100 | recipientString.recipientType shouldBe recipientType 101 | recipientString.pattern shouldBe str 102 | } 103 | 104 | @Test 105 | fun withRecipientMethodsShouldProperlyFillSearchBuilderTerms() { 106 | val sb1 = SearchBuilder() 107 | val recipientType = MimeMessage.RecipientType.TO 108 | 109 | val address = InternetAddress("test@drive.com") 110 | sb1.withRecipient(recipientType, address) 111 | val terms1 = sb1.build() 112 | terms1.shouldNotBeNull() 113 | 114 | val recipientInternetAddress = terms1 as RecipientTerm 115 | recipientInternetAddress.recipientType shouldBe recipientType 116 | recipientInternetAddress.address shouldBe address 117 | 118 | val sb2 = SearchBuilder() 119 | val str = "another@example.com" 120 | sb2.withRecipient(recipientType, str) 121 | val terms2 = sb2.build() 122 | terms2.shouldNotBeNull() 123 | 124 | val recipientString = terms2 as RecipientStringTerm 125 | recipientString.recipientType shouldBe recipientType 126 | recipientString.pattern shouldBe str 127 | } 128 | 129 | @Test 130 | fun toMethodsShouldConstructAppropriateSearchTerms() { 131 | val sb = SearchBuilder() 132 | val expRecipientType = MimeMessage.RecipientType.TO 133 | 134 | val address = InternetAddress("test@drive.com") 135 | val recipientInternetAddress = sb.to(address) 136 | 137 | recipientInternetAddress.recipientType shouldBe expRecipientType 138 | recipientInternetAddress.address shouldBe address 139 | 140 | val str = "another@example.com" 141 | val recipientString = sb.to(str) 142 | 143 | recipientString.recipientType shouldBe expRecipientType 144 | recipientString.pattern shouldBe str 145 | } 146 | 147 | @Test 148 | fun withToMethodsShouldProperlyFillSearchBuilderTerms() { 149 | val sb1 = SearchBuilder() 150 | val expRecipientType = MimeMessage.RecipientType.TO 151 | 152 | val address = InternetAddress("test@drive.com") 153 | sb1.withTo(address) 154 | val terms1 = sb1.build() 155 | terms1.shouldNotBeNull() 156 | 157 | val recipientInternetAddress = terms1 as RecipientTerm 158 | recipientInternetAddress.recipientType shouldBe expRecipientType 159 | recipientInternetAddress.address shouldBe address 160 | 161 | val sb2 = SearchBuilder() 162 | val str = "another@example.com" 163 | sb2.withTo(str) 164 | val terms2 = sb2.build() 165 | terms2.shouldNotBeNull() 166 | 167 | val recipientString = terms2 as RecipientStringTerm 168 | recipientString.recipientType shouldBe expRecipientType 169 | recipientString.pattern shouldBe str 170 | } 171 | 172 | @Test 173 | fun ccMethodsShouldConstructAppropriateSearchTerms() { 174 | val sb = SearchBuilder() 175 | val expRecipientType = MimeMessage.RecipientType.CC 176 | 177 | val address = InternetAddress("test@drive.com") 178 | val recipientInternetAddress = sb.cc(address) 179 | 180 | recipientInternetAddress.recipientType shouldBe expRecipientType 181 | recipientInternetAddress.address shouldBe address 182 | 183 | val str = "another@example.com" 184 | val recipientString = sb.cc(str) 185 | 186 | recipientString.recipientType shouldBe expRecipientType 187 | recipientString.pattern shouldBe str 188 | } 189 | 190 | @Test 191 | fun withCCMethodsShouldProperlyFillSearchBuilderTerms() { 192 | val sb1 = SearchBuilder() 193 | val expRecipientType = MimeMessage.RecipientType.CC 194 | 195 | val address = InternetAddress("test@drive.com") 196 | sb1.withCC(address) 197 | val terms1 = sb1.build() 198 | terms1.shouldNotBeNull() 199 | 200 | val recipientInternetAddress = terms1 as RecipientTerm 201 | recipientInternetAddress.recipientType shouldBe expRecipientType 202 | recipientInternetAddress.address shouldBe address 203 | 204 | val sb2 = SearchBuilder() 205 | val str = "another@example.com" 206 | sb2.withCC(str) 207 | val terms2 = sb2.build() 208 | terms2.shouldNotBeNull() 209 | 210 | val recipientString = terms2 as RecipientStringTerm 211 | recipientString.recipientType shouldBe expRecipientType 212 | recipientString.pattern shouldBe str 213 | } 214 | 215 | @Test 216 | fun bccMethodsShouldConstructAppropriateSearchTerms() { 217 | val sb = SearchBuilder() 218 | val expRecipientType = MimeMessage.RecipientType.BCC 219 | 220 | val address = InternetAddress("test@drive.com") 221 | val recipientInternetAddress = sb.bcc(address) 222 | 223 | recipientInternetAddress.recipientType shouldBe expRecipientType 224 | recipientInternetAddress.address shouldBe address 225 | 226 | val str = "another@example.com" 227 | val recipientString = sb.bcc(str) 228 | 229 | recipientString.recipientType shouldBe expRecipientType 230 | recipientString.pattern shouldBe str 231 | } 232 | 233 | @Test 234 | fun withBCCMethodsShouldProperlyFillSearchBuilderTerms() { 235 | val sb1 = SearchBuilder() 236 | val expRecipientType = MimeMessage.RecipientType.BCC 237 | 238 | val address = InternetAddress("test@drive.com") 239 | sb1.withBCC(address) 240 | val terms1 = sb1.build() 241 | terms1.shouldNotBeNull() 242 | 243 | val recipientInternetAddress = terms1 as RecipientTerm 244 | recipientInternetAddress.recipientType shouldBe expRecipientType 245 | recipientInternetAddress.address shouldBe address 246 | 247 | val sb2 = SearchBuilder() 248 | val str = "another@example.com" 249 | sb2.withBCC(str) 250 | val terms2 = sb2.build() 251 | terms1.shouldNotBeNull() 252 | 253 | val recipientString = terms2 as RecipientStringTerm 254 | recipientString.recipientType shouldBe expRecipientType 255 | recipientString.pattern shouldBe str 256 | } 257 | 258 | @Test 259 | fun subjectMethodShouldConstructAppropriateSearchTerm() { 260 | val sb = SearchBuilder() 261 | 262 | val subject = "Test Subject" 263 | val subjectTerm = sb.subject(subject) 264 | 265 | subjectTerm.pattern shouldBe subject 266 | } 267 | 268 | @Test 269 | fun withSubjectMethodShouldProperlyFillSearchBuilderTerms() { 270 | val sb = SearchBuilder() 271 | 272 | val subject = "Test Subject" 273 | sb.withSubject(subject) 274 | 275 | val terms = sb.build() 276 | terms.shouldNotBeNull() 277 | 278 | val subjectTerm = terms as SubjectTerm 279 | subjectTerm.pattern shouldBe subject 280 | } 281 | 282 | @Test 283 | fun bodyMethodShouldConstructAppropriateSearchTerm() { 284 | val sb = SearchBuilder() 285 | 286 | val body = "Test Body" 287 | val bodyTerm = sb.body(body) 288 | 289 | bodyTerm.pattern shouldBe body 290 | } 291 | 292 | @Test 293 | fun withBodyMethodShouldProperlyFillSearchBuilderTerms() { 294 | val sb = SearchBuilder() 295 | 296 | val body = "Test Body" 297 | sb.withBody(body) 298 | 299 | val terms = sb.build() 300 | terms.shouldNotBeNull() 301 | 302 | val bodyTerm = terms as BodyTerm 303 | bodyTerm.pattern shouldBe body 304 | } 305 | 306 | @Test 307 | fun headerMethodShouldConstructAppropriateSearchTerm() { 308 | val sb = SearchBuilder() 309 | 310 | val headerName = "TheHeader" 311 | val headerValue = "Header Value" 312 | val headerTerm = sb.header(headerName, headerValue) 313 | 314 | headerTerm.headerName shouldBe headerName 315 | headerTerm.pattern shouldBe headerValue 316 | } 317 | 318 | @Test 319 | fun withHeaderMethodShouldProperlyFillSearchBuilderTerms() { 320 | val sb = SearchBuilder() 321 | 322 | val headerName = "TheHeader" 323 | val headerValue = "Header Value" 324 | sb.withHeader(headerName, headerValue) 325 | 326 | val terms = sb.build() 327 | terms.shouldNotBeNull() 328 | 329 | val headerTerm = terms as HeaderTerm 330 | headerTerm.headerName shouldBe headerName 331 | headerTerm.pattern shouldBe headerValue 332 | } 333 | 334 | @Test 335 | fun orMethodShouldConstructAppropriateSearchTerm() { 336 | val sb = SearchBuilder() 337 | 338 | val firstFrom = sb.from("test@drive.com") 339 | val secondFrom = sb.from("another@example.com") 340 | val orTerm = sb.or(firstFrom, secondFrom) as OrTerm 341 | 342 | orTerm.terms[0] shouldBe firstFrom 343 | orTerm.terms[1] shouldBe secondFrom 344 | } 345 | 346 | @Test 347 | fun withOrMethodShouldProperlyFillSearchBuilderTerms() { 348 | val sb = SearchBuilder() 349 | 350 | val firstFrom = sb.from("test@drive.com") 351 | val secondFrom = sb.from("another@example.com") 352 | sb.withOr(firstFrom, secondFrom) 353 | 354 | val terms = sb.build() 355 | terms.shouldNotBeNull() 356 | 357 | val orTerm = terms as OrTerm 358 | 359 | orTerm.terms[0] shouldBe firstFrom 360 | orTerm.terms[1] shouldBe secondFrom 361 | } 362 | 363 | @Test 364 | fun andMethodShouldConstructAppropriateSearchTerm() { 365 | val sb = SearchBuilder() 366 | 367 | val firstFrom = sb.from("test@drive.com") 368 | val secondFrom = sb.from("another@example.com") 369 | val andTerm = sb.and(firstFrom, secondFrom) 370 | 371 | andTerm.terms[0] shouldBe firstFrom 372 | andTerm.terms[1] shouldBe secondFrom 373 | 374 | } 375 | 376 | @Test 377 | fun withAndMethodShouldProperlyFillSearchBuilderTerms() { 378 | val sb = SearchBuilder() 379 | 380 | val firstFrom = sb.from("test@drive.com") 381 | val secondFrom = sb.from("another@example.com") 382 | sb.withAnd(firstFrom, secondFrom) 383 | 384 | val terms = sb.build() 385 | terms.shouldNotBeNull() 386 | 387 | val andTerm = terms as AndTerm 388 | 389 | 390 | andTerm.terms[0] shouldBe firstFrom 391 | andTerm.terms[1] shouldBe secondFrom 392 | } 393 | 394 | @Test 395 | fun receivedMethodShouldConstructAppropriateSearchTerm() { 396 | val sb = SearchBuilder() 397 | 398 | val compTerm = ComparisonTerm.GE 399 | val date = Date() 400 | val receivedTerm = sb.received(compTerm, date) 401 | 402 | receivedTerm.comparison shouldBe compTerm 403 | receivedTerm.date shouldBe date 404 | 405 | } 406 | 407 | @Test 408 | fun withReceivedMethodShouldProperlyFillSearchBuilderTerms() { 409 | val sb = SearchBuilder() 410 | 411 | val compTerm = ComparisonTerm.GE 412 | val date = Date() 413 | sb.withReceived(compTerm, date) 414 | 415 | val terms = sb.build() 416 | terms.shouldNotBeNull() 417 | 418 | val receivedTerm = terms as ReceivedDateTerm 419 | 420 | receivedTerm.comparison shouldBe compTerm 421 | receivedTerm.date shouldBe date 422 | } 423 | 424 | @Test 425 | fun receivedOnMethodShouldConstructAppropriateSearchTerm() { 426 | val sb = SearchBuilder() 427 | 428 | val expCompTerm = ComparisonTerm.EQ 429 | val date = Date() 430 | val receivedTerm = sb.receivedOn(date) 431 | 432 | receivedTerm.comparison shouldBe expCompTerm 433 | receivedTerm.date shouldBe date 434 | } 435 | 436 | @Test 437 | fun withReceivedOnMethodShouldProperlyFillSearchBuilderTerms() { 438 | val sb = SearchBuilder() 439 | 440 | val expCompTerm = ComparisonTerm.EQ 441 | val date = Date() 442 | sb.withReceivedOn(date) 443 | 444 | val terms = sb.build() 445 | terms.shouldNotBeNull() 446 | 447 | val receivedTerm = terms as ReceivedDateTerm 448 | 449 | receivedTerm.comparison shouldBe expCompTerm 450 | receivedTerm.date shouldBe date 451 | } 452 | 453 | @Test 454 | fun receivedOnOrAfterMethodShouldConstructAppropriateSearchTerm() { 455 | val sb = SearchBuilder() 456 | 457 | val expCompTerm = ComparisonTerm.GE 458 | val date = Date() 459 | val receivedTerm = sb.receivedOnOrAfter(date) 460 | 461 | receivedTerm.comparison shouldBe expCompTerm 462 | receivedTerm.date shouldBe date 463 | } 464 | 465 | @Test 466 | fun withReceivedOnOrAfterMethodShouldProperlyFillSearchBuilderTerms() { 467 | val sb = SearchBuilder() 468 | 469 | val expCompTerm = ComparisonTerm.GE 470 | val date = Date() 471 | sb.withReceivedOnOrAfter(date) 472 | 473 | val terms = sb.build() 474 | terms.shouldNotBeNull() 475 | 476 | val receivedTerm = terms as ReceivedDateTerm 477 | 478 | receivedTerm.comparison shouldBe expCompTerm 479 | receivedTerm.date shouldBe date 480 | } 481 | 482 | @Test 483 | fun receivedAfterMethodShouldConstructAppropriateSearchTerm() { 484 | val sb = SearchBuilder() 485 | 486 | val expCompTerm = ComparisonTerm.GT 487 | val date = Date() 488 | val receivedTerm = sb.receivedAfter(date) 489 | 490 | receivedTerm.comparison shouldBe expCompTerm 491 | receivedTerm.date shouldBe date 492 | } 493 | 494 | @Test 495 | fun withReceivedAfterMethodShouldProperlyFillSearchBuilderTerms() { 496 | val sb = SearchBuilder() 497 | 498 | val expCompTerm = ComparisonTerm.GT 499 | val date = Date() 500 | sb.withReceivedAfter(date) 501 | 502 | val terms = sb.build() 503 | terms.shouldNotBeNull() 504 | 505 | val receivedTerm = terms as ReceivedDateTerm 506 | 507 | receivedTerm.comparison shouldBe expCompTerm 508 | receivedTerm.date shouldBe date 509 | } 510 | 511 | @Test 512 | fun receivedOnOrBeforeMethodShouldConstructAppropriateSearchTerm() { 513 | val sb = SearchBuilder() 514 | 515 | val expCompTerm = ComparisonTerm.LE 516 | val date = Date() 517 | val receivedTerm = sb.receivedOnOrBefore(date) 518 | 519 | receivedTerm.comparison shouldBe expCompTerm 520 | receivedTerm.date shouldBe date 521 | } 522 | 523 | @Test 524 | fun withReceivedOnOrBeforeMethodShouldProperlyFillSearchBuilderTerms() { 525 | val sb = SearchBuilder() 526 | 527 | val expCompTerm = ComparisonTerm.LE 528 | val date = Date() 529 | sb.withReceivedOnOrBefore(date) 530 | 531 | val terms = sb.build() 532 | terms.shouldNotBeNull() 533 | 534 | val receivedTerm = terms as ReceivedDateTerm 535 | 536 | receivedTerm.comparison shouldBe expCompTerm 537 | receivedTerm.date shouldBe date 538 | } 539 | 540 | @Test 541 | fun receivedBeforeMethodShouldConstructAppropriateSearchTerm() { 542 | val sb = SearchBuilder() 543 | 544 | val expCompTerm = ComparisonTerm.LT 545 | val date = Date() 546 | val receivedTerm = sb.receivedBefore(date) 547 | 548 | receivedTerm.comparison shouldBe expCompTerm 549 | receivedTerm.date shouldBe date 550 | } 551 | 552 | @Test 553 | fun withReceivedBeforeMethodShouldProperlyFillSearchBuilderTerms() { 554 | val sb = SearchBuilder() 555 | 556 | val expCompTerm = ComparisonTerm.LT 557 | val date = Date() 558 | sb.withReceivedBefore(date) 559 | 560 | val terms = sb.build() 561 | terms.shouldNotBeNull() 562 | 563 | val receivedTerm = terms as ReceivedDateTerm 564 | 565 | receivedTerm.comparison shouldBe expCompTerm 566 | receivedTerm.date shouldBe date 567 | } 568 | 569 | @Test 570 | fun notReceivedOnMethodShouldConstructAppropriateSearchTerm() { 571 | val sb = SearchBuilder() 572 | 573 | val expCompTerm = ComparisonTerm.NE 574 | val date = Date() 575 | val receivedTerm = sb.notReceivedOn(date) 576 | 577 | receivedTerm.comparison shouldBe expCompTerm 578 | receivedTerm.date shouldBe date 579 | } 580 | 581 | @Test 582 | fun withNotReceivedOnMethodShouldProperlyFillSearchBuilderTerms() { 583 | val sb = SearchBuilder() 584 | 585 | val expCompTerm = ComparisonTerm.NE 586 | val date = Date() 587 | sb.withNotReceivedOn(date) 588 | 589 | val terms = sb.build() 590 | terms.shouldNotBeNull() 591 | 592 | val receivedTerm = terms as ReceivedDateTerm 593 | 594 | receivedTerm.comparison shouldBe expCompTerm 595 | receivedTerm.date shouldBe date 596 | } 597 | 598 | @Test 599 | fun receivedBetweenMethodWithTwoParamsShouldConstructAppropriateSearchTerm() { 600 | val sb = SearchBuilder() 601 | 602 | val startDate = Date() 603 | val endDate = Date() 604 | assertNotSame(startDate, endDate) 605 | val andTerm = sb.receivedBetween(startDate, endDate) as AndTerm 606 | 607 | val startTerm = andTerm.terms[0] as ReceivedDateTerm 608 | startTerm.comparison shouldBe ComparisonTerm.GE 609 | startTerm.date shouldBe startDate 610 | 611 | val endTerm = andTerm.terms[1] as ReceivedDateTerm 612 | endTerm.comparison shouldBe ComparisonTerm.LE 613 | endTerm.date shouldBe endDate 614 | } 615 | 616 | @Test 617 | fun withReceivedBetweenMethodWithTwoParamsShouldProperlyFillSearchBuilderTerms() { 618 | val sb = SearchBuilder() 619 | 620 | val startDate = Date() 621 | val endDate = Date() 622 | assertNotSame(startDate, endDate) 623 | sb.withReceivedBetween(startDate, endDate) 624 | 625 | val terms = sb.build() 626 | terms.shouldNotBeNull() 627 | 628 | val andTerm = terms as AndTerm 629 | 630 | val startTerm = andTerm.terms[0] as ReceivedDateTerm 631 | startTerm.comparison shouldBe ComparisonTerm.GE 632 | startTerm.date shouldBe startDate 633 | 634 | val endTerm = andTerm.terms[1] as ReceivedDateTerm 635 | endTerm.comparison shouldBe ComparisonTerm.LE 636 | endTerm.date shouldBe endDate 637 | } 638 | 639 | @Test 640 | fun receivedBetweenMethodWithRangeShouldConstructAppropriateSearchTerm() { 641 | val sb = SearchBuilder() 642 | 643 | val startDate = Date() 644 | val endDate = Date() 645 | assertNotSame(startDate, endDate) 646 | val andTerm = sb.receivedBetween(startDate..endDate) as AndTerm 647 | 648 | val startTerm = andTerm.terms[0] as ReceivedDateTerm 649 | startTerm.comparison shouldBe ComparisonTerm.GE 650 | startTerm.date shouldBe startDate 651 | 652 | val endTerm = andTerm.terms[1] as ReceivedDateTerm 653 | endTerm.comparison shouldBe ComparisonTerm.LE 654 | endTerm.date shouldBe endDate 655 | } 656 | 657 | @Test 658 | fun withReceivedBetweenMethodWithRangeShouldProperlyFillSearchBuilderTerms() { 659 | val sb = SearchBuilder() 660 | 661 | val startDate = Date() 662 | val endDate = Date() 663 | assertNotSame(startDate, endDate) 664 | sb.withReceivedBetween(startDate..endDate) 665 | 666 | val terms = sb.build() 667 | terms.shouldNotBeNull() 668 | 669 | val andTerm = terms as AndTerm 670 | 671 | val startTerm = andTerm.terms[0] as ReceivedDateTerm 672 | startTerm.comparison shouldBe ComparisonTerm.GE 673 | startTerm.date shouldBe startDate 674 | 675 | val endTerm = andTerm.terms[1] as ReceivedDateTerm 676 | endTerm.comparison shouldBe ComparisonTerm.LE 677 | endTerm.date shouldBe endDate 678 | } 679 | 680 | @Test 681 | fun sentMethodShouldConstructAppropriateSearchTerm() { 682 | val sb = SearchBuilder() 683 | 684 | val compTerm = ComparisonTerm.GE 685 | val date = Date() 686 | val sentTerm = sb.sent(compTerm, date) 687 | 688 | sentTerm.comparison shouldBe compTerm 689 | sentTerm.date shouldBe date 690 | } 691 | 692 | @Test 693 | fun withSentMethodShouldProperlyFillSearchBuilderTerms() { 694 | val sb = SearchBuilder() 695 | 696 | val compTerm = ComparisonTerm.GE 697 | val date = Date() 698 | sb.withSent(compTerm, date) 699 | 700 | val terms = sb.build() 701 | terms.shouldNotBeNull() 702 | 703 | val sentTerm = terms as SentDateTerm 704 | 705 | sentTerm.comparison shouldBe compTerm 706 | sentTerm.date shouldBe date 707 | } 708 | 709 | @Test 710 | fun sentOnMethodShouldConstructAppropriateSearchTerm() { 711 | val sb = SearchBuilder() 712 | 713 | val expCompTerm = ComparisonTerm.EQ 714 | val date = Date() 715 | val sentTerm = sb.sentOn(date) 716 | 717 | sentTerm.comparison shouldBe expCompTerm 718 | sentTerm.date shouldBe date 719 | } 720 | 721 | @Test 722 | fun withSentOnMethodShouldProperlyFillSearchBuilderTerms() { 723 | val sb = SearchBuilder() 724 | 725 | val expCompTerm = ComparisonTerm.EQ 726 | val date = Date() 727 | sb.withSentOn(date) 728 | 729 | val terms = sb.build() 730 | terms.shouldNotBeNull() 731 | 732 | val sentTerm = terms as SentDateTerm 733 | 734 | sentTerm.comparison shouldBe expCompTerm 735 | sentTerm.date shouldBe date 736 | } 737 | 738 | @Test 739 | fun sentOnOrAfterMethodShouldConstructAppropriateSearchTerm() { 740 | val sb = SearchBuilder() 741 | 742 | val expCompTerm = ComparisonTerm.GE 743 | val date = Date() 744 | val sentTerm = sb.sentOnOrAfter(date) 745 | 746 | sentTerm.comparison shouldBe expCompTerm 747 | sentTerm.date shouldBe date 748 | } 749 | 750 | @Test 751 | fun withSentOnOrAfterMethodShouldProperlyFillSearchBuilderTerms() { 752 | val sb = SearchBuilder() 753 | 754 | val expCompTerm = ComparisonTerm.GE 755 | val date = Date() 756 | sb.withSentOnOrAfter(date) 757 | 758 | val terms = sb.build() 759 | terms.shouldNotBeNull() 760 | 761 | val sentTerm = terms as SentDateTerm 762 | 763 | sentTerm.comparison shouldBe expCompTerm 764 | sentTerm.date shouldBe date 765 | } 766 | 767 | @Test 768 | fun sentAfterMethodShouldConstructAppropriateSearchTerm() { 769 | val sb = SearchBuilder() 770 | 771 | val expCompTerm = ComparisonTerm.GT 772 | val date = Date() 773 | val sentTerm = sb.sentAfter(date) 774 | 775 | sentTerm.comparison shouldBe expCompTerm 776 | sentTerm.date shouldBe date 777 | } 778 | 779 | @Test 780 | fun withSentAfterMethodShouldProperlyFillSearchBuilderTerms() { 781 | val sb = SearchBuilder() 782 | 783 | val expCompTerm = ComparisonTerm.GT 784 | val date = Date() 785 | sb.withSentAfter(date) 786 | 787 | val terms = sb.build() 788 | terms.shouldNotBeNull() 789 | 790 | val sentTerm = terms as SentDateTerm 791 | 792 | sentTerm.comparison shouldBe expCompTerm 793 | sentTerm.date shouldBe date 794 | } 795 | 796 | @Test 797 | fun sentOnOrBeforeMethodShouldConstructAppropriateSearchTerm() { 798 | val sb = SearchBuilder() 799 | 800 | val expCompTerm = ComparisonTerm.LE 801 | val date = Date() 802 | val sentTerm = sb.sentOnOrBefore(date) 803 | 804 | sentTerm.comparison shouldBe expCompTerm 805 | sentTerm.date shouldBe date 806 | } 807 | 808 | @Test 809 | fun withSentOnOrBeforeMethodShouldProperlyFillSearchBuilderTerms() { 810 | val sb = SearchBuilder() 811 | 812 | val expCompTerm = ComparisonTerm.LE 813 | val date = Date() 814 | sb.withSentOnOrBefore(date) 815 | 816 | val terms = sb.build() 817 | terms.shouldNotBeNull() 818 | 819 | val sentTerm = terms as SentDateTerm 820 | 821 | sentTerm.comparison shouldBe expCompTerm 822 | sentTerm.date shouldBe date 823 | } 824 | 825 | @Test 826 | fun sentBeforeMethodShouldConstructAppropriateSearchTerm() { 827 | val sb = SearchBuilder() 828 | 829 | val expCompTerm = ComparisonTerm.LT 830 | val date = Date() 831 | val sentTerm = sb.sentBefore(date) 832 | 833 | sentTerm.comparison shouldBe expCompTerm 834 | sentTerm.date shouldBe date 835 | } 836 | 837 | @Test 838 | fun withSentBeforeMethodShouldProperlyFillSearchBuilderTerms() { 839 | val sb = SearchBuilder() 840 | 841 | val expCompTerm = ComparisonTerm.LT 842 | val date = Date() 843 | sb.withSentBefore(date) 844 | 845 | val terms = sb.build() 846 | terms.shouldNotBeNull() 847 | 848 | val sentTerm = terms as SentDateTerm 849 | 850 | sentTerm.comparison shouldBe expCompTerm 851 | sentTerm.date shouldBe date 852 | } 853 | 854 | @Test 855 | fun notSentOnMethodShouldConstructAppropriateSearchTerm() { 856 | val sb = SearchBuilder() 857 | 858 | val expCompTerm = ComparisonTerm.NE 859 | val date = Date() 860 | val sentTerm = sb.notSentOn(date) 861 | 862 | sentTerm.comparison shouldBe expCompTerm 863 | sentTerm.date shouldBe date 864 | } 865 | 866 | @Test 867 | fun withNotSentOnMethodShouldProperlyFillSearchBuilderTerms() { 868 | val sb = SearchBuilder() 869 | 870 | val expCompTerm = ComparisonTerm.NE 871 | val date = Date() 872 | sb.withNotSentOn(date) 873 | 874 | val terms = sb.build() 875 | terms.shouldNotBeNull() 876 | 877 | val sentTerm = terms as SentDateTerm 878 | 879 | sentTerm.comparison shouldBe expCompTerm 880 | sentTerm.date shouldBe date 881 | } 882 | 883 | @Test 884 | fun sentBetweenMethodWithTwoParamsShouldConstructAppropriateSearchTerm() { 885 | val sb = SearchBuilder() 886 | 887 | val startDate = Date() 888 | val endDate = Date() 889 | assertNotSame(startDate, endDate) 890 | val andTerm = sb.sentBetween(startDate, endDate) as AndTerm 891 | 892 | val startTerm = andTerm.terms[0] as SentDateTerm 893 | startTerm.comparison shouldBe ComparisonTerm.GE 894 | startTerm.date shouldBe startDate 895 | 896 | val endTerm = andTerm.terms[1] as SentDateTerm 897 | endTerm.comparison shouldBe ComparisonTerm.LE 898 | endTerm.date shouldBe endDate 899 | } 900 | 901 | @Test 902 | fun withSentBetweenMethodWithTwoParamsShouldProperlyFillSearchBuilderTerms() { 903 | val sb = SearchBuilder() 904 | 905 | val startDate = Date() 906 | val endDate = Date() 907 | assertNotSame(startDate, endDate) 908 | sb.withSentBetween(startDate, endDate) 909 | 910 | val terms = sb.build() 911 | terms.shouldNotBeNull() 912 | 913 | val andTerm = terms as AndTerm 914 | 915 | val startTerm = andTerm.terms[0] as SentDateTerm 916 | startTerm.comparison shouldBe ComparisonTerm.GE 917 | startTerm.date shouldBe startDate 918 | 919 | val endTerm = andTerm.terms[1] as SentDateTerm 920 | endTerm.comparison shouldBe ComparisonTerm.LE 921 | endTerm.date shouldBe endDate 922 | } 923 | 924 | @Test 925 | fun sentBetweenMethodWithRangeShouldConstructAppropriateSearchTerm() { 926 | val sb = SearchBuilder() 927 | 928 | val startDate = Date() 929 | val endDate = Date() 930 | assertNotSame(startDate, endDate) 931 | val andTerm = sb.sentBetween(startDate..endDate) as AndTerm 932 | 933 | val startTerm = andTerm.terms[0] as SentDateTerm 934 | startTerm.comparison shouldBe ComparisonTerm.GE 935 | startTerm.date shouldBe startDate 936 | 937 | val endTerm = andTerm.terms[1] as SentDateTerm 938 | endTerm.comparison shouldBe ComparisonTerm.LE 939 | endTerm.date shouldBe endDate 940 | } 941 | 942 | @Test 943 | fun withSentBetweenMethodWithRangeShouldProperlyFillSearchBuilderTerms() { 944 | val sb = SearchBuilder() 945 | 946 | val startDate = Date() 947 | val endDate = Date() 948 | assertNotSame(startDate, endDate) 949 | sb.withSentBetween(startDate..endDate) 950 | 951 | val terms = sb.build() 952 | terms.shouldNotBeNull() 953 | 954 | val andTerm = terms as AndTerm 955 | 956 | val startTerm = andTerm.terms[0] as SentDateTerm 957 | startTerm.comparison shouldBe ComparisonTerm.GE 958 | startTerm.date shouldBe startDate 959 | 960 | val endTerm = andTerm.terms[1] as SentDateTerm 961 | endTerm.comparison shouldBe ComparisonTerm.LE 962 | endTerm.date shouldBe endDate 963 | } 964 | 965 | @Test 966 | fun messageIdMethodShouldConstructAppropriateSearchTerm() { 967 | val sb = SearchBuilder() 968 | 969 | val id = "1234" 970 | val messageIdTerm = sb.messageId(id) 971 | 972 | messageIdTerm.pattern shouldBe id 973 | } 974 | 975 | @Test 976 | fun withMessageIdMethodShouldProperlyFillSearchBuilderTerm() { 977 | val sb = SearchBuilder() 978 | 979 | val id = "1234" 980 | sb.withMessageId(id) 981 | val terms = sb.build() 982 | terms.shouldNotBeNull() 983 | 984 | val messageIdTerm = terms as MessageIDTerm 985 | 986 | messageIdTerm.pattern shouldBe id 987 | } 988 | 989 | @Test 990 | fun messageNumberMethodShouldConstructAppropriateSearchTerm() { 991 | val sb = SearchBuilder() 992 | 993 | val number = 1234 994 | val messageNumberTerm = sb.messageNumber(number) 995 | 996 | messageNumberTerm.number shouldBe number 997 | } 998 | 999 | @Test 1000 | fun withMessageNumberMethodShouldProperlyFillSearchBuilderTerm() { 1001 | val sb = SearchBuilder() 1002 | 1003 | val number = 1234 1004 | sb.withMessageNumber(number) 1005 | val terms = sb.build() 1006 | terms.shouldNotBeNull() 1007 | 1008 | val messageNumberTerm = terms as MessageNumberTerm 1009 | messageNumberTerm.number shouldBe number 1010 | } 1011 | 1012 | @Test 1013 | fun sizeMethodShouldConstructAppropriateSearchTerm() { 1014 | val sb = SearchBuilder() 1015 | 1016 | val compTerm = ComparisonTerm.GE 1017 | val size = 1234 1018 | val sizeTerm = sb.size(compTerm, size) 1019 | 1020 | sizeTerm.comparison shouldBe compTerm 1021 | sizeTerm.number shouldBe size 1022 | } 1023 | 1024 | @Test 1025 | fun withSizeMethodShouldProperlyFillSearchBuilderTerms() { 1026 | val sb = SearchBuilder() 1027 | 1028 | val compTerm = ComparisonTerm.GE 1029 | val size = 1234 1030 | sb.withSize(compTerm, size) 1031 | 1032 | val terms = sb.build() 1033 | terms.shouldNotBeNull() 1034 | 1035 | val sizeTerm = terms as SizeTerm 1036 | 1037 | sizeTerm.comparison shouldBe compTerm 1038 | sizeTerm.number shouldBe size 1039 | } 1040 | 1041 | @Test 1042 | fun sizeIsMethodShouldConstructAppropriateSearchTerm() { 1043 | val sb = SearchBuilder() 1044 | 1045 | val expCompTerm = ComparisonTerm.EQ 1046 | val size = 1234 1047 | val sentTerm = sb.sizeIs(size) 1048 | 1049 | sentTerm.comparison shouldBe expCompTerm 1050 | sentTerm.number shouldBe size 1051 | } 1052 | 1053 | @Test 1054 | fun withSizeIsMethodShouldProperlyFillSearchBuilderTerms() { 1055 | val sb = SearchBuilder() 1056 | 1057 | val expCompTerm = ComparisonTerm.EQ 1058 | val size = 1234 1059 | sb.withSizeIs(size) 1060 | 1061 | val terms = sb.build() 1062 | terms.shouldNotBeNull() 1063 | 1064 | val sizeTerm = terms as SizeTerm 1065 | 1066 | sizeTerm.comparison shouldBe expCompTerm 1067 | sizeTerm.number shouldBe size 1068 | } 1069 | 1070 | @Test 1071 | fun sizeIsAtLeastMethodShouldConstructAppropriateSearchTerm() { 1072 | val sb = SearchBuilder() 1073 | 1074 | val expCompTerm = ComparisonTerm.GE 1075 | val size = 1234 1076 | val sizeTerm = sb.sizeIsAtLeast(size) 1077 | 1078 | sizeTerm.comparison shouldBe expCompTerm 1079 | sizeTerm.number shouldBe size 1080 | } 1081 | 1082 | @Test 1083 | fun withSizeIsAtLeastMethodShouldProperlyFillSearchBuilderTerms() { 1084 | val sb = SearchBuilder() 1085 | 1086 | val expCompTerm = ComparisonTerm.GE 1087 | val size = 1234 1088 | sb.withSizeIsAtLeast(size) 1089 | 1090 | val terms = sb.build() 1091 | terms.shouldNotBeNull() 1092 | 1093 | val sizeTerm = terms as SizeTerm 1094 | 1095 | sizeTerm.comparison shouldBe expCompTerm 1096 | sizeTerm.number shouldBe size 1097 | } 1098 | 1099 | @Test 1100 | fun sizeIsGreaterThanMethodShouldConstructAppropriateSearchTerm() { 1101 | val sb = SearchBuilder() 1102 | 1103 | val expCompTerm = ComparisonTerm.GT 1104 | val size = 1234 1105 | val sizeTerm = sb.sizeIsGreaterThan(size) 1106 | 1107 | sizeTerm.comparison shouldBe expCompTerm 1108 | sizeTerm.number shouldBe size 1109 | } 1110 | 1111 | @Test 1112 | fun withSizeIsGreaterThanMethodShouldProperlyFillSearchBuilderTerms() { 1113 | val sb = SearchBuilder() 1114 | 1115 | val expCompTerm = ComparisonTerm.GT 1116 | val size = 1234 1117 | sb.withSizeIsGreaterThan(size) 1118 | 1119 | val terms = sb.build() 1120 | terms.shouldNotBeNull() 1121 | 1122 | val sizeTerm = terms as SizeTerm 1123 | 1124 | sizeTerm.comparison shouldBe expCompTerm 1125 | sizeTerm.number shouldBe size 1126 | } 1127 | 1128 | @Test 1129 | fun sizeIsNoMoreThanMethodShouldConstructAppropriateSearchTerm() { 1130 | val sb = SearchBuilder() 1131 | 1132 | val expCompTerm = ComparisonTerm.LE 1133 | val size = 1234 1134 | val sizeTerm = sb.sizeIsNoMoreThan(size) 1135 | 1136 | sizeTerm.comparison shouldBe expCompTerm 1137 | sizeTerm.number shouldBe size 1138 | } 1139 | 1140 | @Test 1141 | fun withSizeIsNoMoreThanMethodShouldProperlyFillSearchBuilderTerms() { 1142 | val sb = SearchBuilder() 1143 | 1144 | val expCompTerm = ComparisonTerm.LE 1145 | val size = 1234 1146 | sb.withSizeIsNoMoreThan(size) 1147 | 1148 | val terms = sb.build() 1149 | terms.shouldNotBeNull() 1150 | 1151 | val sizeTerm = terms as SizeTerm 1152 | 1153 | sizeTerm.comparison shouldBe expCompTerm 1154 | sizeTerm.number shouldBe size 1155 | } 1156 | 1157 | @Test 1158 | fun sizeIsLessThanMethodShouldConstructAppropriateSearchTerm() { 1159 | val sb = SearchBuilder() 1160 | 1161 | val expCompTerm = ComparisonTerm.LT 1162 | val size = 1234 1163 | val sizeTerm = sb.sizeIsLessThan(size) 1164 | 1165 | sizeTerm.comparison shouldBe expCompTerm 1166 | sizeTerm.number shouldBe size 1167 | } 1168 | 1169 | @Test 1170 | fun withSizeIsLessThanMethodShouldProperlyFillSearchBuilderTerms() { 1171 | val sb = SearchBuilder() 1172 | 1173 | val expCompTerm = ComparisonTerm.LT 1174 | val size = 1234 1175 | sb.withSizeIsLessThan(size) 1176 | 1177 | val terms = sb.build() 1178 | terms.shouldNotBeNull() 1179 | 1180 | val sizeTerm = terms as SizeTerm 1181 | 1182 | sizeTerm.comparison shouldBe expCompTerm 1183 | sizeTerm.number shouldBe size 1184 | } 1185 | 1186 | @Test 1187 | fun sizeIsNotMethodShouldConstructAppropriateSearchTerm() { 1188 | val sb = SearchBuilder() 1189 | 1190 | val expCompTerm = ComparisonTerm.NE 1191 | val size = 1234 1192 | val sentTerm = sb.sizeIsNot(size) 1193 | 1194 | sentTerm.comparison shouldBe expCompTerm 1195 | sentTerm.number shouldBe size 1196 | } 1197 | 1198 | @Test 1199 | fun withSizeIsNotMethodShouldProperlyFillSearchBuilderTerms() { 1200 | val sb = SearchBuilder() 1201 | 1202 | val expCompTerm = ComparisonTerm.NE 1203 | val size = 1234 1204 | sb.withSizeIsNot(size) 1205 | 1206 | val terms = sb.build() 1207 | terms.shouldNotBeNull() 1208 | 1209 | val sizeTerm = terms as SizeTerm 1210 | 1211 | sizeTerm.comparison shouldBe expCompTerm 1212 | sizeTerm.number shouldBe size 1213 | } 1214 | 1215 | @Test 1216 | fun sizeBetweenMethodWithTwoParamsShouldConstructAppropriateSearchTerm() { 1217 | val sb = SearchBuilder() 1218 | 1219 | val startSize = 1234 1220 | val endSize = 4321 1221 | assertNotSame(startSize, endSize) 1222 | val andTerm = sb.sizeBetween(startSize, endSize) as AndTerm 1223 | 1224 | val startTerm = andTerm.terms[0] as SizeTerm 1225 | startTerm.comparison shouldBe ComparisonTerm.GE 1226 | startTerm.number shouldBe startSize 1227 | 1228 | val endTerm = andTerm.terms[1] as SizeTerm 1229 | endTerm.comparison shouldBe ComparisonTerm.LE 1230 | endTerm.number shouldBe endSize 1231 | } 1232 | 1233 | @Test 1234 | fun withSizeBetweenMethodWithTwoParamsShouldProperlyFillSearchBuilderTerms() { 1235 | val sb = SearchBuilder() 1236 | 1237 | val startSize = 1234 1238 | val endSize = 4321 1239 | assertNotSame(startSize, endSize) 1240 | sb.withSizeBetween(startSize, endSize) 1241 | 1242 | val terms = sb.build() 1243 | terms.shouldNotBeNull() 1244 | 1245 | val andTerm = terms as AndTerm 1246 | 1247 | val startTerm = andTerm.terms[0] as SizeTerm 1248 | startTerm.comparison shouldBe ComparisonTerm.GE 1249 | startTerm.number shouldBe startSize 1250 | 1251 | val endTerm = andTerm.terms[1] as SizeTerm 1252 | endTerm.comparison shouldBe ComparisonTerm.LE 1253 | endTerm.number shouldBe endSize 1254 | } 1255 | 1256 | @Test 1257 | fun sizeBetweenMethodWithRangeShouldConstructAppropriateSearchTerm() { 1258 | val sb = SearchBuilder() 1259 | 1260 | val startSize = 1234 1261 | val endSize = 4321 1262 | assertNotSame(startSize, endSize) 1263 | val andTerm = sb.sizeBetween(startSize..endSize) as AndTerm 1264 | 1265 | val startTerm = andTerm.terms[0] as SizeTerm 1266 | startTerm.comparison shouldBe ComparisonTerm.GE 1267 | startTerm.number shouldBe startSize 1268 | 1269 | val endTerm = andTerm.terms[1] as SizeTerm 1270 | endTerm.comparison shouldBe ComparisonTerm.LE 1271 | endTerm.number shouldBe endSize 1272 | } 1273 | 1274 | @Test 1275 | fun withSizeBetweenMethodWithRangeShouldProperlyFillSearchBuilderTerms() { 1276 | val sb = SearchBuilder() 1277 | 1278 | val startSize = 1234 1279 | val endSize = 4321 1280 | assertNotSame(startSize, endSize) 1281 | sb.withSizeBetween(startSize..endSize) 1282 | 1283 | val terms = sb.build() 1284 | terms.shouldNotBeNull() 1285 | 1286 | val andTerm = terms as AndTerm 1287 | 1288 | val startTerm = andTerm.terms[0] as SizeTerm 1289 | startTerm.comparison shouldBe ComparisonTerm.GE 1290 | startTerm.number shouldBe startSize 1291 | 1292 | val endTerm = andTerm.terms[1] as SizeTerm 1293 | endTerm.comparison shouldBe ComparisonTerm.LE 1294 | endTerm.number shouldBe endSize 1295 | } 1296 | 1297 | @Test 1298 | fun flagsMethodShouldConstructAppropriateSearchTerm() { 1299 | val sb = SearchBuilder() 1300 | val flags = Flags(Flag.Draft, Flag.Recent) 1301 | val set = false 1302 | val flagTerm = sb.flags(flags, set) 1303 | 1304 | flagTerm.flags shouldBe flags.javaMailFlags 1305 | flagTerm.testSet shouldBe set 1306 | } 1307 | 1308 | @Test 1309 | fun withFlagsMethodShouldProperlyFillSearchBuilderTerms() { 1310 | val sb = SearchBuilder() 1311 | 1312 | val flags = Flags(Flag.Draft, Flag.Recent) 1313 | val set = true 1314 | 1315 | sb.withFlags(flags, set) 1316 | 1317 | val terms = sb.build() 1318 | terms.shouldNotBeNull() 1319 | 1320 | val flagTerm = terms as FlagTerm 1321 | flagTerm.flags shouldBe flags.javaMailFlags 1322 | flagTerm.testSet shouldBe set 1323 | } 1324 | 1325 | @Test 1326 | fun modifiedSinceMethodShouldConstructAppropriateSearchTerm() { 1327 | val sb = SearchBuilder() 1328 | 1329 | val since = 1234L 1330 | val sinceTerm = sb.modifiedSince(since) 1331 | 1332 | sinceTerm.modSeq shouldBe since 1333 | } 1334 | 1335 | @Test 1336 | fun withModifiedSinceMethodShouldProperlyFillSearchBuilderTerms() { 1337 | val sb = SearchBuilder() 1338 | 1339 | val since = 1234L 1340 | sb.withModifiedSince(since) 1341 | 1342 | val terms = sb.build() 1343 | terms.shouldNotBeNull() 1344 | 1345 | val sinceTerm = terms as ModifiedSinceTerm 1346 | 1347 | sinceTerm.modSeq shouldBe since 1348 | } 1349 | 1350 | @Test 1351 | fun olderMethodShouldConstructAppropriateSearchTerm() { 1352 | val sb = SearchBuilder() 1353 | 1354 | val interval = 1234 1355 | val olderTerm = sb.older(interval) 1356 | 1357 | olderTerm.interval shouldBe interval 1358 | } 1359 | 1360 | @Test 1361 | fun withOlderMethodShouldProperlyFillSearchBuilderTerms() { 1362 | val sb = SearchBuilder() 1363 | 1364 | val interval = 1234 1365 | sb.withOlder(interval) 1366 | 1367 | val terms = sb.build() 1368 | terms.shouldNotBeNull() 1369 | 1370 | val olderTerm = terms as OlderTerm 1371 | olderTerm.interval shouldBe interval 1372 | } 1373 | 1374 | @Test 1375 | fun youngerMethodShouldConstructAppropriateSearchTerm() { 1376 | val sb = SearchBuilder() 1377 | 1378 | val interval = 1234 1379 | val youngerTerm = sb.younger(interval) 1380 | 1381 | youngerTerm.interval shouldBe interval 1382 | } 1383 | 1384 | @Test 1385 | fun withYoungerMethodShouldProperlyFillSearchBuilderTerms() { 1386 | val sb = SearchBuilder() 1387 | 1388 | val interval = 1234 1389 | sb.withYounger(interval) 1390 | 1391 | val terms = sb.build() 1392 | terms.shouldNotBeNull() 1393 | 1394 | val youngerTerm = terms as YoungerTerm 1395 | 1396 | youngerTerm.interval shouldBe interval 1397 | } 1398 | 1399 | @Test 1400 | fun withMethodShouldProperlyFillSearchBuilderTerms() { 1401 | val sb = SearchBuilder() 1402 | 1403 | val interval = 1234 1404 | val youngerTerm = YoungerTerm(interval) 1405 | sb.with(youngerTerm) 1406 | 1407 | val terms = sb.build() 1408 | terms.shouldNotBeNull() 1409 | 1410 | val youngerTermFromBuilder = terms as YoungerTerm 1411 | 1412 | youngerTermFromBuilder shouldBe youngerTerm 1413 | } 1414 | 1415 | @Test 1416 | fun unaryOperatorsShouldProperlyFillSearchBuilderTerms() { 1417 | val sb = SearchBuilder() 1418 | 1419 | val interval = 1234 1420 | val youngerTerm = YoungerTerm(interval) 1421 | val olderTerm = OlderTerm(interval) 1422 | with(sb) { 1423 | +youngerTerm 1424 | -olderTerm 1425 | } 1426 | 1427 | val terms = sb.build() 1428 | terms.shouldNotBeNull() 1429 | 1430 | val andTerm = terms as AndTerm 1431 | 1432 | val youngerTermFromBuilder = andTerm.terms[0] as YoungerTerm 1433 | youngerTermFromBuilder shouldBe youngerTerm 1434 | 1435 | val notOlderTermFromBuilder = andTerm.terms[1] as NotTerm 1436 | val olderTermFromBuilder = notOlderTermFromBuilder.term as OlderTerm 1437 | olderTermFromBuilder shouldBe olderTerm 1438 | } 1439 | 1440 | @Test 1441 | fun markAsReadShouldProperlyBeSet() { 1442 | val sb1 = SearchBuilder() 1443 | with(sb1) { 1444 | markAsRead = true 1445 | } 1446 | sb1.markAsRead shouldBe true 1447 | 1448 | val sb2 = SearchBuilder() 1449 | with(sb2) { 1450 | markAsRead = false 1451 | } 1452 | sb2.markAsRead shouldBe false 1453 | 1454 | val sb3 = SearchBuilder() 1455 | sb3.markAsRead shouldBe false 1456 | } 1457 | } 1458 | -------------------------------------------------------------------------------- /src/test/kotlin/io/github/slothLabs/mail/imap/Utils.kt: -------------------------------------------------------------------------------- 1 | package io.github.slothLabs.mail.imap 2 | 3 | import com.icegreen.greenmail.util.GreenMail 4 | import java.time.Instant 5 | import java.time.temporal.TemporalUnit 6 | import java.util.Date 7 | 8 | fun Instant.toDate(): Date = Date.from(this) 9 | 10 | fun GreenMail.firstReceivedMailSentDatePlusOffset(amount: Long, units: TemporalUnit): Date { 11 | val firstReceivedMailSentDate = receivedMessages.first().sentDate 12 | return firstReceivedMailSentDate.toInstant().plus(amount, units).toDate() 13 | } 14 | --------------------------------------------------------------------------------