Skip to content

Commit 483c61a

Browse files
committed
Some refactoring, added javadocs
1 parent 95cf9fe commit 483c61a

File tree

4 files changed

+171
-90
lines changed

4 files changed

+171
-90
lines changed

promise/src/main/java/com/iluwatar/promise/App.java

Lines changed: 82 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -29,35 +29,45 @@
2929
import java.util.concurrent.Executors;
3030

3131
/**
32-
*
33-
* <p>The Promise object is used for asynchronous computations. A Promise represents an operation that
34-
* hasn't completed yet, but is expected in the future.
35-
*
36-
* <p>A Promise represents a proxy for a value not necessarily known when the promise is created. It
37-
* allows you to associate dependent promises to an asynchronous action's eventual success value or
38-
* failure reason. This lets asynchronous methods return values like synchronous methods: instead of the final
39-
* value, the asynchronous method returns a promise of having a value at some point in the future.
40-
*
32+
*
33+
* The Promise object is used for asynchronous computations. A Promise represents an operation
34+
* that hasn't completed yet, but is expected in the future.
35+
*
36+
* <p>A Promise represents a proxy for a value not necessarily known when the promise is created. It
37+
* allows you to associate dependent promises to an asynchronous action's eventual success value or
38+
* failure reason. This lets asynchronous methods return values like synchronous methods: instead
39+
* of the final value, the asynchronous method returns a promise of having a value at some point
40+
* in the future.
41+
*
4142
* <p>Promises provide a few advantages over callback objects:
4243
* <ul>
4344
* <li> Functional composition and error handling
4445
* <li> Prevents callback hell and provides callback aggregation
4546
* </ul>
46-
*
47+
*
4748
* <p>
49+
* In this application the usage of promise is demonstrated with two examples:
50+
* <ul>
51+
* <li>Count Lines: In this example a file is downloaded and its line count is calculated.
52+
* The calculated line count is then consumed and printed on console.
53+
* <li>Lowest Character Frequency: In this example a file is downloaded and its lowest frequency
54+
* character is found and printed on console. This happens via a chain of promises, we start with
55+
* a file download promise, then a promise of character frequency, then a promise of lowest frequency
56+
* character which is finally consumed and result is printed on console.
57+
* </ul>
4858
*
4959
* @see CompletableFuture
5060
*/
5161
public class App {
5262

53-
private static final String URL = "https://raw.githubusercontent.com/iluwatar/java-design-patterns/Promise/promise/README.md";
63+
private static final String DEFAULT_URL = "https://raw.githubusercontent.com/iluwatar/java-design-patterns/Promise/promise/README.md";
5464
private ExecutorService executor;
55-
private CountDownLatch canStop = new CountDownLatch(2);
56-
65+
private CountDownLatch stopLatch = new CountDownLatch(2);
66+
5767
private App() {
5868
executor = Executors.newFixedThreadPool(2);
5969
}
60-
70+
6171
/**
6272
* Program entry point
6373
* @param args arguments
@@ -67,78 +77,99 @@ private App() {
6777
public static void main(String[] args) throws InterruptedException, ExecutionException {
6878
App app = new App();
6979
try {
70-
app.run();
80+
app.promiseUsage();
7181
} finally {
7282
app.stop();
7383
}
7484
}
7585

76-
private void run() throws InterruptedException, ExecutionException {
77-
promiseUsage();
86+
private void promiseUsage() {
87+
calculateLineCount();
88+
89+
calculateLowestFrequencyChar();
7890
}
7991

80-
private void promiseUsage() {
81-
82-
countLines()
83-
.then(
84-
count -> {
85-
System.out.println("Line count is: " + count);
92+
/*
93+
* Calculate the lowest frequency character and when that promise is fulfilled,
94+
* consume the result in a Consumer<Character>
95+
*/
96+
private void calculateLowestFrequencyChar() {
97+
lowestFrequencyChar()
98+
.thenAccept(
99+
charFrequency -> {
100+
System.out.println("Char with lowest frequency is: " + charFrequency);
86101
taskCompleted();
87102
}
88103
);
89-
90-
lowestCharFrequency()
91-
.then(
92-
charFrequency -> {
93-
System.out.println("Char with lowest frequency is: " + charFrequency);
104+
}
105+
106+
/*
107+
* Calculate the line count and when that promise is fulfilled, consume the result
108+
* in a Consumer<Integer>
109+
*/
110+
private void calculateLineCount() {
111+
countLines()
112+
.thenAccept(
113+
count -> {
114+
System.out.println("Line count is: " + count);
94115
taskCompleted();
95116
}
96117
);
97118
}
98119

99-
private Promise<Character> lowestCharFrequency() {
120+
/*
121+
* Calculate the character frequency of a file and when that promise is fulfilled,
122+
* then promise to apply function to calculate lowest character frequency.
123+
*/
124+
private Promise<Character> lowestFrequencyChar() {
100125
return characterFrequency()
101-
.then(
102-
charFrequency -> {
103-
return Utility.lowestFrequencyChar(charFrequency).orElse(null);
104-
}
105-
);
126+
.thenApply(Utility::lowestFrequencyChar);
106127
}
107128

129+
/*
130+
* Download the file at DEFAULT_URL and when that promise is fulfilled,
131+
* then promise to apply function to calculate character frequency.
132+
*/
108133
private Promise<Map<Character, Integer>> characterFrequency() {
109-
return download(URL)
110-
.then(
111-
fileLocation -> {
112-
return Utility.characterFrequency(fileLocation);
113-
}
114-
);
134+
return download(DEFAULT_URL)
135+
.thenApply(Utility::characterFrequency);
115136
}
116137

138+
/*
139+
* Download the file at DEFAULT_URL and when that promise is fulfilled,
140+
* then promise to apply function to count lines in that file.
141+
*/
117142
private Promise<Integer> countLines() {
118-
return download(URL)
119-
.then(
120-
fileLocation -> {
121-
return Utility.countLines(fileLocation);
122-
}
123-
);
143+
return download(DEFAULT_URL)
144+
.thenApply(Utility::countLines);
124145
}
125146

147+
/*
148+
* Return a promise to provide the local absolute path of the file downloaded in background.
149+
* This is an async method and does not wait until the file is downloaded.
150+
*/
126151
private Promise<String> download(String urlString) {
127152
Promise<String> downloadPromise = new Promise<String>()
128153
.fulfillInAsync(
129154
() -> {
130155
return Utility.downloadFile(urlString);
131-
}, executor);
132-
156+
}, executor)
157+
.onError(
158+
throwable -> {
159+
throwable.printStackTrace();
160+
taskCompleted();
161+
}
162+
);
163+
133164
return downloadPromise;
134165
}
135166

136167
private void stop() throws InterruptedException {
137-
canStop.await();
168+
stopLatch.await();
138169
executor.shutdownNow();
139170
}
140-
171+
141172
private void taskCompleted() {
142-
canStop.countDown();
173+
stopLatch.countDown();
143174
}
144175
}

promise/src/main/java/com/iluwatar/promise/Promise.java

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
public class Promise<T> extends PromiseSupport<T> {
3737

3838
private Runnable fulfillmentAction;
39+
private Consumer<? super Throwable> exceptionHandler;
3940

4041
/**
4142
* Creates a promise that will be fulfilled in future.
@@ -61,9 +62,17 @@ public void fulfill(T value) {
6162
@Override
6263
public void fulfillExceptionally(Exception exception) {
6364
super.fulfillExceptionally(exception);
65+
handleException(exception);
6466
postFulfillment();
6567
}
6668

69+
private void handleException(Exception exception) {
70+
if (exceptionHandler == null) {
71+
return;
72+
}
73+
exceptionHandler.accept(exception);
74+
}
75+
6776
private void postFulfillment() {
6877
if (fulfillmentAction == null) {
6978
return;
@@ -83,8 +92,8 @@ public Promise<T> fulfillInAsync(final Callable<T> task, Executor executor) {
8392
executor.execute(() -> {
8493
try {
8594
fulfill(task.call());
86-
} catch (Exception e) {
87-
fulfillExceptionally(e);
95+
} catch (Exception ex) {
96+
fulfillExceptionally(ex);
8897
}
8998
});
9099
return this;
@@ -96,19 +105,30 @@ public Promise<T> fulfillInAsync(final Callable<T> task, Executor executor) {
96105
* @param action action to be executed.
97106
* @return a new promise.
98107
*/
99-
public Promise<Void> then(Consumer<? super T> action) {
108+
public Promise<Void> thenAccept(Consumer<? super T> action) {
100109
Promise<Void> dest = new Promise<>();
101110
fulfillmentAction = new ConsumeAction(this, dest, action);
102111
return dest;
103112
}
113+
114+
/**
115+
* Set the exception handler on this promise.
116+
* @param exceptionHandler a consumer that will handle the exception occurred while fulfilling
117+
* the promise.
118+
* @return this
119+
*/
120+
public Promise<T> onError(Consumer<? super Throwable> exceptionHandler) {
121+
this.exceptionHandler = exceptionHandler;
122+
return this;
123+
}
104124

105125
/**
106126
* Returns a new promise that, when this promise is fulfilled normally, is fulfilled with
107127
* result of this promise as argument to the function provided.
108128
* @param func function to be executed.
109129
* @return a new promise.
110130
*/
111-
public <V> Promise<V> then(Function<? super T, V> func) {
131+
public <V> Promise<V> thenApply(Function<? super T, V> func) {
112132
Promise<V> dest = new Promise<>();
113133
fulfillmentAction = new TransformAction<V>(this, dest, func);
114134
return dest;
@@ -135,8 +155,8 @@ public void run() {
135155
try {
136156
action.accept(src.get());
137157
dest.fulfill(null);
138-
} catch (Throwable e) {
139-
dest.fulfillExceptionally((Exception) e.getCause());
158+
} catch (Throwable throwable) {
159+
dest.fulfillExceptionally((Exception) throwable.getCause());
140160
}
141161
}
142162
}
@@ -162,8 +182,8 @@ public void run() {
162182
try {
163183
V result = func.apply(src.get());
164184
dest.fulfill(result);
165-
} catch (Throwable e) {
166-
dest.fulfillExceptionally((Exception) e.getCause());
185+
} catch (Throwable throwable) {
186+
dest.fulfillExceptionally((Exception) throwable.getCause());
167187
}
168188
}
169189
}

promise/src/main/java/com/iluwatar/promise/Utility.java

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,19 @@
1212
import java.util.HashMap;
1313
import java.util.Iterator;
1414
import java.util.Map;
15-
import java.util.Optional;
1615
import java.util.Map.Entry;
1716

1817
public class Utility {
1918

19+
/**
20+
* Calculates character frequency of the file provided.
21+
* @param fileLocation location of the file.
22+
* @return a map of character to its frequency, an empty map if file does not exist.
23+
*/
2024
public static Map<Character, Integer> characterFrequency(String fileLocation) {
2125
Map<Character, Integer> characterToFrequency = new HashMap<>();
22-
try (Reader reader = new FileReader(fileLocation);
23-
BufferedReader bufferedReader = new BufferedReader(reader);) {
26+
try (Reader reader = new FileReader(fileLocation);
27+
BufferedReader bufferedReader = new BufferedReader(reader)) {
2428
for (String line; (line = bufferedReader.readLine()) != null;) {
2529
for (char c : line.toCharArray()) {
2630
if (!characterToFrequency.containsKey(c)) {
@@ -35,33 +39,35 @@ public static Map<Character, Integer> characterFrequency(String fileLocation) {
3539
}
3640
return characterToFrequency;
3741
}
38-
39-
public static Optional<Character> lowestFrequencyChar(Map<Character, Integer> charFrequency) {
40-
Optional<Character> lowestFrequencyChar = Optional.empty();
41-
if (charFrequency.isEmpty()) {
42-
return lowestFrequencyChar;
43-
}
44-
42+
43+
/**
44+
* @return the character with lowest frequency if it exists, {@code Optional.empty()} otherwise.
45+
*/
46+
public static Character lowestFrequencyChar(Map<Character, Integer> charFrequency) {
47+
Character lowestFrequencyChar = null;
4548
Iterator<Entry<Character, Integer>> iterator = charFrequency.entrySet().iterator();
4649
Entry<Character, Integer> entry = iterator.next();
4750
int minFrequency = entry.getValue();
48-
lowestFrequencyChar = Optional.of(entry.getKey());
49-
51+
lowestFrequencyChar = entry.getKey();
52+
5053
while (iterator.hasNext()) {
5154
entry = iterator.next();
5255
if (entry.getValue() < minFrequency) {
5356
minFrequency = entry.getValue();
54-
lowestFrequencyChar = Optional.of(entry.getKey());
57+
lowestFrequencyChar = entry.getKey();
5558
}
5659
}
57-
60+
5861
return lowestFrequencyChar;
5962
}
60-
63+
64+
/**
65+
* @return number of lines in the file at provided location. 0 if file does not exist.
66+
*/
6167
public static Integer countLines(String fileLocation) {
6268
int lineCount = 0;
63-
try (Reader reader = new FileReader(fileLocation);
64-
BufferedReader bufferedReader = new BufferedReader(reader);) {
69+
try (Reader reader = new FileReader(fileLocation);
70+
BufferedReader bufferedReader = new BufferedReader(reader)) {
6571
while (bufferedReader.readLine() != null) {
6672
lineCount++;
6773
}
@@ -71,11 +77,15 @@ public static Integer countLines(String fileLocation) {
7177
return lineCount;
7278
}
7379

80+
/**
81+
* Downloads the contents from the given urlString, and stores it in a temporary directory.
82+
* @return the absolute path of the file downloaded.
83+
*/
7484
public static String downloadFile(String urlString) throws MalformedURLException, IOException {
7585
System.out.println("Downloading contents from url: " + urlString);
7686
URL url = new URL(urlString);
7787
File file = File.createTempFile("promise_pattern", null);
78-
try (Reader reader = new InputStreamReader(url.openStream());
88+
try (Reader reader = new InputStreamReader(url.openStream());
7989
BufferedReader bufferedReader = new BufferedReader(reader);
8090
FileWriter writer = new FileWriter(file)) {
8191
for (String line; (line = bufferedReader.readLine()) != null; ) {

0 commit comments

Comments
 (0)