├── .gitignore ├── .jitpack.yml ├── .travis.yml ├── README.md ├── build.gradle ├── build.gradle.kts ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── pom-default.xml ├── settings.gradle └── src ├── main └── java │ └── com │ └── memoizrlabs │ └── retrooptional │ ├── Action1.java │ ├── Empty.java │ ├── Function0.java │ ├── Function1.java │ ├── Optional.java │ ├── Predicate.java │ └── Some.java └── test └── java └── com └── memoizrlabs └── retrooptional └── OptionalTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | /.idea/libraries 5 | .idea/ 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /.jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - oraclejdk7 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | after_success: 5 | - if [ -e ./gradlew ]; then ./gradlew jacocoTestReport;else gradle jacocoTestReport;fi 6 | - bash <(curl -s https://codecov.io/bash) -t 132b7cb7-2aea-40f0-a0ea-87ddd017952e 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/memoizr/retro-optional.svg?branch=master)](https://travis-ci.org/memoizr/retro-optional) 2 | [![codecov](https://codecov.io/gh/memoizr/retro-optional/branch/master/graph/badge.svg)](https://codecov.io/gh/memoizr/retro-optional) 3 | [![](https://jitpack.io/v/memoizr/retro-optional.svg)](https://jitpack.io/#memoizr/retro-optional) 4 | [![GitHub license](https://img.shields.io/github/license/kotlintest/kotlintest.svg)](http://www.apache.org/licenses/LICENSE-2.0.html) 5 | ## Retro-Optional 6 | A backport of Java8 monad optionals for Java7. 7 | 8 | Example: 9 | Let's say we have an instance of a class `A` which can be nullable, and we're interested in getting the 10 | result of a chain of nested function calls, which eventually will result in a `boolean`. Every intermediate 11 | step may fail and return a null. To get to the end result without optionals we'd have to do multiple 12 | null checks: 13 | 14 | ```java 15 | boolean result = a != null && 16 | a.getB() != null && 17 | a.getB().getC() != null && 18 | a.getB().getC().isD(); 19 | ``` 20 | But by converting everything to return monadic optionals, the syntax can be streamlined a bit: 21 | ```java 22 | boolean result = a.flatMap(A::getB) 23 | .flatMap(A::getC) 24 | .filter(x -> x.isD()) 25 | .isPresent(); 26 | ``` 27 | For more examples of how to use Java 8 optional types, you can refer to this excellent article: 28 | http://www.nurkiewicz.com/2013/08/optional-in-java-8-cheat-sheet.html 29 | 30 | ## Get it 31 | ```groovy 32 | repositories { 33 | // ... 34 | maven { url "https://jitpack.io" } 35 | } 36 | ``` 37 | ```groovy 38 | dependencies { 39 | compile 'com.github.memoizr:retro-optional:0.2.0' 40 | } 41 | ``` 42 | 43 | Copyright 2015 memoizr 44 | 45 | Licensed under the Apache License, Version 2.0 (the "License"); 46 | you may not use this file except in compliance with the License. 47 | You may obtain a copy of the License at 48 | 49 | http://www.apache.org/licenses/LICENSE-2.0 50 | 51 | Unless required by applicable law or agreed to in writing, software 52 | distributed under the License is distributed on an "AS IS" BASIS, 53 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 54 | implied. 55 | See the License for the specific language governing permissions and 56 | limitations under the License. 57 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'maven' 3 | group = 'com.memoizr' 4 | version = 'v0.1.1' 5 | 6 | sourceCompatibility = 1.7 7 | targetCompatibility = 1.7 8 | 9 | repositories { 10 | jcenter() 11 | maven { url "https://jitpack.io" } 12 | } 13 | 14 | buildscript { 15 | repositories { 16 | jcenter() 17 | } 18 | dependencies { 19 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.4' 20 | } 21 | } 22 | dependencies { 23 | testCompile group: 'junit', name: 'junit', version: '4.11' 24 | } 25 | 26 | task sourcesJar(type: Jar, dependsOn: classes) { 27 | classifier = 'sources' 28 | from sourceSets.main.allSource 29 | } 30 | 31 | task javadocJar(type: Jar, dependsOn: javadoc) { 32 | classifier = 'javadoc' 33 | from javadoc.destinationDir 34 | } 35 | 36 | artifacts { 37 | archives sourcesJar 38 | archives javadocJar 39 | } 40 | 41 | ext { 42 | bintrayRepo = 'maven' 43 | bintrayName = 'retro-optional' 44 | 45 | publishedGroupId = 'com.memoizr' 46 | libraryName = 'retro-optional' 47 | artifact = 'retro-optional' 48 | 49 | libraryDescription = 'A backport of Java 8 optional for Java 7' 50 | 51 | siteUrl = 'https://github.com/memoizr/retro-optional' 52 | gitUrl = 'https://github.com/memoizr/retro-optional' 53 | 54 | libraryVersion = '0.9.3' 55 | 56 | developerId = 'memoizr' 57 | developerName = 'memoizr' 58 | developerEmail = 'memoizrlabs@gmail.com' 59 | 60 | licenseName = 'The Apache Software License, Version 2.0' 61 | licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 62 | allLicenses = ["Apache-2.0"] 63 | } 64 | 65 | install { 66 | repositories.mavenInstaller { 67 | pom.project { 68 | licenses { 69 | license { 70 | name 'The Apache Software License, Version 2.0' 71 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt' 72 | distribution 'repo' 73 | } 74 | } 75 | } 76 | } 77 | } 78 | 79 | apply plugin: 'com.jfrog.bintray' 80 | 81 | Properties myproperties = new Properties() 82 | myproperties.load(project.rootProject.file('local.properties').newDataInputStream()) 83 | 84 | bintray { 85 | user = myproperties.getProperty("bintray.user") 86 | key = myproperties.getProperty("bintray.apikey") 87 | 88 | configurations = ['archives'] 89 | pkg { 90 | repo = bintrayRepo 91 | name = bintrayName 92 | desc = libraryDescription 93 | websiteUrl = siteUrl 94 | vcsUrl = gitUrl 95 | licenses = allLicenses 96 | publish = true 97 | publicDownloadNumbers = true 98 | version { 99 | desc = libraryDescription 100 | gpg { 101 | sign = true //Determines whether to GPG sign the files. The default is false 102 | passphrase = myproperties.getProperty("bintray.gpg.password") 103 | //Optional. The passphrase for GPG signing' 104 | } 105 | } 106 | } 107 | } 108 | 109 | apply plugin: 'maven' 110 | 111 | task createPom << { 112 | pom { 113 | project { 114 | groupId 'com.memoizr' 115 | artifactId 'retro-optional' 116 | version 'v0.1.0' 117 | 118 | licenses { 119 | license { 120 | name 'The Apache Software License, Version 2.0' 121 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt' 122 | distribution 'repo' 123 | } 124 | } 125 | } 126 | }.writeTo("pom.xml") 127 | } 128 | 129 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.jfrog.bintray.gradle.BintrayExtension 2 | import org.apache.xml.serialize.OutputFormat 3 | import org.apache.xml.serialize.XMLSerializer 4 | import org.gradle.api.Project 5 | import org.gradle.api.Task 6 | import org.gradle.api.artifacts.PublishArtifact 7 | import org.gradle.api.artifacts.dsl.ArtifactHandler 8 | import org.gradle.api.plugins.MavenPluginConvention 9 | import org.gradle.api.tasks.javadoc.Javadoc 10 | import org.gradle.jvm.tasks.Jar 11 | import org.gradle.script.lang.kotlin.* 12 | import org.gradle.testing.jacoco.tasks.JacocoReport 13 | import org.w3c.dom.Document 14 | import org.w3c.dom.Node 15 | import java.io.StringWriter 16 | import javax.xml.parsers.DocumentBuilderFactory 17 | 18 | version = "v0.1.4" 19 | 20 | buildscript { 21 | repositories { 22 | jcenter() 23 | gradleScriptKotlin() 24 | } 25 | dependencies { 26 | classpath(kotlinModule("gradle-plugin")) 27 | classpath("com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7") 28 | } 29 | } 30 | 31 | repositories { 32 | jcenter() 33 | gradleScriptKotlin() 34 | } 35 | 36 | apply { 37 | plugin("java") 38 | plugin("maven") 39 | plugin("jacoco") 40 | plugin("com.jfrog.bintray") 41 | } 42 | 43 | 44 | setProperty("targetCompatibility", 1.7) 45 | setProperty("sourceCompatibility", 1.7) 46 | 47 | dependencies { 48 | testCompile("junit:junit:4.12") 49 | testCompile("nl.jqno.equalsverifier:equalsverifier:2.1.5") 50 | } 51 | 52 | val sourceSets = the().sourceSets 53 | 54 | val sourcesJar = task("sourcesJars") { 55 | dependsOn + "classes" 56 | classifier = "sources" 57 | from(sourceSets.getByName("main").allSource) 58 | } 59 | 60 | val javadoc = tasks.getByName("javadoc") as Javadoc 61 | val javadocJar = task("javadocJar") { 62 | dependsOn + "javadoc" 63 | classifier = "javadoc" 64 | from(javadoc.destinationDir) 65 | } 66 | 67 | artifacts { 68 | artifacts.add("archives", sourcesJar) 69 | artifacts.add("archives", javadocJar) 70 | } 71 | 72 | (getTasksByName("jacocoTestReport", false).first() as JacocoReport).apply { 73 | reports { 74 | it.xml.isEnabled = true 75 | } 76 | } 77 | 78 | configure { 79 | user = System.getenv("BINTRAY_USER") 80 | key = System.getenv("BINTRAY_APIKEY") 81 | setConfigurations("archives") 82 | pkg.apply { 83 | repo = "maven" 84 | name = "retro-optional" 85 | desc = "A backport of Java 8 optional for Java 7" 86 | websiteUrl = "https://github.com/memoizr/retro-optional" 87 | vcsUrl = "https://github.com/memoizr/retro-optional" 88 | publish = true 89 | publicDownloadNumbers = false 90 | version.apply { 91 | desc = "A backport of Java 8 optional for Java 7" 92 | gpg.apply { 93 | sign = true 94 | passphrase = System.getenv("BINTRAY_CREDENTIAL") 95 | } 96 | } 97 | } 98 | } 99 | 100 | val documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder() 101 | task("createPom") { 102 | doLast { 103 | the().pom().apply { 104 | project.apply { 105 | groupId = "com.memoizr" 106 | artifactId = "retro-optional" 107 | withXml { 108 | val xml = it.asString() 109 | val document: Document = documentBuilder.parse(xml.toString().byteInputStream()) 110 | 111 | document.append { 112 | "licenses" { 113 | "license" { 114 | "name"("The Apache Software License, Version 2.0") 115 | "url"("http://www.apache.org/licenses/LICENSE-2.0.txt") 116 | "distribution"("repo") 117 | } 118 | } 119 | } 120 | 121 | val format = OutputFormat(document) 122 | format.indenting = true 123 | val writer = StringWriter() 124 | val serializer = XMLSerializer(writer, format) 125 | serializer.serialize(document) 126 | 127 | xml.setLength(0) 128 | xml.append(writer) 129 | } 130 | } 131 | }.writeTo("build/poms/pom-default.xml") 132 | } 133 | } 134 | 135 | class DocuBuilder(private val document: Document, private val parent: Node) { 136 | operator fun String.invoke(content: DocuBuilder.() -> Unit) { 137 | val element = document.createElement(this) 138 | DocuBuilder(document, element).content() 139 | parent.appendChild(element) 140 | } 141 | 142 | operator fun String.invoke(text: String) { 143 | val element = document.createElement(this) 144 | element.appendChild(document.createTextNode(text)) 145 | parent.appendChild(element) 146 | } 147 | } 148 | 149 | fun Document.append(content: DocuBuilder.() -> Unit) { 150 | DocuBuilder(this, firstChild).content() 151 | } 152 | 153 | inline fun Project.artifacts(configuration: KotlinArtifactsHandler.() -> Unit) = 154 | KotlinArtifactsHandler(artifacts).configuration() 155 | 156 | class KotlinArtifactsHandler(val artifacts: ArtifactHandler) : ArtifactHandler by artifacts { 157 | 158 | operator fun String.invoke(dependencyNotation: Any): PublishArtifact = 159 | artifacts.add(this, dependencyNotation) 160 | 161 | inline operator fun invoke(configuration: KotlinArtifactsHandler.() -> Unit) = 162 | configuration() 163 | } 164 | 165 | infix fun Task.doLast(task: org.gradle.api.Task.() -> kotlin.Unit) { 166 | this.doLast(task) 167 | } 168 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/memoizr/retro-optional/e3270a31f2f5603093d95bc7dbfe9fee2959f586/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://repo.gradle.org/gradle/dist-snapshots/gradle-script-kotlin-3.0.0-20160805133427+0000-all.zip 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /pom-default.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | com.memoizr 6 | retro-optional 7 | v0.1.4 8 | 9 | 10 | junit 11 | junit 12 | 4.12 13 | test 14 | 15 | 16 | 17 | 18 | The Apache Software License, Version 2.0 19 | http://www.apache.org/licenses/LICENSE-2.0.txt 20 | repo 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'retro-optional' 2 | rootProject.buildFileName = 'build.gradle.kts' 3 | -------------------------------------------------------------------------------- /src/main/java/com/memoizrlabs/retrooptional/Action1.java: -------------------------------------------------------------------------------- 1 | package com.memoizrlabs.retrooptional; 2 | 3 | public interface Action1 { 4 | 5 | void accept(T t); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/memoizrlabs/retrooptional/Empty.java: -------------------------------------------------------------------------------- 1 | package com.memoizrlabs.retrooptional; 2 | 3 | import java.util.NoSuchElementException; 4 | 5 | final class Empty extends Optional { 6 | 7 | Empty() { 8 | } 9 | 10 | @Override 11 | public T get() { 12 | throw new NoSuchElementException(); 13 | } 14 | 15 | @Override 16 | public Optional map(Function1 function) { 17 | return empty(); 18 | } 19 | 20 | @Override 21 | public Optional flatMap(Function1> function) { 22 | return empty(); 23 | } 24 | 25 | @Override 26 | public boolean isPresent() { 27 | return false; 28 | } 29 | 30 | @Override 31 | public Optional filter(Predicate predicate) { 32 | return empty(); 33 | } 34 | 35 | @Override 36 | public void doIfPresent(Action1 action1) { 37 | } 38 | 39 | @Override 40 | public T orElse(T alternative) { 41 | return alternative; 42 | } 43 | 44 | @Override 45 | public T orElseGet(Function0 function) { 46 | return function.apply(); 47 | } 48 | 49 | @Override 50 | public T orElseThrow(Function0 function) throws X { 51 | throw function.apply(); 52 | } 53 | 54 | @Override 55 | public String toString() { 56 | return "Empty option"; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/memoizrlabs/retrooptional/Function0.java: -------------------------------------------------------------------------------- 1 | package com.memoizrlabs.retrooptional; 2 | 3 | public interface Function0 { 4 | 5 | T apply(); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/memoizrlabs/retrooptional/Function1.java: -------------------------------------------------------------------------------- 1 | package com.memoizrlabs.retrooptional; 2 | 3 | public interface Function1 { 4 | 5 | K apply(T t); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/memoizrlabs/retrooptional/Optional.java: -------------------------------------------------------------------------------- 1 | package com.memoizrlabs.retrooptional; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * A wrapper class that wraps non-null values that may or may not be there. As this wrapper acts as a 7 | * monad, it is possible to safely perform chained operations on wrapped values that may or may not exist. 8 | * 9 | * As this is a value-based class, equals and hashcode operations are simply forwarded to the wrapped object. 10 | * This may cause unexpected behavior for some identity-sensitive operations, therefore equals and hascode operations 11 | * should generally be avoided on wrappers. 12 | * 13 | * @param the type of the wrapped value. 14 | */ 15 | public abstract class Optional implements Serializable { 16 | 17 | private static final Empty EMPTY = new Empty<>(); 18 | 19 | /** 20 | * Wraps a value in a non-empty wrapper if the value is non-null and returns the wrapped value, 21 | * returns an empty wrapper in case the value is null. 22 | * 23 | * @param value the nullable value to wrap. 24 | * @param the type of the value to be wrapped. 25 | * @return the wrapped value or an empty wrapper if the value is null. 26 | */ 27 | public static Optional of(T value) { 28 | if (value == null) { 29 | return empty(); 30 | } else { 31 | return new Some<>(value); 32 | } 33 | } 34 | 35 | /** 36 | * Returns an empty instance to wrap missing values. 37 | * 38 | * @param the type of the missing value. 39 | * @return the empty wrapper. 40 | */ 41 | @SuppressWarnings("unchecked") 42 | public static Optional empty() { 43 | return (Optional) EMPTY; 44 | } 45 | 46 | /** 47 | * Extracts the value from the wrapper. Throws a NoSuchElementException if 48 | * performed on an empty wrapper. 49 | * 50 | * @return the unwrapped value. 51 | */ 52 | public abstract T get(); 53 | 54 | /** 55 | * Returns a wrapped object obtained from a function if performed on a non-empty wrapper. 56 | * 57 | * @param function the factory function that generates the object. 58 | * @param the type of the transformed object; 59 | * @return the transformed object or an empty wrapper if executed on an empty-wrapper. 60 | */ 61 | public abstract Optional map(Function1 function); 62 | 63 | /** 64 | * If the predicate evaluates to true, returns the instance, otherwise returns an empty 65 | * wrapper. 66 | * 67 | * @param predicate the predicate function to evaluated. 68 | * @return the same instance of the wrapped object. 69 | */ 70 | public abstract Optional filter(Predicate predicate); 71 | 72 | /** 73 | * If performed on a non-empty wrapper, returns the wrapped object returned by the specified 74 | * function. Otherwise returns an empty wrapper. 75 | * 76 | * @param function the function that returns another wrapped object. 77 | * @param the type of the new returned object. 78 | * @return the wrapped object returned by the function. 79 | */ 80 | public abstract Optional flatMap(Function1> function); 81 | 82 | /** 83 | * Tells whether this wrapper is non-empty or empty. 84 | * 85 | * @return true if the wrapper is non-empty, false if empty. 86 | */ 87 | public abstract boolean isPresent(); 88 | 89 | /** 90 | * Calls a function if called on a non-empty wrapper. 91 | * 92 | * @param action1 the function to call. 93 | */ 94 | public abstract void doIfPresent(Action1 action1); 95 | 96 | /** 97 | * Returns the unwrapped value if called on a non-empty wrapper, or returns the specified value if called 98 | * on an empty wrapper. 99 | * 100 | * @param alternative the value to return in case the wrapper is empty. 101 | * @return either the unwrapped value or the specified one. 102 | */ 103 | public abstract T orElse(T alternative); 104 | 105 | /** 106 | * Returns the unwrapped value if called on a non-empty wrapper, or returns the value returned by the 107 | * specified function if called on an empty wrapper. 108 | * 109 | * @param function the unwrapped value or the specified one. 110 | * @return either the unwrapped value or the value obtained from the specified function. 111 | */ 112 | public abstract T orElseGet(Function0 function); 113 | 114 | /** 115 | * Returns the unwrapped valued if called on a non-empty wrapper, or throws the exeception returned by the 116 | * specified function if called on an empty wrapper. 117 | * 118 | * @param function the function that returns the exception in case the wrapper is empty. 119 | * @param the type of the exception 120 | * @return the exception to throw 121 | * @throws X the thrown exception. 122 | */ 123 | public abstract T orElseThrow(Function0 function) throws X; 124 | 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/com/memoizrlabs/retrooptional/Predicate.java: -------------------------------------------------------------------------------- 1 | package com.memoizrlabs.retrooptional; 2 | 3 | public interface Predicate { 4 | 5 | boolean verify(T subject); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/memoizrlabs/retrooptional/Some.java: -------------------------------------------------------------------------------- 1 | package com.memoizrlabs.retrooptional; 2 | 3 | final class Some extends Optional { 4 | 5 | private final T value; 6 | 7 | Some(T value) { 8 | this.value = value; 9 | } 10 | 11 | @Override 12 | public T get() { 13 | return value; 14 | } 15 | 16 | @Override 17 | public Optional map(Function1 function) { 18 | return of(function.apply(value)); 19 | } 20 | 21 | @Override 22 | public Optional filter(Predicate predicate) { 23 | if (predicate.verify(value)) { 24 | return this; 25 | } else { 26 | return empty(); 27 | } 28 | } 29 | 30 | @Override 31 | public Optional flatMap(Function1> function) { 32 | return function.apply(value); 33 | } 34 | 35 | @Override 36 | public boolean isPresent() { 37 | return true; 38 | } 39 | 40 | @Override 41 | public void doIfPresent(Action1 action1) { 42 | action1.accept(value); 43 | } 44 | 45 | @Override 46 | public T orElse(T alternative) { 47 | return value; 48 | } 49 | 50 | @Override 51 | public T orElseGet(Function0 function) { 52 | return value; 53 | } 54 | 55 | @Override 56 | public T orElseThrow(Function0 function) throws X { 57 | return value; 58 | } 59 | 60 | @Override 61 | public String toString() { 62 | return "Some {" + 63 | "value=" + value + 64 | '}'; 65 | } 66 | 67 | @Override 68 | public boolean equals(Object o) { 69 | if (this == o) { 70 | return true; 71 | } 72 | if (o == null || getClass() != o.getClass()) { 73 | return false; 74 | } 75 | 76 | Some some = (Some) o; 77 | 78 | return value != null ? value.equals(some.value) : some.value == null; 79 | } 80 | 81 | @Override 82 | public int hashCode() { 83 | return value != null ? value.hashCode() : 0; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/test/java/com/memoizrlabs/retrooptional/OptionalTest.java: -------------------------------------------------------------------------------- 1 | package com.memoizrlabs.retrooptional; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.NoSuchElementException; 6 | import java.util.concurrent.atomic.AtomicInteger; 7 | 8 | import nl.jqno.equalsverifier.EqualsVerifier; 9 | 10 | import static junit.framework.TestCase.assertEquals; 11 | import static junit.framework.TestCase.fail; 12 | import static org.junit.Assert.assertFalse; 13 | import static org.junit.Assert.assertTrue; 14 | 15 | public class OptionalTest { 16 | 17 | private static final String CONTENT_TO_STRING = "Content description"; 18 | 19 | @Test 20 | public void of_withNullValue_returnsEmpty() throws Exception { 21 | assertFalse(Optional.of(null).isPresent()); 22 | } 23 | 24 | @Test 25 | public void of_withValue_returnsWrappedValue() throws Exception { 26 | assertTrue(Optional.of(new A()) 27 | .isPresent()); 28 | } 29 | 30 | @Test 31 | public void get_withValue_returnsValue() throws Exception { 32 | final A value = new A(); 33 | 34 | assertEquals(value, Optional.of(value) 35 | .get()); 36 | } 37 | 38 | @Test(expected = NoSuchElementException.class) 39 | public void get_withoutValue_throwsException() throws Exception { 40 | Optional.empty().get(); 41 | } 42 | 43 | @Test 44 | public void map_withValue_continuesTheChain() throws Exception { 45 | final A value = new A(); 46 | final B other = new B(); 47 | 48 | Function1 function = new Function1() { 49 | @Override 50 | public B apply(A a) { 51 | return other; 52 | } 53 | }; 54 | 55 | Optional optionalB = Optional.of(value).map(function); 56 | 57 | assertEquals(other, optionalB.get()); 58 | } 59 | 60 | @Test 61 | public void map_withoutValue_doesNotContinueTheChain() throws Exception { 62 | Optional.empty().map(new Function1() { 63 | @Override 64 | public Object apply(Object a) { 65 | fail(); 66 | return null; 67 | } 68 | }); 69 | } 70 | 71 | @Test 72 | public void flatMap_withValue_continuesTheChain() throws Exception { 73 | final A value = new A(); 74 | final B other = new B(); 75 | 76 | Function1> monadicFunction = new Function1>() { 77 | @Override 78 | public Optional apply(A a) { 79 | return Optional.of(other); 80 | } 81 | }; 82 | 83 | Optional optionalB = Optional.of(value).flatMap(monadicFunction); 84 | 85 | assertEquals(other, optionalB.get()); 86 | } 87 | 88 | @Test 89 | public void flatMap_withNoValue_doesNotContinue() throws Exception { 90 | Optional.empty().flatMap(new Function1>() { 91 | @Override 92 | public Optional apply(Object a) { 93 | fail(); 94 | return null; 95 | } 96 | }); 97 | } 98 | 99 | @Test 100 | public void filterFilters_withValue_ifMatches_returnsWrappedValue() throws Exception { 101 | A value = new A(); 102 | 103 | Optional filteredOption = Optional.of(value).filter(new Predicate() { 104 | @Override 105 | public boolean verify(A a) { 106 | return true; 107 | } 108 | }); 109 | 110 | assertEquals(value, filteredOption.get()); 111 | } 112 | 113 | 114 | @Test 115 | public void filterFilters_withValue_ifNoMatch_returnsEmpty() throws Exception { 116 | Optional filteredOption = Optional.of(new A()).filter(new Predicate() { 117 | @Override 118 | public boolean verify(A a) { 119 | return false; 120 | } 121 | }); 122 | 123 | assertFalse(filteredOption.isPresent()); 124 | } 125 | 126 | @Test 127 | public void filterFilters_withoutValue_returnsEmpty() throws Exception { 128 | 129 | Optional filteredOption = Optional.empty().filter(new Predicate() { 130 | @Override 131 | public boolean verify(A a) { 132 | return false; 133 | } 134 | }); 135 | 136 | assertFalse(filteredOption.isPresent()); 137 | } 138 | 139 | @Test 140 | public void isPresent_withValue_returnsTrue() throws Exception { 141 | assertTrue(Optional.of(new A()) 142 | .isPresent()); 143 | } 144 | 145 | @Test 146 | public void isPresent_withoutValue_returnsFalse() throws Exception { 147 | assertFalse(Optional.empty() 148 | .isPresent()); 149 | } 150 | 151 | @Test 152 | public void doIfPresent_withValue_executesFunction() throws Exception { 153 | A value = new A(); 154 | 155 | final AtomicInteger integer = new AtomicInteger(); 156 | 157 | Optional.of(value) 158 | .doIfPresent(new Action1() { 159 | @Override 160 | public void accept(A x) { 161 | integer.incrementAndGet(); 162 | } 163 | }); 164 | 165 | assertEquals(1, integer.get()); 166 | } 167 | 168 | @Test 169 | public void doIfPresentDoesnt_withoutValue_doesNotExecuteFunction() throws Exception { 170 | Optional.empty() 171 | .doIfPresent(new Action1() { 172 | @Override 173 | public void accept(Object x) { 174 | fail(); 175 | } 176 | }); 177 | } 178 | 179 | @Test 180 | public void orElse_withValue_returnsValue() throws Exception { 181 | A value = new A(); 182 | A alternative = new A(); 183 | 184 | assertEquals(value, Optional.of(value) 185 | .orElse(alternative)); 186 | } 187 | 188 | @Test 189 | public void orElse_withoutValue_returnsAlternative() throws Exception { 190 | A alternative = new A(); 191 | 192 | assertEquals(alternative, Optional.empty() 193 | .orElse(alternative)); 194 | } 195 | 196 | @Test 197 | public void orElseGet_withValue_returnsValue() throws Exception { 198 | final A value = new A(); 199 | final A alternative = new A(); 200 | 201 | assertEquals(value, Optional.of(value) 202 | .orElseGet(new Function0() { 203 | @Override 204 | public A apply() { 205 | return alternative; 206 | } 207 | })); 208 | } 209 | 210 | @Test 211 | public void orElseGet_withoutValue_returnsAlternative() throws Exception { 212 | final A alternative = new A(); 213 | 214 | assertEquals(alternative, Optional.empty() 215 | .orElseGet(new Function0() { 216 | @Override 217 | public Object apply() { 218 | return alternative; 219 | } 220 | })); 221 | } 222 | 223 | @Test 224 | public void orElseThrow_withValue_returnsValue() throws Exception, AnException { 225 | A value = new A(); 226 | 227 | assertEquals(value, Optional.of(value) 228 | .orElseThrow(new Function0() { 229 | @Override 230 | public AnException apply() { 231 | return new AnException(); 232 | } 233 | })); 234 | } 235 | 236 | @Test(expected = AnException.class) 237 | public void orElseThrow_withoutValue_throwsException() throws Exception, AnException { 238 | Optional.empty() 239 | .orElseThrow(new Function0() { 240 | @Override 241 | public AnException apply() { 242 | return new AnException(); 243 | } 244 | }); 245 | } 246 | 247 | @Test 248 | public void toString_withValue_printsTheValue() throws Exception { 249 | String s = Optional.of(new A()) 250 | .toString(); 251 | 252 | assertTrue(s.contains(CONTENT_TO_STRING)); 253 | } 254 | 255 | @Test 256 | public void toString_withoutValue_printsEmptyDescription() throws Exception { 257 | String s = Optional.empty() 258 | .toString(); 259 | 260 | assertTrue(s.toLowerCase() 261 | .contains("empty")); 262 | } 263 | 264 | @Test 265 | public void equals_withValue_returnsTrueIfValueIsEqual() throws Exception { 266 | A value = new A(); 267 | assertTrue(Optional.of(value) 268 | .equals(Optional.of(value))); 269 | } 270 | 271 | @Test 272 | public void hashcode_withValue_returnsValuesHashcode() throws Exception { 273 | A value = new A(); 274 | assertEquals(value.hashCode(), Optional.of(value).hashCode()); 275 | } 276 | 277 | @Test 278 | public void equalsAndHashcode_withValue_fulfilsStandardContract() throws Exception { 279 | EqualsVerifier.forClass(Optional.of(new A()).getClass()).verify(); 280 | } 281 | 282 | @Test 283 | public void equals_withAnotherEmpty_isAlwaysTrue() throws Exception { 284 | assertEquals(Optional.of(null), Optional.empty()); 285 | } 286 | 287 | private static final class A { 288 | @Override 289 | public String toString() { 290 | return CONTENT_TO_STRING; 291 | } 292 | } 293 | 294 | private static final class B { 295 | } 296 | 297 | private static final class AnException extends Throwable { 298 | } 299 | } 300 | 301 | --------------------------------------------------------------------------------