From 9188fa8ccebc0bc2ff5813d129b9b2fa5bb159ca Mon Sep 17 00:00:00 2001 From: Steve Loughran Date: Fri, 17 Sep 2021 11:06:13 +0100 Subject: [PATCH] HADOOP-17126. implement non-guava Precondition checkNotNull This adds a new class org.apache.hadoop.util.Preconditions which is * @Private/@Unstable * Intended to allow us to move off Google Guava * Is designed to be trivially backportable (i.e contains no references to guava classes internally) Please use this instead of the guava equivalents, where possible. Contributed by: Ahmed Hussein Change-Id: Ic392451bcfe7d446184b7c995734bcca8c07286e --- .../org/apache/hadoop/util/Preconditions.java | 163 ++++++++++++++++++ .../apache/hadoop/util/TestPreconditions.java | 122 +++++++++++++ 2 files changed, 285 insertions(+) create mode 100644 hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/Preconditions.java create mode 100644 hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/util/TestPreconditions.java diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/Preconditions.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/Preconditions.java new file mode 100644 index 0000000000..67d6e6cc9f --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/util/Preconditions.java @@ -0,0 +1,163 @@ +/* + * 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.hadoop.util; + +import java.util.function.Supplier; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.classification.InterfaceStability; + +/** + *

This class replaces {@code guava.Preconditions} which provides helpers + * to validate the following conditions: + *

+ */ +@InterfaceAudience.Private +@InterfaceStability.Unstable +public final class Preconditions { + private static final Logger LOG = + LoggerFactory.getLogger(Preconditions.class); + + private static final String VALIDATE_IS_NOT_NULL_EX_MESSAGE = + "The argument object is NULL"; + + private Preconditions() { + } + + /** + *

Preconditions that the specified argument is not {@code null}, + * throwing a NPE exception otherwise. + * + *

The message of the exception is + * "The validated object is null".

+ * + * @param the object type + * @param obj the object to check + * @return the validated object + * @throws NullPointerException if the object is {@code null} + * @see #checkNotNull(Object, Object) + */ + public static T checkNotNull(final T obj) { + return checkNotNull(obj, VALIDATE_IS_NOT_NULL_EX_MESSAGE); + } + + /** + *

Preconditions that the specified argument is not {@code null}, + * throwing a NPE exception otherwise. + * + *

The message of the exception is {@code errorMessage}.

+ * + * @param the object type + * @param obj the object to check + * @param errorMessage the message associated with the NPE + * @return the validated object + * @throws NullPointerException if the object is {@code null} + * @see #checkNotNull(Object, String, Object...) + */ + public static T checkNotNull(final T obj, + final Object errorMessage) { + if (obj == null) { + throw new NullPointerException(String.valueOf(errorMessage)); + } + return obj; + } + + /** + *

Preconditions that the specified argument is not {@code null}, + * throwing a NPE exception otherwise. + * + *

The message of the exception is {@code String.format(f, m)}.

+ * + * @param the object type + * @param obj the object to check + * @param message the {@link String#format(String, Object...)} + * exception message if valid. Otherwise, + * the message is {@link #VALIDATE_IS_NOT_NULL_EX_MESSAGE} + * @param values the optional values for the formatted exception message + * @return the validated object + * @throws NullPointerException if the object is {@code null} + * @see #checkNotNull(Object, Supplier) + */ + public static T checkNotNull(final T obj, final String message, + final Object... values) { + // Deferring the evaluation of the message is a tradeoff between the cost + // of constructing lambda Vs constructing a string object. + // Using lambda would allocate an object on every call: + // return checkNotNull(obj, () -> String.format(message, values)); + if (obj == null) { + String msg; + try { + msg = String.format(message, values); + } catch (Exception e) { + LOG.debug("Error formatting message", e); + msg = VALIDATE_IS_NOT_NULL_EX_MESSAGE; + } + throw new NullPointerException(msg); + } + return obj; + } + + /** + *

Preconditions that the specified argument is not {@code null}, + * throwing a NPE exception otherwise. + * + *

The message of the exception is {@code msgSupplier.get()}.

+ * + * @param the object type + * @param obj the object to check + * @param msgSupplier the {@link Supplier#get()} set the + * exception message if valid. Otherwise, + * the message is {@link #VALIDATE_IS_NOT_NULL_EX_MESSAGE} + * @return the validated object (never {@code null} for method chaining) + * @throws NullPointerException if the object is {@code null} + */ + public static T checkNotNull(final T obj, + final Supplier msgSupplier) { + if (obj == null) { + String msg; + try { + // note that we can get NPE evaluating the message itself; + // but we do not want this to override the actual NPE. + msg = msgSupplier.get(); + } catch (Exception e) { + // ideally we want to log the error to capture. This may cause log files + // to bloat. On the other hand, swallowing the exception may hide a bug + // in the caller. Debug level is a good compromise between the two + // concerns. + LOG.debug("Error formatting message", e); + msg = VALIDATE_IS_NOT_NULL_EX_MESSAGE; + } + throw new NullPointerException(msg); + } + return obj; + } + + /* @VisibleForTesting */ + static String getDefaultNullMSG() { + return VALIDATE_IS_NOT_NULL_EX_MESSAGE; + } +} diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/util/TestPreconditions.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/util/TestPreconditions.java new file mode 100644 index 0000000000..7feffc3163 --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/util/TestPreconditions.java @@ -0,0 +1,122 @@ +/* + * 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.hadoop.util; + +import org.junit.Test; + +import org.apache.hadoop.test.LambdaTestUtils; + +public class TestPreconditions { + private static final String NON_NULL_STRING = "NON_NULL_OBJECT"; + private static final String NON_INT_STRING = "NOT_INT"; + private static final String EXPECTED_ERROR_MSG = "Expected-Error-MSG"; + private static final String EXPECTED_ERROR_MSG_ARGS = + EXPECTED_ERROR_MSG + " %s number %d"; + private static final String NULL_FORMATTER = null; + + private String errorMessage; + + @Test + public void testCheckNotNullSuccess() { + Preconditions.checkNotNull(NON_NULL_STRING); + // null supplier + Preconditions.checkNotNull(NON_NULL_STRING, null); + // ill-formated string supplier + Preconditions.checkNotNull(NON_NULL_STRING, ()-> String.format("%d", + NON_INT_STRING)); + // null pattern to string formatter + Preconditions.checkNotNull(NON_NULL_STRING, NULL_FORMATTER, null, 1); + // null arguments to string formatter + Preconditions.checkNotNull(NON_NULL_STRING, EXPECTED_ERROR_MSG_ARGS, + null, null); + // illegal format exception + Preconditions.checkNotNull(NON_NULL_STRING, "message %d %d", + NON_INT_STRING, 1); + // insufficient arguments + Preconditions.checkNotNull(NON_NULL_STRING, EXPECTED_ERROR_MSG_ARGS, + NON_INT_STRING); + // null format in string supplier + Preconditions.checkNotNull(NON_NULL_STRING, + () -> String.format(NULL_FORMATTER, NON_INT_STRING)); + } + + @Test + public void testCheckNotNullFailure() throws Exception { + // failure without message + LambdaTestUtils.intercept(NullPointerException.class, + Preconditions.getDefaultNullMSG(), + () -> Preconditions.checkNotNull(null)); + + // failure with message + errorMessage = EXPECTED_ERROR_MSG; + LambdaTestUtils.intercept(NullPointerException.class, + errorMessage, + () -> Preconditions.checkNotNull(null, errorMessage)); + + // failure with Null message + LambdaTestUtils.intercept(NullPointerException.class, + null, + () -> Preconditions.checkNotNull(null, errorMessage)); + + // failure with message format + errorMessage = EXPECTED_ERROR_MSG + " %s"; + String arg = "NPE"; + String expectedMSG = String.format(errorMessage, arg); + LambdaTestUtils.intercept(NullPointerException.class, + expectedMSG, + () -> Preconditions.checkNotNull(null, errorMessage, arg)); + + // failure with multiple arg message format + errorMessage = EXPECTED_ERROR_MSG_ARGS; + expectedMSG = + String.format(errorMessage, arg, 1); + LambdaTestUtils.intercept(NullPointerException.class, + expectedMSG, + () -> Preconditions.checkNotNull(null, errorMessage, arg, 1)); + + // illegal format will be thrown if the case is not handled correctly + LambdaTestUtils.intercept(NullPointerException.class, + Preconditions.getDefaultNullMSG(), + () -> Preconditions.checkNotNull(null, + errorMessage, 1, NON_INT_STRING)); + + // illegal format will be thrown for insufficient Insufficient Args + LambdaTestUtils.intercept(NullPointerException.class, + Preconditions.getDefaultNullMSG(), + () -> Preconditions.checkNotNull(null, errorMessage, NON_NULL_STRING)); + + // illegal format in supplier + LambdaTestUtils.intercept(NullPointerException.class, + Preconditions.getDefaultNullMSG(), + () -> Preconditions.checkNotNull(null, + () -> String.format(errorMessage, 1, NON_INT_STRING))); + + // insufficient arguments in string Supplier + LambdaTestUtils.intercept(NullPointerException.class, + Preconditions.getDefaultNullMSG(), + () -> Preconditions.checkNotNull(null, + () -> String.format(errorMessage, NON_NULL_STRING))); + + // null formatter + LambdaTestUtils.intercept(NullPointerException.class, + Preconditions.getDefaultNullMSG(), + () -> Preconditions.checkNotNull(null, + () -> String.format(NULL_FORMATTER, NON_NULL_STRING))); + } +}