Skip to content

Commit

Permalink
Merge pull request #50 from aNNiMON/safe-functions
Browse files Browse the repository at this point in the history
Safe functions
  • Loading branch information
aNNiMON committed Jun 23, 2016
2 parents 09e3947 + 823e520 commit fbb39e0
Show file tree
Hide file tree
Showing 11 changed files with 345 additions and 6 deletions.
45 changes: 45 additions & 0 deletions stream/src/main/java/com/annimon/stream/function/Consumer.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.annimon.stream.function;

import com.annimon.stream.Objects;

/**
* Represents an operation on input argument.
*
Expand Down Expand Up @@ -40,5 +42,48 @@ public void accept(T value) {
}
};
}

/**
* Creates a safe {@code Consumer}.
*
* @param <T> the type of the input to the function
* @param throwableConsumer the consumer that may throw an exception
* @return a {@code Consumer}
* @throws NullPointerException if {@code throwableConsumer} is null
* @see #safe(com.annimon.stream.function.ThrowableConsumer, com.annimon.stream.function.Consumer)
*/
public static <T> Consumer<T> safe(ThrowableConsumer<? super T, Throwable> throwableConsumer) {
return safe(throwableConsumer, null);
}

/**
* Creates a safe {@code Consumer}.
*
* @param <T> the type of the input to the function
* @param throwableConsumer the consumer that may throw an exception
* @param onFailedConsumer the consumer which applies if exception was thrown
* @return a {@code Consumer}
* @throws NullPointerException if {@code throwableConsumer} is null
* @see #safe(com.annimon.stream.function.ThrowableConsumer)
*/
public static <T> Consumer<T> safe(
final ThrowableConsumer<? super T, Throwable> throwableConsumer,
final Consumer<? super T> onFailedConsumer) {
return new Consumer<T>() {

@Override
public void accept(T value) {
Objects.requireNonNull(throwableConsumer);
try {
throwableConsumer.accept(value);
} catch (Throwable ex) {
if (onFailedConsumer != null) {
onFailedConsumer.accept(value);
}
}
}
};
}

}
}
43 changes: 43 additions & 0 deletions stream/src/main/java/com/annimon/stream/function/Function.java
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,48 @@ public V apply(T t) {
}
};
}

/**
* Creates a safe {@code Function},
*
* @param <T> the type of the input of the function
* @param <R> the type of the result of the function
* @param throwableFunction the function that may throw an exception
* @return the function result or {@code null} if exception was thrown
* @throws NullPointerException if {@code throwableFunction} is null
* @see #safe(com.annimon.stream.function.ThrowableFunction, java.lang.Object)
*/
public static <T, R> Function<T, R> safe(
ThrowableFunction<? super T, ? extends R, Throwable> throwableFunction) {
return Util.<T, R>safe(throwableFunction, null);
}

/**
* Creates a safe {@code Function},
*
* @param <T> the type of the input of the function
* @param <R> the type of the result of the function
* @param throwableFunction the function that may throw an exception
* @param resultIfFailed the result which returned if exception was thrown
* @return the function result or {@code resultIfFailed}
* @throws NullPointerException if {@code throwableFunction} is null
* @see #safe(com.annimon.stream.function.ThrowableFunction)
*/
public static <T, R> Function<T, R> safe(
final ThrowableFunction<? super T, ? extends R, Throwable> throwableFunction,
final R resultIfFailed) {
return new Function<T, R>() {

@Override
public R apply(T value) {
try {
return throwableFunction.apply(value);
} catch (Throwable throwable) {
return resultIfFailed;
}
}
};
}

}
}
37 changes: 37 additions & 0 deletions stream/src/main/java/com/annimon/stream/function/Predicate.java
Original file line number Diff line number Diff line change
Expand Up @@ -90,5 +90,42 @@ public boolean test(T value) {
}
};
}

