diff --git a/src/changes/changes.xml b/src/changes/changes.xml index b1e02e609..5a1a4286b 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -46,6 +46,7 @@ The type attribute can be add,update,fix,remove. + Adding the Functions class. Update to JUnit 5 Add @FunctionalInterface to ThreadPredicate and ThreadGroupPredicate Update Java Language requirement to 1.8 diff --git a/src/main/java/org/apache/commons/lang3/Functions.java b/src/main/java/org/apache/commons/lang3/Functions.java new file mode 100644 index 000000000..9b6168fd6 --- /dev/null +++ b/src/main/java/org/apache/commons/lang3/Functions.java @@ -0,0 +1,144 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.lang3; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.lang.reflect.Method; +import java.lang.reflect.UndeclaredThrowableException; +import java.util.ArrayList; + +import org.apache.commons.lang3.exception.ExceptionUtils; + +/** This class provides utility functions, and classes for working with the + * {@code java.util.function} package, or more generally, with Java 8 + * lambdas. + * More specifically, it attempts to address the fact that lambdas are supposed + * not to throw Exceptions, at least not checked Exceptions, aka instances of + * {@link Exception}. This enforces the use of constructs like + *
+ *   Consumer consumer = (m) -> {
+ *       try {
+ *           m.invoke(o, args);
+ *       } catch (Throwable t) {
+ *           throw Functions.rethrow(t);
+ *       }
+ *   };
+ * 
+ * By replacing a {@link Consumer Consumer} with a + * {@link FailableConsumer FailableConsumer + * Functions.accept((m) -> m.invoke(o,args)); + * + * Obviously, the second version is much more concise and the spirit of + * Lambda expressions is met better than the second version. + */ +public class Functions { + @FunctionalInterface + public interface FailableRunnable { + public void run() throws T; + } + @FunctionalInterface + public interface FailableCallable { + public O call() throws T; + } + @FunctionalInterface + public interface FailableConsumer { + public void accept(O pObject) throws T; + } + @FunctionalInterface + public interface FailableBiConsumer { + public void accept(O1 pObject1, O2 pObject2) throws T; + } + @FunctionalInterface + public interface FailableFunction { + public O apply(I pInput) throws T; + } + @FunctionalInterface + public interface FailableBiFunction { + public O apply(I1 pInput1, I2 pInput2) throws T; + } + + public static void run(FailableRunnable pRunnable) { + try { + pRunnable.run(); + } catch (Throwable t) { + throw rethrow(t); + } + } + + public static O call(FailableCallable pCallable) { + try { + return pCallable.call(); + } catch (Throwable t) { + throw rethrow(t); + } + } + + public static void accept(FailableConsumer pConsumer, O pObject) { + try { + pConsumer.accept(pObject); + } catch (Throwable t) { + throw rethrow(t); + } + } + + public static void accept(FailableBiConsumer pConsumer, O1 pObject1, O2 pObject2) { + try { + pConsumer.accept(pObject1, pObject2); + } catch (Throwable t) { + throw rethrow(t); + } + } + + public static O apply(FailableFunction pFunction, I pInput) { + try { + return pFunction.apply(pInput); + } catch (Throwable t) { + throw rethrow(t); + } + } + + public static O apply(FailableBiFunction pFunction, I1 pInput1, I2 pInput2) { + try { + return pFunction.apply(pInput1, pInput2); + } catch (Throwable t) { + throw rethrow(t); + } + } + + public static RuntimeException rethrow(Throwable pThrowable) { + if (pThrowable == null) { + throw new NullPointerException("The Throwable must not be null."); + } else { + if (pThrowable instanceof RuntimeException) { + throw (RuntimeException) pThrowable; + } else if (pThrowable instanceof Error) { + throw (Error) pThrowable; + } else if (pThrowable instanceof IOException) { + throw new UncheckedIOException((IOException) pThrowable); + } else { + throw new UndeclaredThrowableException(pThrowable); + } + } + } + + public static void test() throws Exception { + run(() -> ArrayList.class.newInstance()); + } +} diff --git a/src/test/java/org/apache/commons/lang3/FunctionsTest.java b/src/test/java/org/apache/commons/lang3/FunctionsTest.java new file mode 100644 index 000000000..4f62c5327 --- /dev/null +++ b/src/test/java/org/apache/commons/lang3/FunctionsTest.java @@ -0,0 +1,232 @@ +package org.apache.commons.lang3; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.lang.reflect.UndeclaredThrowableException; + +import org.junit.jupiter.api.Test; + +class FunctionsTest { + public static class SomeException extends Exception { + private static final long serialVersionUID = -4965704778119283411L; + + private Throwable t; + + public SomeException(String pMsg) { + super(pMsg); + } + + public void setThrowable(Throwable pThrowable) { + t = pThrowable; + } + + public void test() throws Throwable { + if (t != null) { + throw t; + } + } + } + public static class Testable { + private Throwable t; + + public Testable(Throwable pTh) { + t = pTh; + } + + public void setThrowable(Throwable pThrowable) { + t = pThrowable; + } + + public void test() throws Throwable { + test(t); + } + + public void test(Throwable pThrowable) throws Throwable { + if (pThrowable != null) { + throw pThrowable; + } + } + + public Integer testInt() throws Throwable { + return testInt(t); + } + + public Integer testInt(Throwable pThrowable) throws Throwable { + if (pThrowable != null) { + throw pThrowable; + } + return 0; + } + } + + public static class FailureOnOddInvocations { + private static int invocation; + public FailureOnOddInvocations() throws SomeException { + final int i = ++invocation; + if (i % 2 == 1) { + throw new SomeException("Odd Invocation: " + i); + } + } + } + + @Test + void testRunnable() { + FailureOnOddInvocations.invocation = 0; + try { + Functions.run(() -> new FailureOnOddInvocations()); + fail("Expected Exception"); + } catch (UndeclaredThrowableException e) { + final Throwable cause = e.getCause(); + assertNotNull(cause); + assertTrue(cause instanceof SomeException); + assertEquals("Odd Invocation: 1", cause.getMessage()); + } + Functions.run(() -> new FailureOnOddInvocations()); + } + + @Test + void testCallable() { + FailureOnOddInvocations.invocation = 0; + try { + Functions.call(() -> new FailureOnOddInvocations()); + fail("Expected Exception"); + } catch (UndeclaredThrowableException e) { + final Throwable cause = e.getCause(); + assertNotNull(cause); + assertTrue(cause instanceof SomeException); + assertEquals("Odd Invocation: 1", cause.getMessage()); + } + final FailureOnOddInvocations instance = Functions.call(() -> new FailureOnOddInvocations()); + assertNotNull(instance); + } + + @Test + void testAcceptConsumer() { + final IllegalStateException ise = new IllegalStateException(); + final Testable testable = new Testable(ise); + try { + Functions.accept((t) -> t.test(), testable); + fail("Expected Exception"); + } catch (IllegalStateException e) { + assertSame(ise, e); + } + final Error error = new OutOfMemoryError(); + testable.setThrowable(error); + try { + Functions.accept((t) -> t.test(), testable); + } catch (OutOfMemoryError e) { + assertSame(error, e); + } + final IOException ioe = new IOException("Unknown I/O error"); + testable.setThrowable(ioe); + try { + Functions.accept((t) -> t.test(), testable); + fail("Expected Exception"); + } catch (UncheckedIOException e) { + final Throwable t = e.getCause(); + assertNotNull(t); + assertTrue(t instanceof IOException); + assertSame(ioe, t); + } + testable.setThrowable(null); + Functions.accept((t) -> t.test(), testable); + } + + @Test + void testAcceptBiConsumer() { + final IllegalStateException ise = new IllegalStateException(); + final Testable testable = new Testable(null); + try { + Functions.accept((t1,t2) -> t1.test(t2), testable, ise); + fail("Expected Exception"); + } catch (IllegalStateException e) { + assertSame(ise, e); + } + final Error error = new OutOfMemoryError(); + try { + Functions.accept((t1,t2) -> t1.test(t2), testable, error); + } catch (OutOfMemoryError e) { + assertSame(error, e); + } + final IOException ioe = new IOException("Unknown I/O error"); + testable.setThrowable(ioe); + try { + Functions.accept((t1,t2) -> t1.test(t2), testable, ioe); + fail("Expected Exception"); + } catch (UncheckedIOException e) { + final Throwable t = e.getCause(); + assertNotNull(t); + assertTrue(t instanceof IOException); + assertSame(ioe, t); + } + testable.setThrowable(null); + Functions.accept((t1,t2) -> t1.test(t2), testable, (Throwable) null); + } + + @Test + public void testApplyFunction() { + final IllegalStateException ise = new IllegalStateException(); + final Testable testable = new Testable(ise); + try { + Functions.apply((t) -> t.testInt(), testable); + fail("Expected Exception"); + } catch (IllegalStateException e) { + assertSame(ise, e); + } + final Error error = new OutOfMemoryError(); + testable.setThrowable(error); + try { + Functions.apply((t) -> t.testInt(), testable); + } catch (OutOfMemoryError e) { + assertSame(error, e); + } + final IOException ioe = new IOException("Unknown I/O error"); + testable.setThrowable(ioe); + try { + Functions.apply((t) -> t.testInt(), testable); + fail("Expected Exception"); + } catch (UncheckedIOException e) { + final Throwable t = e.getCause(); + assertNotNull(t); + assertTrue(t instanceof IOException); + assertSame(ioe, t); + } + testable.setThrowable(null); + final Integer i = Functions.apply((t) -> t.testInt(), testable); + assertNotNull(i); + assertEquals(0, i.intValue()); + } + + @Test + public void testApplyBiFunction() { + final IllegalStateException ise = new IllegalStateException(); + final Testable testable = new Testable(null); + try { + Functions.apply((t1,t2) -> t1.testInt(t2), testable, ise); + fail("Expected Exception"); + } catch (IllegalStateException e) { + assertSame(ise, e); + } + final Error error = new OutOfMemoryError(); + try { + Functions.apply((t1,t2) -> t1.testInt(t2), testable, error); + } catch (OutOfMemoryError e) { + assertSame(error, e); + } + final IOException ioe = new IOException("Unknown I/O error"); + try { + Functions.apply((t1,t2) -> t1.testInt(t2), testable, ioe); + fail("Expected Exception"); + } catch (UncheckedIOException e) { + final Throwable t = e.getCause(); + assertNotNull(t); + assertTrue(t instanceof IOException); + assertSame(ioe, t); + } + final Integer i = Functions.apply((t1,t2) -> t1.testInt(t2), testable, (Throwable) null); + assertNotNull(i); + assertEquals(0, i.intValue()); + } +}