Merge pull request #470 from iluwatar/Promise
Implements #403 Promise pattern
This commit is contained in:
		
							
								
								
									
										1
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								pom.xml
									
									
									
									
									
								
							@@ -129,6 +129,7 @@
 | 
			
		||||
		<module>hexagonal</module>
 | 
			
		||||
		<module>abstract-document</module>
 | 
			
		||||
		<module>aggregator-microservices</module>
 | 
			
		||||
		<module>promise</module>
 | 
			
		||||
        <module>page-object</module>
 | 
			
		||||
	</modules>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										46
									
								
								promise/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								promise/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
---
 | 
			
		||||
layout: pattern
 | 
			
		||||
title: Promise
 | 
			
		||||
folder: promise
 | 
			
		||||
permalink: /patterns/promise/
 | 
			
		||||
categories: Concurrency
 | 
			
		||||
tags:
 | 
			
		||||
 - Java
 | 
			
		||||
 - Functional
 | 
			
		||||
 - Reactive
 | 
			
		||||
 - Difficulty-Intermediate
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
## Also known as
 | 
			
		||||
CompletableFuture
 | 
			
		||||
 | 
			
		||||
## Intent
 | 
			
		||||
A Promise represents a proxy for a value not necessarily known when the promise is created. It
 | 
			
		||||
allows you to associate dependent promises to an asynchronous action's eventual success value or 
 | 
			
		||||
failure reason. Promises are a way to write async code that still appears as though it is executing 
 | 
			
		||||
in a synchronous way.
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
## Applicability
 | 
			
		||||
Promise pattern is applicable in concurrent programming when some work needs to be done asynchronously
 | 
			
		||||
and:
 | 
			
		||||
 | 
			
		||||
* code maintainablity and readability suffers due to callback hell.
 | 
			
		||||
* you need to compose promises and need better error handling for asynchronous tasks.
 | 
			
		||||
* you want to use functional style of programming.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Real world examples
 | 
			
		||||
 | 
			
		||||
* [java.util.concurrent.CompletableFuture](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html)
 | 
			
		||||