/**
* Creates a safe {@code Predicate}.
*
* @param <T> the type of the input to the function
* @param throwablePredicate the predicate that may throw an exception
* @return a {@code Predicate} or {@code false} if exception was thrown
*/
public static <T> Predicate<T> safe(ThrowablePredicate<? super T, Throwable> throwablePredicate) {
return safe(throwablePredicate, false);
}

/**
* Creates a safe {@code Predicate}.
*
* @param <T> the type of the input to the function
* @param throwablePredicate the predicate that may throw an exception
* @param resultIfFailed the result which returned if exception was thrown
* @return a {@code Predicate} or {@code resultIfFailed}
* @throws NullPointerException if {@code throwablePredicate} is null
*/
public static <T> Predicate<T> safe(
final ThrowablePredicate<? super T, Throwable> throwablePredicate,
final boolean resultIfFailed) {
return new Predicate<T>() {

@Override
public boolean test(T value) {
try {
return throwablePredicate.test(value);
} catch (Throwable throwable) {
return resultIfFailed;
}
}
};
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.annimon.stream.function;

/**
* Represents an operation on input argument and can throw an exception.
*
* @param <T> the type of the input to the operation
* @param <E> the type of the exception
* @see Consumer
*/
@FunctionalInterface
public interface ThrowableConsumer<T, E extends Throwable> {

/**
* Performs operation on argument.
*
* @param value the input argument
* @throws E an exception
*/
void accept(T value) throws E;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.annimon.stream.function;

/**
* Represents a predicate, i.e. function with boolean type result which can throw an exception.
*
* @param <T> the type of the input to the function
* @param <E> the type of the exception
*/
@FunctionalInterface
public interface ThrowablePredicate<T, E extends Throwable> {

/**
* Tests the value for satisfying predicate.
*
* @param value the value to be tests
* @return {@code true} if the value matches the predicate, otherwise {@code false}
* @throws E an exception
*/
boolean test(T value) throws E;
}
10 changes: 6 additions & 4 deletions stream/src/test/java/com/annimon/stream/RatingsTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.annimon.stream;

import com.annimon.stream.function.Function;
import com.annimon.stream.function.ThrowableFunction;
import java.io.IOException;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -53,12 +55,12 @@ public static void setUpData() {
public void testRatings() {
String ratings = Stream.of(fileContents.keySet()) // list files
// read content of files
.flatMap(new Function<String, Stream<String>>() {
.flatMap(Function.Util.safe(new ThrowableFunction<String, Stream<String>, Throwable>() {
@Override
public Stream<String> apply(String filename) {
public Stream<String> apply(String filename) throws IOException {
return readLines(filename);
}
})
}))
// split line by whitespaces
.map(new Function<String, String[]>() {
@Override
Expand Down Expand Up @@ -136,7 +138,7 @@ public String apply(Map.Entry<String, Integer> value) {
/*
* Emulates read lines from file
*/
private static Stream<String> readLines(String filename) {
private static Stream<String> readLines(String filename) throws IOException {
return Stream.of(fileContents.get(filename).split("\n"));
}
}
39 changes: 39 additions & 0 deletions stream/src/test/java/com/annimon/stream/function/ConsumerTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.annimon.stream.function;

import static com.annimon.stream.test.CommonMatcher.hasOnlyPrivateConstructors;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import static org.junit.Assert.*;
import org.junit.Test;

Expand Down Expand Up @@ -36,6 +38,35 @@ public void testAndThen() {
consumer.accept(holder);
assertEquals(46, holder.value);
}

@Test
public void testSafe() {
Consumer<OutputStream> consumer = Consumer.Util.safe(writer);

ByteArrayOutputStream baos = new ByteArrayOutputStream(5);
consumer.accept(baos);
consumer.accept(null);
consumer.accept(null);
consumer.accept(baos);
assertEquals(">>", baos.toString());
}

@Test
public void testSafeWithOnFailedConsumer() {
final ByteArrayOutputStream baos = new ByteArrayOutputStream(5);
Consumer<OutputStream> consumer = Consumer.Util.safe(writer, new Consumer<OutputStream>() {
@Override
public void accept(OutputStream os) {
baos.write('<');
}
});

consumer.accept(baos);
consumer.accept(baos);
consumer.accept(null);
consumer.accept(null);
assertEquals(">><<", baos.toString());
}

@Test
public void testPrivateConstructor() throws Exception {
Expand All @@ -55,6 +86,14 @@ public void accept(IntHolder holder) {
holder.value *= 2;
}
};

private static final ThrowableConsumer<OutputStream, Throwable> writer
= new ThrowableConsumer<OutputStream, Throwable>() {
@Override
public void accept(OutputStream os) throws Throwable {
os.write('>');
}
};

class IntHolder {
int value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.annimon.stream.Functions;
import static com.annimon.stream.test.CommonMatcher.hasOnlyPrivateConstructors;
import java.io.IOException;
import java.util.Locale;
import static org.junit.Assert.*;
import org.junit.Test;
Expand Down Expand Up @@ -41,11 +42,46 @@ public void testCompose() {
assertEquals("A", function.apply((char)65));
}

@Test
public void testSafe() {
Function<Boolean, Integer> function = Function.Util.safe(new ThrowableFunction<Boolean, Integer, Throwable>() {

@Override
public Integer apply(Boolean value) throws IOException {
return unsafeFunction(value);
}
});

assertEquals(10, (int) function.apply(false));
assertEquals(null, function.apply(true));
}

@Test
public void testSafeWithResultIfFailed() {
Function<Object, String> function = Function.Util.safe(new ThrowableFunction<Object, String, Throwable>() {

@Override
public String apply(Object value) {
return value.toString();
}
}, "default");

assertEquals("10", function.apply(10));
assertEquals("default", function.apply(null));
}

@Test
public void testPrivateConstructor() throws Exception {
assertThat(Function.Util.class, hasOnlyPrivateConstructors());
}

private static int unsafeFunction(boolean throwException) throws IOException {
if (throwException) {
throw new IOException();
}
return 10;
}

private static final Function<Character, String> toString = Functions.<Character>convertToString();

private static final Function<String, String> toUpperCase = new Function<String, String>() {
Expand All @@ -54,5 +90,4 @@ public String apply(String value) {
return value.toUpperCase(Locale.ENGLISH);
}
};

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.annimon.stream.Functions;
import static com.annimon.stream.test.CommonMatcher.hasOnlyPrivateConstructors;
import java.io.IOException;
import static org.junit.Assert.*;
import org.junit.Test;

Expand Down Expand Up @@ -60,6 +61,24 @@ public void testNegatePredicate() {
assertTrue(isOdd.test(55));
assertFalse(isOdd.test(56));
}

@Test
public void testSafe() {
Predicate<Integer> predicate = Predicate.Util.safe(throwablePredicate);

assertTrue(predicate.test(40));
assertFalse(predicate.test(60));
assertFalse(predicate.test(-5));
}

@Test
public void testSafeWithResultIfFailed() {
Predicate<Integer> predicate = Predicate.Util.safe(throwablePredicate, true);

assertTrue(predicate.test(40));
assertFalse(predicate.test(60));
assertTrue(predicate.test(-5));
}

@Test
public void testPrivateConstructor() throws Exception {
Expand All @@ -74,5 +93,16 @@ public boolean test(Integer value) {
};

private static final Predicate<Integer> isEven = Functions.remainder(2);


private static final ThrowablePredicate<Integer, Throwable> throwablePredicate =
new ThrowablePredicate<Integer, Throwable>() {

@Override
public boolean test(Integer value) throws Throwable {
if (value < 0) {
throw new IOException();
}
return value < 50;
}
};
}
Loading

0 comments on commit fbb39e0

Please sign in to comment.