This is an automated email from the ASF dual-hosted git repository. jochen pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-lang.git
The following commit(s) were added to refs/heads/master by this push: new 45e8395 Introducing @Nonnull, @Nullable, and the Objects class as a helper tool. 45e8395 is described below commit 45e8395ec7a63f432ce8df03159ee5465b1380ca Author: Jochen Wiedmann <jochen.wiedm...@gmail.com> AuthorDate: Sat Feb 6 22:51:47 2021 +0100 Introducing @Nonnull, @Nullable, and the Objects class as a helper tool. --- pom.xml | 7 +- spotbugs-exclude-filter.xml | 8 + src/changes/changes.xml | 1 + .../org/apache/commons/lang3/function/Objects.java | 176 +++++++++++++++++++++ .../apache/commons/lang3/function/ObjectsTest.java | 146 +++++++++++++++++ 5 files changed, 337 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f8524e8..5135e4c 100644 --- a/pom.xml +++ b/pom.xml @@ -566,7 +566,12 @@ <version>${jmh.version}</version> <scope>test</scope> </dependency> - + <dependency> + <groupId>com.google.code.findbugs</groupId> + <artifactId>jsr305</artifactId> + <version>3.0.2</version> + <scope>provided</scope> + </dependency> </dependencies> <distributionManagement> diff --git a/spotbugs-exclude-filter.xml b/spotbugs-exclude-filter.xml index 8636890..be00732 100644 --- a/spotbugs-exclude-filter.xml +++ b/spotbugs-exclude-filter.xml @@ -153,4 +153,12 @@ <Method name="compare" /> <Bug pattern="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE" /> </Match> + + <!-- Reason: requireNonNull is supposed to take a nullable parameter, + whatever Spotbugs thinks of it. --> + <Match> + <Class name="org.apache.commons.lang3.function.Objects" /> + <Method name="requireNonNull" /> + <Bug pattern="NP_PARAMETER_MUST_BE_NONNULL_BUT_MARKED_AS_NULLABLE" /> + </Match> </FindBugsFilter> diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 1eca750..4908376 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -105,6 +105,7 @@ The <action> type attribute can be add,update,fix,remove. <action type="update" dev="ggregory" due-to="Ali K. Nouri">Processor.java: check enum equality with == instead of .equals() method #690.</action> <action type="update" dev="ggregory" due-to="Dependabot">Bump junit-pioneer from 1.1.0 to 1.3.0 #702.</action> <action type="update" dev="ggregory" due-to="Dependabot">Bump maven-checkstyle-plugin from 3.1.1 to 3.1.2 #705.</action> + <action type="add" dev="jochen">Introduce the use of @Nonnull, and @Nullable, and the Objects class as a helper tool.</action> </release> <release version="3.11" date="2020-07-12" description="New features and bug fixes."> <action type="update" dev="chtompki" due-to="Jin Xu">Refine test output for FastDateParserTest</action> diff --git a/src/main/java/org/apache/commons/lang3/function/Objects.java b/src/main/java/org/apache/commons/lang3/function/Objects.java new file mode 100755 index 0000000..02f7ddb --- /dev/null +++ b/src/main/java/org/apache/commons/lang3/function/Objects.java @@ -0,0 +1,176 @@ +/* + * 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.function; + +import java.util.function.Supplier; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import org.apache.commons.lang3.ObjectUtils; + + +/** + * This class provides some replacements for the corresponding methods in + * {@link Objects}. The replacements have the advantage, that they are properly + * annotated with {@link Nullable}, and/or {@link Nonnull}, so they let the + * compiler know, what their respective results are. + * + * The various {@code requireNonNull} methods are particularly handy, when + * dealing with external code, that a) doesn't support the {@link Nonnull} + * annotation, or if you know for other reasons, that an object is non-null. + * Take for example, a {@link java.util.Map map}, that you have filled with + * non-null values. So, in your opinion, the following should be perfectably + * valid code: + * <pre> + * final Map<String,Object> map = getMapOfNonNullValues(); + * final @Nonnull Object o = map.get("SomeKey"); + * </pre> + * However, if your Java compiler *does* null analysis, it will reject this + * example as invalid, because {@link java.util.Map#get(Object)} might return + * a null value. As a workaround, you can use this: + * <pre> + * import static org.apache.commons.lang3.function.Objects.requireNonNull; + * + * final Map<String,Object> map = getMapOfNonNullValues(); + * final @Nonnull Object o = requireNonNull(map.get("SomeKey")); + * </pre> + * + * This class is somewhat redundant with regards to {@link ObjectUtils}. + * For example, {@link #requireNonNull(Object, Object)} is almost equivalent + * with {@link ObjectUtils#defaultIfNull(Object, Object)}. However, it isn't + * quite the same, because the latter can, in fact, return null. The former + * can't, and the Java compiler confirms this.(An alternative to redundancy + * would have been to change the {@code ObjectUtils} class. However, that + * would mean loosing upwards compatibility, and we don't do that.) + */ +public class Objects { + /** + * Checks, whether the given object is non-null. If so, returns the non-null + * object as a result value. Otherwise, a NullPointerException is thrown. + * @param <O> The type of parameter {@code value}, also the result type. + * @param value The value, which is being checked. + * @return The given input value, if it was found to be non-null. + * @throws NullPointerException The input value was null. + * @see java.util.Objects#requireNonNull(Object) + */ + public static <O> @Nonnull O requireNonNull(@Nullable O value) throws NullPointerException { + if (value == null) { + throw new NullPointerException("The value must not be null."); + } else { + return value; + } + } + + /** + * Checks, whether the given object is non-null. If so, returns the non-null + * object as a result value. Otherwise, a NullPointerException is thrown. + * @param <O> The type of parameter {@code value}, also the result type. + * @param value The value, which is being checked. + * @param defaultValue The default value, which is being returned, if the + * check fails, and the {@code value} is null. + * @throws NullPointerException The input value, and the default value are null. + * @return The given input value, if it was found to be non-null. + * @see java.util.Objects#requireNonNull(Object) + */ + public static <O> @Nonnull O requireNonNull(@Nullable O value, @Nonnull O defaultValue) { + if (value == null) { + if (defaultValue == null) { + throw new NullPointerException("The default value must not be null."); + } else { + return defaultValue; + } + } else { + return value; + } + } + + /** + * Checks, whether the given object is non-null. If so, returns the non-null + * object as a result value. Otherwise, a NullPointerException is thrown. + * @param <O> The type of parameter {@code value}, also the result type. + * @param value The value, which is being checked. + * @param msg A string, which is being used as the exceptions message, if the + * check fails. + * @return The given input value, if it was found to be non-null. + * @throws NullPointerException The input value was null. + * @see java.util.Objects#requireNonNull(Object, String) + * @see #requireNonNull(Object, Supplier) + */ + public static <O> @Nonnull O requireNonNull(@Nullable O value, @Nonnull String msg) throws NullPointerException { + if (value == null) { + throw new NullPointerException(msg); + } else { + return value; + } + } + + /** + * Checks, whether the given object is non-null. If so, returns the non-null + * object as a result value. Otherwise, a NullPointerException is thrown. + * @param <O> The type of parameter {@code value}, also the result type. + * @param value The value, which is being checked. + * @param msgSupplier A supplier, which creates the exception message, if the check fails. + * This supplier will only be invoked, if necessary. + * @return The given input value, if it was found to be non-null. + * @throws NullPointerException The input value was null. + * @see java.util.Objects#requireNonNull(Object, String) + * @see #requireNonNull(Object, String) + */ + public static <O> @Nonnull O requireNonNull(@Nullable O value, @Nonnull Supplier<String> msgSupplier) throws NullPointerException { + if (value == null) { + throw new NullPointerException(msgSupplier.get()); + } else { + return value; + } + } + + /** Checks, whether the given object is non-null. If so, returns the non-null + * object as a result value. Otherwise, invokes the given {@link Supplier}, + * and returns the suppliers result value. + * @param <O> The type of parameter {@code value}, also the result type of + * the default value supplier, and of the method itself. + * @param <E> The type of exception, that the {@code default value supplier}, + * may throw. + * @param value The value, which is being checked. + * @param defaultValueSupplier The supplier, which returns the default value. This default + * value <em>must</em> be non-null. The supplier will only be invoked, if + * necessary. (If the {@code value} parameter is null, that is.) + * @return The given input value, if it was found to be non-null. Otherwise, + * the value, that has been returned by the default value supplier. + * @see #requireNonNull(Object) + * @see #requireNonNull(Object, String) + * @see #requireNonNull(Object, Supplier) + * @throws NullPointerException The default value supplier is null, or the default + * value supplier has returned null. + */ + public static <O, E extends Throwable> @Nonnull O requireNonNull(@Nullable O value, @Nonnull FailableSupplier<O, E> defaultValueSupplier) { + if (value == null) { + final FailableSupplier<O, ?> supp = requireNonNull(defaultValueSupplier, "The supplier must not be null"); + final O o; + try { + o = supp.get(); + } catch (Throwable t) { + throw Failable.rethrow(t); + } + return requireNonNull(o, "The supplier must not return null."); + } else { + return value; + } + } +} diff --git a/src/test/java/org/apache/commons/lang3/function/ObjectsTest.java b/src/test/java/org/apache/commons/lang3/function/ObjectsTest.java new file mode 100755 index 0000000..cea1c8e --- /dev/null +++ b/src/test/java/org/apache/commons/lang3/function/ObjectsTest.java @@ -0,0 +1,146 @@ +/* + * 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.function; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.function.Supplier; + +import org.junit.jupiter.api.Test; + + +class ObjectsTest { + @Test + void testRequireNonNullObject() { + assertSame("foo", Objects.requireNonNull("foo")); + try { + Objects.requireNonNull(null); + fail("Expected Exception"); + } catch (NullPointerException e) { + assertEquals("The value must not be null.", e.getMessage()); + } + } + + @Test + void testRequireNonNullObjectString() { + assertSame("foo", Objects.requireNonNull("foo", "bar")); + try { + Objects.requireNonNull(null, "bar"); + fail("Expected Exception"); + } catch (NullPointerException e) { + assertEquals("bar", e.getMessage()); + } + } + + public static class TestableSupplier<O> implements Supplier<O> { + private final Supplier<O> supplier; + private boolean invoked; + + TestableSupplier(Supplier<O> pSupplier) { + this.supplier = pSupplier; + } + + @Override + public O get() { + invoked = true; + return supplier.get(); + } + + public boolean isInvoked() { + return invoked; + } + } + + @Test + void testRequireNonNullObjectSupplierString() { + final TestableSupplier<String> supplier = new TestableSupplier<>(() -> "bar"); + assertSame("foo", Objects.requireNonNull("foo", supplier)); + assertFalse(supplier.isInvoked()); + try { + Objects.requireNonNull(null, supplier); + fail("Expected Exception"); + } catch (NullPointerException e) { + assertEquals("bar", e.getMessage()); + assertTrue(supplier.isInvoked()); + } + } + + public static class TestableFailableSupplier<O, E extends Exception> implements FailableSupplier<O, E> { + private final FailableSupplier<O, E> supplier; + private boolean invoked; + + TestableFailableSupplier(FailableSupplier<O, E> pSupplier) { + this.supplier = pSupplier; + } + + @Override + public O get() throws E { + invoked = true; + return supplier.get(); + } + + public boolean isInvoked() { + return invoked; + } + } + + @Test + void testRequireNonNullObjectFailableSupplierString() { + final TestableFailableSupplier<String, ?> supplier = new TestableFailableSupplier<>(() -> { + return null; + }); + assertSame("foo", Objects.requireNonNull("foo", supplier)); + assertFalse(supplier.isInvoked()); + try { + Objects.requireNonNull(null, supplier); + fail("Expected Exception"); + } catch (NullPointerException e) { + assertEquals("The supplier must not return null.", e.getMessage()); + assertTrue(supplier.isInvoked()); + } + final TestableFailableSupplier<String, ?> supplier2 = new TestableFailableSupplier<>(() -> { + return null; + }); + try { + Objects.requireNonNull(null, supplier2); + fail("Expected Exception"); + } catch (NullPointerException e) { + assertEquals("The supplier must not return null.", e.getMessage()); + assertTrue(supplier2.isInvoked()); + } + final TestableFailableSupplier<String, ?> supplier3 = new TestableFailableSupplier<>(() -> { + return "bar"; + }); + assertSame("bar", Objects.requireNonNull(null, supplier3)); + final RuntimeException rte = new RuntimeException(); + final TestableFailableSupplier<String, ?> supplier4 = new TestableFailableSupplier<>(() -> { + throw rte; + }); + try { + Objects.requireNonNull(null, supplier4); + fail("Expected Exception"); + } catch (RuntimeException e) { + assertSame(rte, e); + assertTrue(supplier4.isInvoked()); + } + } +}