浏览代码

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
Steve Loughran 3 年之前
父节点
当前提交
5ebcd4bb92

+ 163 - 0
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;
+
+/**
+ * <p>This class replaces {@code guava.Preconditions} which provides helpers
+ * to validate the following conditions:
+ * <ul>
+ *   <li>An invalid {@code null} obj causes a {@link NullPointerException}.</li>
+ *   <li>An invalid argument causes an {@link IllegalArgumentException}.</li>
+ *   <li>An invalid state causes an {@link IllegalStateException}.</li>
+ *   <li>An invalid index causes an {@link IndexOutOfBoundsException}.</li>
+ * </ul>
+ */
+@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() {
+  }
+
+  /**
+   * <p>Preconditions that the specified argument is not {@code null},
+   * throwing a NPE exception otherwise.
+   *
+   * <p>The message of the exception is
+   * &quot;The validated object is null&quot;.</p>
+   *
+   * @param <T> 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> T checkNotNull(final T obj) {
+    return checkNotNull(obj, VALIDATE_IS_NOT_NULL_EX_MESSAGE);
+  }
+
+  /**
+   * <p>Preconditions that the specified argument is not {@code null},
+   * throwing a NPE exception otherwise.
+   *
+   * <p>The message of the exception is {@code errorMessage}.</p>
+   *
+   * @param <T> 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> T checkNotNull(final T obj,
+      final Object errorMessage) {
+    if (obj == null) {
+      throw new NullPointerException(String.valueOf(errorMessage));
+    }
+    return obj;
+  }
+
+  /**
+   * <p>Preconditions that the specified argument is not {@code null},
+   * throwing a NPE exception otherwise.
+   *
+   * <p>The message of the exception is {@code String.format(f, m)}.</p>
+   *
+   * @param <T> 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> 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;
+  }
+
+  /**
+   * <p>Preconditions that the specified argument is not {@code null},
+   * throwing a NPE exception otherwise.
+   *
+   * <p>The message of the exception is {@code msgSupplier.get()}.</p>
+   *
+   * @param <T> 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> T checkNotNull(final T obj,
+      final Supplier<String> 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;
+  }
+}

+ 122 - 0
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)));
+  }
+}