* [Guava ListenableFuture](https://github.com/google/guava/wiki/ListenableFutureExplained)
 | 
			
		||||
 | 
			
		||||
## Related Patterns
 | 
			
		||||
 * Async Method Invocation
 | 
			
		||||
 * Callback
 | 
			
		||||
 | 
			
		||||
## Credits
 | 
			
		||||
 | 
			
		||||
* [You are missing the point to Promises](https://gist.github.com/domenic/3889970)
 | 
			
		||||
* [Functional style callbacks using CompleteableFuture](https://www.infoq.com/articles/Functional-Style-Callbacks-Using-CompletableFuture)
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								promise/etc/promise.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								promise/etc/promise.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 58 KiB  | 
							
								
								
									
										111
									
								
								promise/etc/promise.ucls
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								promise/etc/promise.ucls
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,111 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<class-diagram version="1.1.10" icons="true" automaticImage="PNG" always-add-relationships="false" 
 | 
			
		||||
  generalizations="true" realizations="true" associations="true" dependencies="false" nesting-relationships="true" 
 | 
			
		||||
  router="FAN">  
 | 
			
		||||
  <class id="1" language="java" name="com.iluwatar.promise.Promise" project="promise" 
 | 
			
		||||
    file="/promise/src/main/java/com/iluwatar/promise/Promise.java" binary="false" corner="BOTTOM_RIGHT">    
 | 
			
		||||
    <position height="-1" width="-1" x="524" y="541"/>    
 | 
			
		||||
    <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" 
 | 
			
		||||
      sort-features="false" accessors="true" visibility="true">      
 | 
			
		||||
      <attributes public="true" package="true" protected="true" private="false" static="true"/>      
 | 
			
		||||
      <operations public="true" package="false" protected="true" private="false" static="true"/>    
 | 
			
		||||
    </display>  
 | 
			
		||||
  </class>  
 | 
			
		||||
  <interface id="2" language="java" name="java.util.concurrent.Future" project="async-method-invocation" 
 | 
			
		||||
    file="/usr/lib/java/jdk1.8.0_45/jre/lib/rt.jar" binary="true" corner="BOTTOM_RIGHT">    
 | 
			
		||||
    <position height="-1" width="-1" x="527" y="94"/>    
 | 
			
		||||
    <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" 
 | 
			
		||||
      sort-features="false" accessors="true" visibility="true">      
 | 
			
		||||
      <attributes public="true" package="true" protected="true" private="true" static="true"/>      
 | 
			
		||||
      <operations public="true" package="true" protected="true" private="true" static="true"/>    
 | 
			
		||||
    </display>  
 | 
			
		||||
  </interface>  
 | 
			
		||||
  <class id="3" language="java" name="com.iluwatar.promise.PromiseSupport" project="promise" 
 | 
			
		||||
    file="/promise/src/main/java/com/iluwatar/promise/PromiseSupport.java" binary="false" corner="BOTTOM_RIGHT">    
 | 
			
		||||
    <position height="-1" width="-1" x="524" y="313"/>    
 | 
			
		||||
    <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" 
 | 
			
		||||
      sort-features="false" accessors="true" visibility="true">      
 | 
			
		||||
      <attributes public="true" package="false" protected="true" private="false" static="true"/>      
 | 
			
		||||
      <operations public="true" package="false" protected="true" private="true" static="true"/>    
 | 
			
		||||
    </display>  
 | 
			
		||||
  </class>  
 | 
			
		||||
  <interface id="4" language="java" name="java.util.concurrent.Executor" project="async-method-invocation" 
 | 
			
		||||
    file="/usr/lib/java/jdk1.8.0_45/jre/lib/rt.jar" binary="true" corner="BOTTOM_RIGHT">    
 | 
			
		||||
    <position height="-1" width="-1" x="798" y="541"/>    
 | 
			
		||||
    <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" 
 | 
			
		||||
      sort-features="false" accessors="true" visibility="true">      
 | 
			
		||||
      <attributes public="true" package="true" protected="true" private="true" static="true"/>      
 | 
			
		||||
      <operations public="true" package="true" protected="true" private="true" static="true"/>    
 | 
			
		||||
    </display>  
 | 
			
		||||
  </interface>  
 | 
			
		||||
  <interface id="5" language="java" name="java.util.concurrent.Callable" project="async-method-invocation" 
 | 
			
		||||
    file="/usr/lib/java/jdk1.8.0_45/jre/lib/rt.jar" binary="true" corner="BOTTOM_RIGHT">    
 | 
			
		||||
    <position height="-1" width="-1" x="847" y="345"/>    
 | 
			
		||||
    <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" 
 | 
			
		||||
      sort-features="false" accessors="true" visibility="true">      
 | 
			
		||||
      <attributes public="true" package="true" protected="true" private="true" static="true"/>      
 | 
			
		||||
      <operations public="true" package="true" protected="true" private="true" static="true"/>    
 | 
			
		||||
    </display>  
 | 
			
		||||
  </interface>  
 | 
			
		||||
  <interface id="6" language="java" name="java.util.function.Consumer" project="async-method-invocation" 
 | 
			
		||||
    file="/usr/lib/java/jdk1.8.0_45/jre/lib/rt.jar" binary="true" corner="BOTTOM_RIGHT">    
 | 
			
		||||
    <position height="-1" width="-1" x="158" y="336"/>    
 | 
			
		||||
    <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" 
 | 
			
		||||
      sort-features="false" accessors="true" visibility="true">      
 | 
			
		||||
      <attributes public="true" package="true" protected="true" private="true" static="true"/>      
 | 
			
		||||
      <operations public="true" package="true" protected="true" private="false" static="true"/>    
 | 
			
		||||
    </display>  
 | 
			
		||||
  </interface>  
 | 
			
		||||
  <interface id="7" language="java" name="java.util.function.Function" project="async-method-invocation" 
 | 
			
		||||
    file="/usr/lib/java/jdk1.8.0_45/jre/lib/rt.jar" binary="true" corner="BOTTOM_RIGHT">    
 | 
			
		||||
    <position height="-1" width="-1" x="166" y="546"/>    
 | 
			
		||||
    <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" 
 | 
			
		||||
      sort-features="false" accessors="true" visibility="true">      
 | 
			
		||||
      <attributes public="true" package="true" protected="true" private="true" static="true"/>      
 | 
			
		||||
      <operations public="true" package="true" protected="true" private="false" static="false"/>    
 | 
			
		||||
    </display>  
 | 
			
		||||
  </interface>  
 | 
			
		||||
  <class id="8" language="java" name="com.iluwatar.promise.App" project="promise" 
 | 
			
		||||
    file="/promise/src/main/java/com/iluwatar/promise/App.java" binary="false" corner="BOTTOM_RIGHT">    
 | 
			
		||||
    <position height="-1" width="-1" x="801" y="189"/>    
 | 
			
		||||
    <display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" 
 | 
			
		||||
      sort-features="false" accessors="true" visibility="true">      
 | 
			
		||||
      <attributes public="true" package="true" protected="true" private="false" static="true"/>      
 | 
			
		||||
      <operations public="true" package="true" protected="true" private="false" static="true"/>    
 | 
			
		||||
    </display>  
 | 
			
		||||
  </class>  
 | 
			
		||||
  <dependency id="9">    
 | 
			
		||||
    <end type="SOURCE" refId="1"/>    
 | 
			
		||||
    <end type="TARGET" refId="5"/>  
 | 
			
		||||
  </dependency>  
 | 
			
		||||
  <dependency id="10">    
 | 
			
		||||
    <end type="SOURCE" refId="8"/>    
 | 
			
		||||
    <end type="TARGET" refId="1"/>  
 | 
			
		||||
  </dependency>  
 | 
			
		||||
  <dependency id="11">    
 | 
			
		||||
    <end type="SOURCE" refId="1"/>    
 | 
			
		||||
    <end type="TARGET" refId="6"/>  
 | 
			
		||||
  </dependency>  
 | 
			
		||||
  <generalization id="12">    
 | 
			
		||||
    <end type="SOURCE" refId="1"/>    
 | 
			
		||||
    <end type="TARGET" refId="3"/>  
 | 
			
		||||
  </generalization>  
 | 
			
		||||
  <dependency id="13">    
 | 
			
		||||
    <end type="SOURCE" refId="1"/>    
 | 
			
		||||
    <end type="TARGET" refId="4"/>  
 | 
			
		||||
  </dependency>  
 | 
			
		||||
  <dependency id="14">    
 | 
			
		||||
    <end type="SOURCE" refId="1"/>    
 | 
			
		||||
    <end type="TARGET" refId="7"/>  
 | 
			
		||||
  </dependency>  
 | 
			
		||||
  <realization id="15">    
 | 
			
		||||
    <end type="SOURCE" refId="3"/>    
 | 
			
		||||
    <end type="TARGET" refId="2"/>  
 | 
			
		||||
  </realization>  
 | 
			
		||||
  <classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true" 
 | 
			
		||||
    sort-features="false" accessors="true" visibility="true">    
 | 
			
		||||
    <attributes public="true" package="true" protected="true" private="true" static="true"/>    
 | 
			
		||||
    <operations public="true" package="true" protected="true" private="true" static="true"/>  
 | 
			
		||||
  </classifier-display>  
 | 
			
		||||
  <association-display labels="true" multiplicity="true"/>
 | 
			
		||||
</class-diagram>
 | 
			
		||||
							
								
								
									
										47
									
								
								promise/pom.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								promise/pom.xml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
<?xml version="1.0"?>
 | 
			
		||||
<!--
 | 
			
		||||
 | 
			
		||||
    The MIT License
 | 
			
		||||
    Copyright (c) 2014 Ilkka Seppälä
 | 
			
		||||
 | 
			
		||||
    Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
    of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
    in the Software without restriction, including without limitation the rights
 | 
			
		||||
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
    copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
    furnished to do so, subject to the following conditions:
 | 
			
		||||
 | 
			
		||||
    The above copyright notice and this permission notice shall be included in
 | 
			
		||||
    all copies or substantial portions of the Software.
 | 
			
		||||
 | 
			
		||||
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
			
		||||
    THE SOFTWARE.
 | 
			
		||||
 | 
			
		||||
-->
 | 
			
		||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
 | 
			
		||||
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 | 
			
		||||
  <modelVersion>4.0.0</modelVersion>
 | 
			
		||||
  <parent>
 | 
			
		||||
    <groupId>com.iluwatar</groupId>
 | 
			
		||||
    <artifactId>java-design-patterns</artifactId>
 | 
			
		||||
    <version>1.13.0-SNAPSHOT</version>
 | 
			
		||||
  </parent>
 | 
			
		||||
  <artifactId>promise</artifactId>
 | 
			
		||||
  <dependencies>
 | 
			
		||||
    <dependency>
 | 
			
		||||
      <groupId>junit</groupId>
 | 
			
		||||
      <artifactId>junit</artifactId>
 | 
			
		||||
      <scope>test</scope>
 | 
			
		||||
    </dependency>
 | 
			
		||||
    <dependency>
 | 
			
		||||
      <groupId>org.mockito</groupId>
 | 
			
		||||
      <artifactId>mockito-core</artifactId>
 | 
			
		||||
      <scope>test</scope>
 | 
			
		||||
    </dependency>
 | 
			
		||||
  </dependencies>
 | 
			
		||||
</project>
 | 
			
		||||
							
								
								
									
										176
									
								
								promise/src/main/java/com/iluwatar/promise/App.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								promise/src/main/java/com/iluwatar/promise/App.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,176 @@
 | 
			
		||||
/**
 | 
			
		||||
 * The MIT License
 | 
			
		||||
 * Copyright (c) 2014 Ilkka Seppälä
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in
 | 
			
		||||
 * all copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
			
		||||
 * THE SOFTWARE.
 | 
			
		||||
 */
 | 
			
		||||
package com.iluwatar.promise;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.concurrent.CompletableFuture;
 | 
			
		||||
import java.util.concurrent.CountDownLatch;
 | 
			
		||||
import java.util.concurrent.ExecutionException;
 | 
			
		||||
import java.util.concurrent.ExecutorService;
 | 
			
		||||
import java.util.concurrent.Executors;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 *
 | 
			
		||||
 *  The Promise object is used for asynchronous computations. A Promise represents an operation
 | 
			
		||||
 *  that hasn't completed yet, but is expected in the future.
 | 
			
		||||
 *
 | 
			
		||||
 * <p>A Promise represents a proxy for a value not necessarily known when the promise is created. It
 | 
			
		||||
 * allows you to associate dependent promises to an asynchronous action's eventual success value or
 | 
			
		||||
 * failure reason. This lets asynchronous methods return values like synchronous methods: instead 
 | 
			
		||||
 * of the final value, the asynchronous method returns a promise of having a value at some point 
 | 
			
		||||
 * in the future.
 | 
			
		||||
 *
 | 
			
		||||
 * <p>Promises provide a few advantages over callback objects:
 | 
			
		||||
 * <ul>
 | 
			
		||||
 * <li> Functional composition and error handling
 | 
			
		||||
 * <li> Prevents callback hell and provides callback aggregation
 | 
			
		||||
 * </ul>
 | 
			
		||||
 *
 | 
			
		||||
 * <p>
 | 
			
		||||
 * In this application the usage of promise is demonstrated with two examples:
 | 
			
		||||
 * <ul>
 | 
			
		||||
 * <li>Count Lines: In this example a file is downloaded and its line count is calculated.
 | 
			
		||||
 * The calculated line count is then consumed and printed on console.
 | 
			
		||||
 * <li>Lowest Character Frequency: In this example a file is downloaded and its lowest frequency
 | 
			
		||||
 * character is found and printed on console. This happens via a chain of promises, we start with
 | 
			
		||||
 * a file download promise, then a promise of character frequency, then a promise of lowest frequency
 | 
			
		||||
 * character which is finally consumed and result is printed on console.
 | 
			
		||||
 * </ul>
 | 
			
		||||
 * 
 | 
			
		||||
 * @see CompletableFuture
 | 
			
		||||
 */
 | 
			
		||||
public class App {
 | 
			
		||||
 | 
			
		||||
  private static final String DEFAULT_URL = "https://raw.githubusercontent.com/iluwatar/java-design-patterns/Promise/promise/README.md";
 | 
			
		||||
  private final ExecutorService executor;
 | 
			
		||||
  private final CountDownLatch stopLatch;
 | 
			
		||||
 | 
			
		||||
  private App() {
 | 
			
		||||
    executor = Executors.newFixedThreadPool(2);
 | 
			
		||||
    stopLatch = new CountDownLatch(2);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Program entry point
 | 
			
		||||
   * @param args arguments
 | 
			
		||||
   * @throws InterruptedException if main thread is interrupted.
 | 
			
		||||
   * @throws ExecutionException if an execution error occurs.
 | 
			
		||||
   */
 | 
			
		||||
  public static void main(String[] args) throws InterruptedException, ExecutionException {
 | 
			
		||||
    App app = new App();
 | 
			
		||||
    try {
 | 
			
		||||
      app.promiseUsage();
 | 
			
		||||
    } finally {
 | 
			
		||||
      app.stop();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void promiseUsage() {
 | 
			
		||||
    calculateLineCount();
 | 
			
		||||
 | 
			
		||||
    calculateLowestFrequencyChar();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /*
 | 
			
		||||
   * Calculate the lowest frequency character and when that promise is fulfilled,
 | 
			
		||||
   * consume the result in a Consumer<Character>
 | 
			
		||||
   */
 | 
			
		||||
  private void calculateLowestFrequencyChar() {
 | 
			
		||||
    lowestFrequencyChar()
 | 
			
		||||
        .thenAccept(
 | 
			
		||||
          charFrequency -> {
 | 
			
		||||
            System.out.println("Char with lowest frequency is: " + charFrequency);
 | 
			
		||||
            taskCompleted();
 | 
			
		||||
          }
 | 
			
		||||
      );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /*
 | 
			
		||||
   * Calculate the line count and when that promise is fulfilled, consume the result
 | 
			
		||||
   * in a Consumer<Integer>
 | 
			
		||||
   */
 | 
			
		||||
  private void calculateLineCount() {
 | 
			
		||||
    countLines()
 | 
			
		||||
        .thenAccept(
 | 
			
		||||
          count -> {
 | 
			
		||||
            System.out.println("Line count is: " + count);
 | 
			
		||||
            taskCompleted();
 | 
			
		||||
          }
 | 
			
		||||
      );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /*
 | 
			
		||||
   * Calculate the character frequency of a file and when that promise is fulfilled,
 | 
			
		||||
   * then promise to apply function to calculate lowest character frequency.
 | 
			
		||||
   */
 | 
			
		||||
  private Promise<Character> lowestFrequencyChar() {
 | 
			
		||||
    return characterFrequency()
 | 
			
		||||
        .thenApply(Utility::lowestFrequencyChar);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /*
 | 
			
		||||
   * Download the file at DEFAULT_URL and when that promise is fulfilled,
 | 
			
		||||
   * then promise to apply function to calculate character frequency.
 | 
			
		||||
   */
 | 
			
		||||
  private Promise<Map<Character, Integer>> characterFrequency() {
 | 
			
		||||
    return download(DEFAULT_URL)
 | 
			
		||||
        .thenApply(Utility::characterFrequency);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /*
 | 
			
		||||
   * Download the file at DEFAULT_URL and when that promise is fulfilled,
 | 
			
		||||
   * then promise to apply function to count lines in that file.
 | 
			
		||||
   */
 | 
			
		||||
  private Promise<Integer> countLines() {
 | 
			
		||||
    return download(DEFAULT_URL)
 | 
			
		||||
        .thenApply(Utility::countLines);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /*
 | 
			
		||||
   * Return a promise to provide the local absolute path of the file downloaded in background.
 | 
			
		||||
   * This is an async method and does not wait until the file is downloaded.
 | 
			
		||||
   */
 | 
			
		||||
  private Promise<String> download(String urlString) {
 | 
			
		||||
    Promise<String> downloadPromise = new Promise<String>()
 | 
			
		||||
        .fulfillInAsync(
 | 
			
		||||
            () -> {
 | 
			
		||||
              return Utility.downloadFile(urlString);
 | 
			
		||||
            }, executor)
 | 
			
		||||
        .onError(
 | 
			
		||||
            throwable -> {
 | 
			
		||||
              throwable.printStackTrace();
 | 
			
		||||
              taskCompleted();
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
    return downloadPromise;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void stop() throws InterruptedException {
 | 
			
		||||
    stopLatch.await();
 | 
			
		||||
    executor.shutdownNow();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void taskCompleted() {
 | 
			
		||||
    stopLatch.countDown();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										193
									
								
								promise/src/main/java/com/iluwatar/promise/Promise.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								promise/src/main/java/com/iluwatar/promise/Promise.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,193 @@
 | 
			
		||||
/**
 | 
			
		||||
 * The MIT License
 | 
			
		||||
 * Copyright (c) 2014 Ilkka Seppälä
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in
 | 
			
		||||
 * all copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
			
		||||
 * THE SOFTWARE.
 | 
			
		||||
 */
 | 
			
		||||
package com.iluwatar.promise;
 | 
			
		||||
 | 
			
		||||
import java.util.concurrent.Callable;
 | 
			
		||||
import java.util.concurrent.ExecutionException;
 | 
			
		||||
import java.util.concurrent.Executor;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A Promise represents a proxy for a value not necessarily known when the promise is created. It
 | 
			
		||||
 * allows you to associate dependent promises to an asynchronous action's eventual success value or
 | 
			
		||||
 * failure reason. This lets asynchronous methods return values like synchronous methods: instead 
 | 
			
		||||
 * of the final value, the asynchronous method returns a promise of having a value at some point 
 | 
			
		||||
 * in the future.
 | 
			
		||||
 * 
 | 
			
		||||
 * @param <T> type of result.
 | 
			
		||||
 */
 | 
			
		||||
public class Promise<T> extends PromiseSupport<T> {
 | 
			
		||||
 | 
			
		||||
  private Runnable fulfillmentAction;
 | 
			
		||||
  private Consumer<? super Throwable> exceptionHandler;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Creates a promise that will be fulfilled in future.
 | 
			
		||||
   */
 | 
			
		||||
  public Promise() {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Fulfills the promise with the provided value.
 | 
			
		||||
   * @param value the fulfilled value that can be accessed using {@link #get()}.
 | 
			
		||||
   */
 | 
			
		||||
  @Override
 | 
			
		||||
  public void fulfill(T value) {
 | 
			
		||||
    super.fulfill(value);
 | 
			
		||||
    postFulfillment();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Fulfills the promise with exception due to error in execution.
 | 
			
		||||
   * @param exception the exception will be wrapped in {@link ExecutionException}
 | 
			
		||||
   *        when accessing the value using {@link #get()}.
 | 
			
		||||
   */
 | 
			
		||||
  @Override
 | 
			
		||||
  public void fulfillExceptionally(Exception exception) {
 | 
			
		||||
    super.fulfillExceptionally(exception);
 | 
			
		||||
    handleException(exception);
 | 
			
		||||
    postFulfillment();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void handleException(Exception exception) {
 | 
			
		||||
    if (exceptionHandler == null) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    exceptionHandler.accept(exception);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void postFulfillment() {
 | 
			
		||||
    if (fulfillmentAction == null) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    fulfillmentAction.run();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Executes the task using the executor in other thread and fulfills the promise returned
 | 
			
		||||
   * once the task completes either successfully or with an exception.
 | 
			
		||||
   * 
 | 
			
		||||
   * @param task the task that will provide the value to fulfill the promise.
 | 
			
		||||
   * @param executor the executor in which the task should be run.
 | 
			
		||||
   * @return a promise that represents the result of running the task provided.
 | 
			
		||||
   */
 | 
			
		||||
  public Promise<T> fulfillInAsync(final Callable<T> task, Executor executor) {
 | 
			
		||||
    executor.execute(() -> {
 | 
			
		||||
      try {
 | 
			
		||||
        fulfill(task.call());
 | 
			
		||||
      } catch (Exception ex) {
 | 
			
		||||
        fulfillExceptionally(ex);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns a new promise that, when this promise is fulfilled normally, is fulfilled with 
 | 
			
		||||
   * result of this promise as argument to the action provided.
 | 
			
		||||
   * @param action action to be executed.
 | 
			
		||||
   * @return a new promise.
 | 
			
		||||
   */
 | 
			
		||||
  public Promise<Void> thenAccept(Consumer<? super T> action) {
 | 
			
		||||
    Promise<Void> dest = new Promise<>();
 | 
			
		||||
    fulfillmentAction = new ConsumeAction(this, dest, action);
 | 
			
		||||
    return dest;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the exception handler on this promise.
 | 
			
		||||
   * @param exceptionHandler a consumer that will handle the exception occurred while fulfilling
 | 
			
		||||
   *            the promise.
 | 
			
		||||
   * @return this
 | 
			
		||||
   */
 | 
			
		||||
  public Promise<T> onError(Consumer<? super Throwable> exceptionHandler) {
 | 
			
		||||
    this.exceptionHandler = exceptionHandler;
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns a new promise that, when this promise is fulfilled normally, is fulfilled with 
 | 
			
		||||
   * result of this promise as argument to the function provided.
 | 
			
		||||
   * @param func function to be executed.
 | 
			
		||||
   * @return a new promise.
 | 
			
		||||
   */
 | 
			
		||||
  public <V> Promise<V> thenApply(Function<? super T, V> func) {
 | 
			
		||||
    Promise<V> dest = new Promise<>();
 | 
			
		||||
    fulfillmentAction = new TransformAction<V>(this, dest, func);
 | 
			
		||||
    return dest;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Accesses the value from source promise and calls the consumer, then fulfills the
 | 
			
		||||
   * destination promise.
 | 
			
		||||
   */
 | 
			
		||||
  private class ConsumeAction implements Runnable {
 | 
			
		||||
 | 
			
		||||
    private final Promise<T> src;
 | 
			
		||||
    private final Promise<Void> dest;
 | 
			
		||||
    private final Consumer<? super T> action;
 | 
			
		||||
 | 
			
		||||
    private ConsumeAction(Promise<T> src, Promise<Void> dest, Consumer<? super T> action) {
 | 
			
		||||
      this.src = src;
 | 
			
		||||
      this.dest = dest;
 | 
			
		||||
      this.action = action;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void run() {
 | 
			
		||||
      try {
 | 
			
		||||
        action.accept(src.get());
 | 
			
		||||
        dest.fulfill(null);
 | 
			
		||||
      } catch (Throwable throwable) {
 | 
			
		||||
        dest.fulfillExceptionally((Exception) throwable.getCause());
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Accesses the value from source promise, then fulfills the destination promise using the
 | 
			
		||||
   * transformed value. The source value is transformed using the transformation function.
 | 
			
		||||
   */
 | 
			
		||||
  private class TransformAction<V> implements Runnable {
 | 
			
		||||
 | 
			
		||||
    private final Promise<T> src;
 | 
			
		||||
    private final Promise<V> dest;
 | 
			
		||||
    private final Function<? super T, V> func;
 | 
			
		||||
 | 
			
		||||
    private TransformAction(Promise<T> src, Promise<V> dest, Function<? super T, V> func) {
 | 
			
		||||
      this.src = src;
 | 
			
		||||
      this.dest = dest;
 | 
			
		||||
      this.func = func;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void run() {
 | 
			
		||||
      try {
 | 
			
		||||
        dest.fulfill(func.apply(src.get()));
 | 
			
		||||
      } catch (Throwable throwable) {
 | 
			
		||||
        dest.fulfillExceptionally((Exception) throwable.getCause());
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										119
									
								
								promise/src/main/java/com/iluwatar/promise/PromiseSupport.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								promise/src/main/java/com/iluwatar/promise/PromiseSupport.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,119 @@
 | 
			
		||||
/**
 | 
			
		||||
 * The MIT License
 | 
			
		||||
 * Copyright (c) 2014 Ilkka Seppälä
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in
 | 
			
		||||
 * all copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
			
		||||
 * THE SOFTWARE.
 | 
			
		||||
 */
 | 
			
		||||
package com.iluwatar.promise;
 | 
			
		||||
 | 
			
		||||
import java.util.concurrent.ExecutionException;
 | 
			
		||||
import java.util.concurrent.Future;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
import java.util.concurrent.TimeoutException;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A really simplified implementation of future that allows completing it successfully with a value 
 | 
			
		||||
 * or exceptionally with an exception.
 | 
			
		||||
 */
 | 
			
		||||
class PromiseSupport<T> implements Future<T> {
 | 
			
		||||
 | 
			
		||||
  private static final int RUNNING = 1;
 | 
			
		||||
  private static final int FAILED = 2;
 | 
			
		||||
  private static final int COMPLETED = 3;
 | 
			
		||||
 | 
			
		||||
  private final Object lock;
 | 
			
		||||
 | 
			
		||||
  private volatile int state = RUNNING;
 | 
			
		||||
  private T value;
 | 
			
		||||
  private Exception exception;
 | 
			
		||||
 | 
			
		||||
  PromiseSupport() {
 | 
			
		||||
    this.lock = new Object();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void fulfill(T value) {
 | 
			
		||||
    this.value = value;
 | 
			
		||||
    this.state = COMPLETED;
 | 
			
		||||
    synchronized (lock) {
 | 
			
		||||
      lock.notifyAll();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void fulfillExceptionally(Exception exception) {
 | 
			
		||||
    this.exception = exception;
 | 
			
		||||
    this.state = FAILED;
 | 
			
		||||
    synchronized (lock) {
 | 
			
		||||
      lock.notifyAll();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public boolean cancel(boolean mayInterruptIfRunning) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public boolean isCancelled() {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public boolean isDone() {
 | 
			
		||||
    return state > RUNNING;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public T get() throws InterruptedException, ExecutionException {
 | 
			
		||||
    if (state == COMPLETED) {
 | 
			
		||||
      return value;
 | 
			
		||||
    } else if (state == FAILED) {
 | 
			
		||||
      throw new ExecutionException(exception);
 | 
			
		||||
    } else {
 | 
			
		||||
      synchronized (lock) {
 | 
			
		||||
        lock.wait();
 | 
			
		||||
        if (state == COMPLETED) {
 | 
			
		||||
          return value;
 | 
			
		||||
        } else {
 | 
			
		||||
          throw new ExecutionException(exception);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public T get(long timeout, TimeUnit unit)
 | 
			
		||||
      throws InterruptedException, ExecutionException, TimeoutException {
 | 
			
		||||
    if (state == COMPLETED) {
 | 
			
		||||
      return value;
 | 
			
		||||
    } else if (state == FAILED) {
 | 
			
		||||
      throw new ExecutionException(exception);
 | 
			
		||||
    } else {
 | 
			
		||||
      synchronized (lock) {
 | 
			
		||||
        lock.wait(unit.toMillis(timeout));
 | 
			
		||||
        if (state == COMPLETED) {
 | 
			
		||||
          return value;
 | 
			
		||||
        } else if (state == FAILED) {
 | 
			
		||||
          throw new ExecutionException(exception);
 | 
			
		||||
        } else {
 | 
			
		||||
          throw new TimeoutException();
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										101
									
								
								promise/src/main/java/com/iluwatar/promise/Utility.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								promise/src/main/java/com/iluwatar/promise/Utility.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,101 @@
 | 
			
		||||
package com.iluwatar.promise;
 | 
			
		||||
 | 
			
		||||
import java.io.BufferedReader;
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.FileReader;
 | 
			
		||||
import java.io.FileWriter;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.io.InputStreamReader;
 | 
			
		||||
import java.io.Reader;
 | 
			
		||||
import java.net.MalformedURLException;
 | 
			
		||||
import java.net.URL;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Iterator;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Map.Entry;
 | 
			
		||||
 | 
			
		||||
public class Utility {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Calculates character frequency of the file provided.
 | 
			
		||||
   * @param fileLocation location of the file.
 | 
			
		||||
   * @return a map of character to its frequency, an empty map if file does not exist.
 | 
			
		||||
   */
 | 
			
		||||
  public static Map<Character, Integer> characterFrequency(String fileLocation) {
 | 
			
		||||
    Map<Character, Integer> characterToFrequency = new HashMap<>();
 | 
			
		||||
    try (Reader reader = new FileReader(fileLocation);
 | 
			
		||||
        BufferedReader bufferedReader = new BufferedReader(reader)) {
 | 
			
		||||
      for (String line; (line = bufferedReader.readLine()) != null;) {
 | 
			
		||||
        for (char c : line.toCharArray()) {
 | 
			
		||||
          if (!characterToFrequency.containsKey(c)) {
 | 
			
		||||
            characterToFrequency.put(c, 1);
 | 
			
		||||
          } else {
 | 
			
		||||
            characterToFrequency.put(c, characterToFrequency.get(c) + 1);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    } catch (IOException ex) {
 | 
			
		||||
      ex.printStackTrace();
 | 
			
		||||
    }
 | 
			
		||||
    return characterToFrequency;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @return the character with lowest frequency if it exists, {@code Optional.empty()} otherwise.
 | 
			
		||||
   */
 | 
			
		||||
  public static Character lowestFrequencyChar(Map<Character, Integer> charFrequency) {
 | 
			
		||||
    Character lowestFrequencyChar = null;
 | 
			
		||||
    Iterator<Entry<Character, Integer>> iterator = charFrequency.entrySet().iterator();
 | 
			
		||||
    Entry<Character, Integer> entry = iterator.next();
 | 
			
		||||
    int minFrequency = entry.getValue();
 | 
			
		||||
    lowestFrequencyChar = entry.getKey();
 | 
			
		||||
 | 
			
		||||
    while (iterator.hasNext()) {
 | 
			
		||||
      entry = iterator.next();
 | 
			
		||||
      if (entry.getValue() < minFrequency) {
 | 
			
		||||
        minFrequency = entry.getValue();
 | 
			
		||||
        lowestFrequencyChar = entry.getKey();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return lowestFrequencyChar;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @return number of lines in the file at provided location. 0 if file does not exist.
 | 
			
		||||
   */
 | 
			
		||||
  public static Integer countLines(String fileLocation) {
 | 
			
		||||
    int lineCount = 0;
 | 
			
		||||
    try (Reader reader = new FileReader(fileLocation);
 | 
			
		||||
        BufferedReader bufferedReader = new BufferedReader(reader)) {
 | 
			
		||||
      while (bufferedReader.readLine() != null) {
 | 
			
		||||
        lineCount++;
 | 
			
		||||
      }
 | 
			
		||||
    } catch (IOException ex) {
 | 
			
		||||
      ex.printStackTrace();
 | 
			
		||||
    }
 | 
			
		||||
    return lineCount;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Downloads the contents from the given urlString, and stores it in a temporary directory.
 | 
			
		||||
   * @return the absolute path of the file downloaded.
 | 
			
		||||
   */
 | 
			
		||||
  public static String downloadFile(String urlString) throws MalformedURLException, IOException {
 | 
			
		||||
    System.out.println("Downloading contents from url: " + urlString);
 | 
			
		||||
    URL url = new URL(urlString);
 | 
			
		||||
    File file = File.createTempFile("promise_pattern", null);
 | 
			
		||||
    try (Reader reader = new InputStreamReader(url.openStream());
 | 
			
		||||
        BufferedReader bufferedReader = new BufferedReader(reader);
 | 
			
		||||
        FileWriter writer = new FileWriter(file)) {
 | 
			
		||||
      for (String line; (line = bufferedReader.readLine()) != null; ) {
 | 
			
		||||
        writer.write(line);
 | 
			
		||||
        writer.write("\n");
 | 
			
		||||
      }
 | 
			
		||||
      System.out.println("File downloaded at: " + file.getAbsolutePath());
 | 
			
		||||
      return file.getAbsolutePath();
 | 
			
		||||
    } catch (IOException ex) {
 | 
			
		||||
      throw ex;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										39
									
								
								promise/src/test/java/com/iluwatar/promise/AppTest.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								promise/src/test/java/com/iluwatar/promise/AppTest.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
/**
 | 
			
		||||
 * The MIT License
 | 
			
		||||
 * Copyright (c) 2014 Ilkka Seppälä
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in
 | 
			
		||||
 * all copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
			
		||||
 * THE SOFTWARE.
 | 
			
		||||
 */
 | 
			
		||||
package com.iluwatar.promise;
 | 
			
		||||
 | 
			
		||||
import java.util.concurrent.ExecutionException;
 | 
			
		||||
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 
 | 
			
		||||
 * Application test.
 | 
			
		||||
 */
 | 
			
		||||
public class AppTest {
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void testApp() throws InterruptedException, ExecutionException {
 | 
			
		||||
    App.main(null);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										263
									
								
								promise/src/test/java/com/iluwatar/promise/PromiseTest.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										263
									
								
								promise/src/test/java/com/iluwatar/promise/PromiseTest.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,263 @@
 | 
			
		||||
/**
 | 
			
		||||
 * The MIT License
 | 
			
		||||
 * Copyright (c) 2014 Ilkka Seppälä
 | 
			
		||||
 *
 | 
			
		||||
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 * of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 * in the Software without restriction, including without limitation the rights
 | 
			
		||||
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 * copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 * furnished to do so, subject to the following conditions:
 | 
			
		||||
 *
 | 
			
		||||
 * The above copyright notice and this permission notice shall be included in
 | 
			
		||||
 * all copies or substantial portions of the Software.
 | 
			
		||||
 *
 | 
			
		||||
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
			
		||||
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
			
		||||
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
			
		||||
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
			
		||||
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
			
		||||
 * THE SOFTWARE.
 | 
			
		||||
 */
 | 
			
		||||
package com.iluwatar.promise;
 | 
			
		||||
 | 
			
		||||
import static org.junit.Assert.assertEquals;
 | 
			
		||||
import static org.junit.Assert.assertFalse;
 | 
			
		||||
import static org.junit.Assert.assertTrue;
 | 
			
		||||
import static org.junit.Assert.fail;
 | 
			
		||||
import static org.mockito.Matchers.eq;
 | 
			
		||||
import static org.mockito.Mockito.mock;
 | 
			
		||||
import static org.mockito.Mockito.verify;
 | 
			
		||||
 | 
			
		||||
import java.util.concurrent.Callable;
 | 
			
		||||
import java.util.concurrent.ExecutionException;
 | 
			
		||||
import java.util.concurrent.Executor;
 | 
			
		||||
import java.util.concurrent.Executors;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
import java.util.concurrent.TimeoutException;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
 | 
			
		||||
import org.junit.Before;
 | 
			
		||||
import org.junit.Rule;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import org.junit.rules.ExpectedException;
 | 
			
		||||
/**
 | 
			
		||||
 * Tests Promise class.
 | 
			
		||||
 */
 | 
			
		||||
public class PromiseTest {
 | 
			
		||||
 | 
			
		||||
  private Executor executor;
 | 
			
		||||
  private Promise<Integer> promise;
 | 
			
		||||
  @Rule public ExpectedException exception = ExpectedException.none();
 | 
			
		||||
 | 
			
		||||
  @Before
 | 
			
		||||
  public void setUp() {
 | 
			
		||||
    executor = Executors.newSingleThreadExecutor();
 | 
			
		||||
    promise = new Promise<>();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void promiseIsFulfilledWithTheResultantValueOfExecutingTheTask() 
 | 
			
		||||
      throws InterruptedException, ExecutionException {
 | 
			
		||||
    promise.fulfillInAsync(new NumberCrunchingTask(), executor);
 | 
			
		||||
 | 
			
		||||
    assertEquals(NumberCrunchingTask.CRUNCHED_NUMBER, promise.get());
 | 
			
		||||
    assertTrue(promise.isDone());
 | 
			
		||||
    assertFalse(promise.isCancelled());
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  @Test
 | 
			
		||||
  public void promiseIsFulfilledWithAnExceptionIfTaskThrowsAnException() 
 | 
			
		||||
      throws InterruptedException, ExecutionException, TimeoutException {
 | 
			
		||||
    testWaitingForeverForPromiseToBeFulfilled();
 | 
			
		||||
    testWaitingSomeTimeForPromiseToBeFulfilled();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void testWaitingForeverForPromiseToBeFulfilled() 
 | 
			
		||||
      throws InterruptedException, TimeoutException {
 | 
			
		||||
    Promise<Integer> promise = new Promise<>();
 | 
			
		||||
    promise.fulfillInAsync(new Callable<Integer>() {
 | 
			
		||||
 | 
			
		||||
      @Override
 | 
			
		||||
      public Integer call() throws Exception {
 | 
			
		||||
        throw new RuntimeException("Barf!");
 | 
			
		||||
      }
 | 
			
		||||
    }, executor);
 | 
			
		||||
    
 | 
			
		||||
    try {
 | 
			
		||||
      promise.get();
 | 
			
		||||
      fail("Fetching promise should result in exception if the task threw an exception");
 | 
			
		||||
    } catch (ExecutionException ex) {
 | 
			
		||||
      assertTrue(promise.isDone());
 | 
			
		||||
      assertFalse(promise.isCancelled());
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    try {
 | 
			
		||||
      promise.get(1000, TimeUnit.SECONDS);
 | 
			
		||||
      fail("Fetching promise should result in exception if the task threw an exception");
 | 
			
		||||
    } catch (ExecutionException ex) {
 | 
			
		||||
      assertTrue(promise.isDone());
 | 
			
		||||
      assertFalse(promise.isCancelled());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  private void testWaitingSomeTimeForPromiseToBeFulfilled() 
 | 
			
		||||
      throws InterruptedException, TimeoutException {
 | 
			
		||||
    Promise<Integer> promise = new Promise<>();
 | 
			
		||||
    promise.fulfillInAsync(new Callable<Integer>() {
 | 
			
		||||
 | 
			
		||||
      @Override
 | 
			
		||||
      public Integer call() throws Exception {
 | 
			
		||||
        throw new RuntimeException("Barf!");
 | 
			
		||||
      }
 | 
			
		||||
    }, executor);
 | 
			
		||||
    
 | 
			
		||||
    try {
 | 
			
		||||
      promise.get(1000, TimeUnit.SECONDS);
 | 
			
		||||
      fail("Fetching promise should result in exception if the task threw an exception");
 | 
			
		||||
    } catch (ExecutionException ex) {
 | 
			
		||||
      assertTrue(promise.isDone());
 | 
			
		||||
      assertFalse(promise.isCancelled());
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    try {
 | 
			
		||||
      promise.get();
 | 
			
		||||
      fail("Fetching promise should result in exception if the task threw an exception");
 | 
			
		||||
    } catch (ExecutionException ex) {
 | 
			
		||||
      assertTrue(promise.isDone());
 | 
			
		||||
      assertFalse(promise.isCancelled());
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void dependentPromiseIsFulfilledAfterTheConsumerConsumesTheResultOfThisPromise() 
 | 
			
		||||
      throws InterruptedException, ExecutionException {
 | 
			
		||||
    Promise<Void> dependentPromise = promise
 | 
			
		||||
        .fulfillInAsync(new NumberCrunchingTask(), executor)
 | 
			
		||||
        .thenAccept(value -> {
 | 
			
		||||
          assertEquals(NumberCrunchingTask.CRUNCHED_NUMBER, value);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    dependentPromise.get();
 | 
			
		||||
    assertTrue(dependentPromise.isDone());
 | 
			
		||||
    assertFalse(dependentPromise.isCancelled());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void dependentPromiseIsFulfilledWithAnExceptionIfConsumerThrowsAnException() 
 | 
			
		||||
      throws InterruptedException, ExecutionException, TimeoutException {
 | 
			
		||||
    Promise<Void> dependentPromise = promise
 | 
			
		||||
        .fulfillInAsync(new NumberCrunchingTask(), executor)
 | 
			
		||||
        .thenAccept(new Consumer<Integer>() {
 | 
			
		||||
 | 
			
		||||
          @Override
 | 
			
		||||
          public void accept(Integer value) {
 | 
			
		||||
            throw new RuntimeException("Barf!");
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      dependentPromise.get();
 | 
			
		||||
      fail("Fetching dependent promise should result in exception "
 | 
			
		||||
          + "if the action threw an exception");
 | 
			
		||||
    } catch (ExecutionException ex) {
 | 
			
		||||
      assertTrue(promise.isDone());
 | 
			
		||||
      assertFalse(promise.isCancelled());
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    try {
 | 
			
		||||
      dependentPromise.get(1000, TimeUnit.SECONDS);
 | 
			
		||||
      fail("Fetching dependent promise should result in exception "
 | 
			
		||||
          + "if the action threw an exception");
 | 
			
		||||
    } catch (ExecutionException ex) {
 | 
			
		||||
      assertTrue(promise.isDone());
 | 
			
		||||
      assertFalse(promise.isCancelled());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void dependentPromiseIsFulfilledAfterTheFunctionTransformsTheResultOfThisPromise() 
 | 
			
		||||
      throws InterruptedException, ExecutionException {
 | 
			
		||||
    Promise<String> dependentPromise = promise
 | 
			
		||||
        .fulfillInAsync(new NumberCrunchingTask(), executor)
 | 
			
		||||
        .thenApply(value -> {
 | 
			
		||||
          assertEquals(NumberCrunchingTask.CRUNCHED_NUMBER, value); 
 | 
			
		||||
          return String.valueOf(value);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    assertEquals(String.valueOf(NumberCrunchingTask.CRUNCHED_NUMBER), dependentPromise.get());
 | 
			
		||||
    assertTrue(dependentPromise.isDone());
 | 
			
		||||
    assertFalse(dependentPromise.isCancelled());
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  @Test
 | 
			
		||||
  public void dependentPromiseIsFulfilledWithAnExceptionIfTheFunctionThrowsException() 
 | 
			
		||||
      throws InterruptedException, ExecutionException, TimeoutException {
 | 
			
		||||
    Promise<String> dependentPromise = promise
 | 
			
		||||
        .fulfillInAsync(new NumberCrunchingTask(), executor)
 | 
			
		||||
        .thenApply(new Function<Integer, String>() {
 | 
			
		||||
 | 
			
		||||
          @Override
 | 
			
		||||
          public String apply(Integer value) {
 | 
			
		||||
            throw new RuntimeException("Barf!");
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      dependentPromise.get();
 | 
			
		||||
      fail("Fetching dependent promise should result in exception "
 | 
			
		||||
          + "if the function threw an exception");
 | 
			
		||||
    } catch (ExecutionException ex) {
 | 
			
		||||
      assertTrue(promise.isDone());
 | 
			
		||||
      assertFalse(promise.isCancelled());
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    try {
 | 
			
		||||
      dependentPromise.get(1000, TimeUnit.SECONDS);
 | 
			
		||||
      fail("Fetching dependent promise should result in exception "
 | 
			
		||||
          + "if the function threw an exception");
 | 
			
		||||
    } catch (ExecutionException ex) {
 | 
			
		||||
      assertTrue(promise.isDone());
 | 
			
		||||
      assertFalse(promise.isCancelled());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  @Test
 | 
			
		||||
  public void fetchingAnAlreadyFulfilledPromiseReturnsTheFulfilledValueImmediately() 
 | 
			
		||||
      throws InterruptedException, ExecutionException, TimeoutException {
 | 
			
		||||
    Promise<Integer> promise = new Promise<>();
 | 
			
		||||
    promise.fulfill(NumberCrunchingTask.CRUNCHED_NUMBER);
 | 
			
		||||
    
 | 
			
		||||
    promise.get(1000, TimeUnit.SECONDS);
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  @SuppressWarnings("unchecked")
 | 
			
		||||
  @Test
 | 
			
		||||
  public void exceptionHandlerIsCalledWhenPromiseIsFulfilledExceptionally() {
 | 
			
		||||
    Promise<Object> promise = new Promise<>();
 | 
			
		||||
    Consumer<Throwable> exceptionHandler = mock(Consumer.class);
 | 
			
		||||
    promise.onError(exceptionHandler);
 | 
			
		||||
    
 | 
			
		||||
    Exception exception = new Exception("barf!");
 | 
			
		||||
    promise.fulfillExceptionally(exception);
 | 
			
		||||
    
 | 
			
		||||
    verify(exceptionHandler).accept(eq(exception));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static class NumberCrunchingTask implements Callable<Integer> {
 | 
			
		||||
 | 
			
		||||
    private static final Integer CRUNCHED_NUMBER = Integer.MAX_VALUE;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Integer call() throws Exception {
 | 
			
		||||
      // Do number crunching
 | 
			
		||||
      Thread.sleep(100);
 | 
			
		||||
      return CRUNCHED_NUMBER;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user