This is an automated email from the ASF dual-hosted git repository.

laurent pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/drill.git


The following commit(s) were added to refs/heads/master by this push:
     new fe85f83  DRILL-7945: Stop patching newly added Guava methods (#2247)
fe85f83 is described below

commit fe85f835ccf5b88d62e383ff34907f3a703223c6
Author: Laurent Goujon <[email protected]>
AuthorDate: Fri Jun 4 13:55:56 2021 -0700

    DRILL-7945: Stop patching newly added Guava methods (#2247)
    
    In order to support Apache Iceberg, GuavaPatcher adds several methods to
    com.google.common.base.Preconditions required by Apache Iceberg runtime
    but not available in Guava 18.
    
    When Guava version was updated to 30.1.1, Guava Patcher tries to update
    existing methods with some code referencing package protected methods
    which do no exist anymore. Patching fails but as the methods do now
    exists, there's no compatibility issue. That said, GuavaPatcher emits
    some logging which might confuse end user.
    
    Remove the unnecessary patching of the Preconditions class and also add
    some unit test designed to fail if patching fails for any reason.
---
 .../org/apache/drill/common/util/GuavaPatcher.java | 118 +---------
 .../apache/drill/common/util/TestGuavaPatcher.java | 243 +++++++++++++++++++++
 src/main/resources/checkstyle-config.xml           |   1 +
 3 files changed, 256 insertions(+), 106 deletions(-)

diff --git 
a/common/src/main/java/org/apache/drill/common/util/GuavaPatcher.java 
b/common/src/main/java/org/apache/drill/common/util/GuavaPatcher.java
index 0a0a7b2..cd13366 100644
--- a/common/src/main/java/org/apache/drill/common/util/GuavaPatcher.java
+++ b/common/src/main/java/org/apache/drill/common/util/GuavaPatcher.java
@@ -18,11 +18,10 @@
 package org.apache.drill.common.util;
 
 import java.lang.reflect.Modifier;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
+
+import 
org.apache.drill.shaded.guava.com.google.common.annotations.VisibleForTesting;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import javassist.ClassPool;
 import javassist.CtClass;
@@ -31,8 +30,6 @@ import javassist.CtMethod;
 import javassist.CtNewMethod;
 import javassist.scopedpool.ScopedClassPoolRepository;
 import javassist.scopedpool.ScopedClassPoolRepositoryImpl;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 public class GuavaPatcher {
   private static final Logger logger = 
LoggerFactory.getLogger(GuavaPatcher.class);
@@ -44,10 +41,16 @@ public class GuavaPatcher {
       patchingAttempted = true;
       patchStopwatch();
       patchCloseables();
-      patchPreconditions();
     }
   }
 
+  private static boolean patchingSuccessful = true;
+
+  @VisibleForTesting
+  static boolean isPatchingSuccessful() {
+    return patchingAttempted && patchingSuccessful;
+  }
+
   /**
    * Makes Guava stopwatch look like the old version for compatibility with 
hbase-server (for test purposes).
    */
@@ -78,6 +81,7 @@ public class GuavaPatcher {
   }
 
   private static void logUnableToPatchException(Exception e) {
+    patchingSuccessful = false;
     logger.warn("Unable to patch Guava classes: {}", e.getMessage());
     logger.debug("Exception:", e);
   }
@@ -103,104 +107,6 @@ public class GuavaPatcher {
   }
 
   /**
-   * Patches Guava Preconditions with missing methods, added for the Apache 
Iceberg.
-   */
-  private static void patchPreconditions() {
-    try {
-      ClassPool cp = getClassPool();
-      CtClass cc = cp.get("com.google.common.base.Preconditions");
-
-      // Javassist does not support varargs, generate methods with varying 
number of arguments
-      int startIndex = 1;
-      int endIndex = 5;
-
-      List<String> methodsWithVarargsTemplates = Arrays.asList(
-          "public static void checkArgument(boolean expression, String 
errorMessageTemplate, %s) {\n"
-              + "    if (!expression) {\n"
-              + "      throw new 
IllegalArgumentException(format(errorMessageTemplate, new Object[] { %s }));\n"
-              + "    }\n"
-              + "  }",
-
-          "public static Object checkNotNull(Object reference, String 
errorMessageTemplate, %s) {\n"
-              + "    if (reference == null) {\n"
-              + "      throw new 
NullPointerException(format(errorMessageTemplate, new Object[] { %s }));\n"
-              + "    } else {\n"
-              + "      return reference;\n"
-              + "    }\n"
-              + "  }",
-
-          "public static void checkState(boolean expression, String 
errorMessageTemplate, %s) {\n"
-              + "    if (!expression) {\n"
-              + "      throw new 
IllegalStateException(format(errorMessageTemplate, new Object[] { %s }));\n"
-              + "    }\n"
-              + "  }"
-      );
-
-      List<String> methodsWithPrimitives = Arrays.asList(
-          "public static void checkArgument(boolean expression, String 
errorMessageTemplate, int arg1) {\n"
-              + "    if (!expression) {\n"
-              + "      throw new 
IllegalArgumentException(format(errorMessageTemplate, new Object[] { new 
Integer(arg1) }));\n"
-              + "    }\n"
-              + "  }",
-          "public static void checkArgument(boolean expression, String 
errorMessageTemplate, long arg1) {\n"
-              + "    if (!expression) {\n"
-              + "      throw new 
IllegalArgumentException(format(errorMessageTemplate, new Object[] { new 
Long(arg1) }));\n"
-              + "    }\n"
-              + "  }",
-          "public static void checkArgument(boolean expression, String 
errorMessageTemplate, long arg1, long arg2) {\n"
-              + "    if (!expression) {\n"
-              + "      throw new 
IllegalArgumentException(format(errorMessageTemplate, new Object[] { new 
Long(arg1), new Long(arg2)}));\n"
-              + "    }\n"
-              + "  }",
-          "public static Object checkNotNull(Object reference, String 
errorMessageTemplate, int arg1) {\n"
-              + "    if (reference == null) {\n"
-              + "      throw new 
NullPointerException(format(errorMessageTemplate, new Object[] { new 
Integer(arg1) }));\n"
-              + "    } else {\n"
-              + "      return reference;\n"
-              + "    }\n"
-              + "  }",
-          "public static void checkState(boolean expression, String 
errorMessageTemplate, int arg1) {\n"
-              + "    if (!expression) {\n"
-              + "      throw new 
IllegalStateException(format(errorMessageTemplate, new Object[] { new 
Integer(arg1) }));\n"
-              + "    }\n"
-              + "  }"
-    );
-
-      List<String> newMethods = IntStream.rangeClosed(startIndex, endIndex)
-          .mapToObj(
-              i -> {
-                List<String> args = IntStream.rangeClosed(startIndex, i)
-                    .mapToObj(j -> "arg" + j)
-                    .collect(Collectors.toList());
-
-                String methodInput = args.stream()
-                    .map(arg -> "Object " + arg)
-                    .collect(Collectors.joining(", "));
-
-                String arrayInput = String.join(", ", args);
-
-                return methodsWithVarargsTemplates.stream()
-                    .map(method -> String.format(method, methodInput, 
arrayInput))
-                    .collect(Collectors.toList());
-              })
-          .flatMap(Collection::stream)
-          .collect(Collectors.toList());
-
-      newMethods.addAll(methodsWithPrimitives);
-
-      for (String method : newMethods) {
-        CtMethod newMethod = CtNewMethod.make(method, cc);
-        cc.addMethod(newMethod);
-      }
-
-      cc.toClass();
-      logger.info("Google's Preconditions were patched to hold new methods.");
-    } catch (Exception e) {
-      logUnableToPatchException(e);
-    }
-  }
-
-  /**
    * Returns {@link javassist.scopedpool.ScopedClassPool} instance which uses 
the same class loader
    * which was used for loading current class.
    *
diff --git 
a/common/src/test/java/org/apache/drill/common/util/TestGuavaPatcher.java 
b/common/src/test/java/org/apache/drill/common/util/TestGuavaPatcher.java
new file mode 100644
index 0000000..5dbdf53
--- /dev/null
+++ b/common/src/test/java/org/apache/drill/common/util/TestGuavaPatcher.java
@@ -0,0 +1,243 @@
+/*
+ * 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.drill.common.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.drill.test.BaseTest;
+import org.junit.Test;
+
+// Easier to test Guava patching if we can reference Guava classes directly 
for the tests
+// CHECKSTYLE:OFF
+import com.google.common.base.Preconditions;
+import com.google.common.base.Stopwatch;
+import com.google.common.base.Ticker;
+import com.google.common.io.Closeables;
+//CHECKSTYLE:ON
+
+/**
+ * Test class for {@code GuavaPatcher}
+ *
+ */
+public class TestGuavaPatcher extends BaseTest {
+
+  @Test
+  public void checkSuccessfulPatching() {
+    // Catch-all test to see if Guava patching was successful
+    assertTrue("Guava Patcher ran with errors, check error log messages",
+        GuavaPatcher.isPatchingSuccessful());
+  }
+
+  @Test
+  public void checkStopwatchEllapsedMillis() throws Exception {
+    long[] currentTimeMillis = new long[] { 0L };
+    final Ticker ticker = new Ticker() {
+      @Override
+      public long read() {
+        return TimeUnit.MILLISECONDS.toNanos(currentTimeMillis[0]);
+      }
+    };
+    final Stopwatch stopwatch = Stopwatch.createStarted(ticker);
+    currentTimeMillis[0] = 12345L;
+    stopwatch.stop();
+
+    assertEquals(currentTimeMillis[0],
+        stopwatch.elapsed(TimeUnit.MILLISECONDS));
+    assertEquals(currentTimeMillis[0], (long) invokeMethod(Stopwatch.class,
+        "elapsedMillis", new Class[] {}, stopwatch));
+  }
+
+  @Test
+  public void checkCloseablesCloseQuietly() throws Exception {
+    final Closeable alwaysThrows = () -> {
+      throw new IOException("Always fail");
+    };
+
+    invokeMethod(Closeables.class, "closeQuietly",
+        new Class[] { Closeable.class }, null, alwaysThrows);
+  }
+
+  // All the preconditions checks are method which were previously added for
+  // compatibility with Apache Iceberg but are not necessary anymore because
+  // Guava's version has been updated since.
+
+  @Test
+  public void checkPreconditionsCheckArgumentIntParam() throws Exception {
+    invokeMethod(Preconditions.class, "checkArgument",
+        new Class[] { boolean.class, String.class, int.class }, null, true,
+        "Error Message %s", 1);
+    try {
+      invokeMethod(Preconditions.class, "checkArgument",
+          new Class[] { boolean.class, String.class, int.class }, null, false,
+          "Error Message %s", 1);
+      fail();
+    } catch (InvocationTargetException e) {
+      Throwable cause = e.getCause();
+      assertEquals(IllegalArgumentException.class, cause.getClass());
+      assertEquals("Error Message 1", cause.getMessage());
+    }
+  }
+
+  @Test
+  public void checkPreconditionsCheckArgumentLongParam() throws Exception {
+    invokeMethod(Preconditions.class, "checkArgument",
+        new Class[] { boolean.class, String.class, long.class }, null, true,
+        "Error Message %s", 2L);
+    try {
+      invokeMethod(Preconditions.class, "checkArgument",
+          new Class[] { boolean.class, String.class, long.class }, null, false,
+          "Error Message %s", 2L);
+      fail();
+    } catch (InvocationTargetException e) {
+      Throwable cause = e.getCause();
+      assertEquals(IllegalArgumentException.class, cause.getClass());
+      assertEquals("Error Message 2", cause.getMessage());
+    }
+  }
+
+  @Test
+  public void checkPreconditionsCheckArgumentLongLongParam() throws Exception {
+    invokeMethod(Preconditions.class, "checkArgument",
+        new Class[] { boolean.class, String.class, long.class, long.class },
+        null, true, "Error Message %s %s", 3L, 4L);
+    try {
+      invokeMethod(Preconditions.class, "checkArgument",
+          new Class[] { boolean.class, String.class, long.class, long.class },
+          null, false, "Error Message %s %s", 3L, 4L);
+      fail();
+    } catch (InvocationTargetException e) {
+      Throwable cause = e.getCause();
+      assertEquals(IllegalArgumentException.class, cause.getClass());
+      assertEquals("Error Message 3 4", cause.getMessage());
+    }
+  }
+
+  @Test
+  public void checkPreconditionsCheckNotNullIntParam() throws Exception {
+    invokeMethod(Preconditions.class, "checkNotNull",
+        new Class[] { Object.class, String.class, int.class }, null, this,
+        "Error Message %s", 5);
+    try {
+      invokeMethod(Preconditions.class, "checkNotNull",
+          new Class[] { Object.class, String.class, int.class }, null, null,
+          "Error Message %s", 5);
+      fail();
+    } catch (InvocationTargetException e) {
+      Throwable cause = e.getCause();
+      assertEquals(NullPointerException.class, cause.getClass());
+      assertEquals("Error Message 5", cause.getMessage());
+    }
+  }
+
+  @Test
+  public void checkPreconditionsCheckStateIntParam() throws Exception {
+    invokeMethod(Preconditions.class, "checkState",
+        new Class[] { boolean.class, String.class, int.class }, null, true,
+        "Error Message %s", 6);
+    try {
+      invokeMethod(Preconditions.class, "checkState",
+          new Class[] { boolean.class, String.class, int.class }, null, false,
+          "Error Message %s", 6);
+      fail();
+    } catch (InvocationTargetException e) {
+      Throwable cause = e.getCause();
+      assertEquals(IllegalStateException.class, cause.getClass());
+      assertEquals("Error Message 6", cause.getMessage());
+    }
+  }
+
+  @Test
+  public void checkPreconditionsCheckNotNullVarargs() throws Exception {
+    checkPreconditionsCheckVarargMethod("checkNotNull", Object.class, this,
+        null, NullPointerException.class);
+  }
+
+  @Test
+  public void checkPreconditionsCheckArgumentVarargs() throws Exception {
+    checkPreconditionsCheckVarargMethod("checkArgument", boolean.class, true,
+        false, IllegalArgumentException.class);
+  }
+
+  @Test
+  public void checkPreconditionsCheckStateVarargs() throws Exception {
+    checkPreconditionsCheckVarargMethod("checkState", boolean.class, true,
+        false, IllegalStateException.class);
+  }
+
+  private <T> void checkPreconditionsCheckVarargMethod(String methodName,
+      Class<T> argClass, T goodValue, T badValue,
+      Class<? extends Throwable> exceptionClass) throws Exception {
+    for (int i = 1; i < 5; i++) {
+      String[] templatePlaceholders = new String[i];
+      Arrays.fill(templatePlaceholders, "%s");
+
+      Object[] templateArguments = new Object[i];
+      Arrays.setAll(templateArguments, Integer::valueOf);
+
+      String template = "Error message: "
+          + Stream.of(templatePlaceholders).collect(Collectors.joining(","));
+      String message = "Error message: " + Stream.of(templateArguments)
+          .map(Object::toString).collect(Collectors.joining(","));
+
+      Class<?>[] parameterTypes = new Class[2 + i];
+      parameterTypes[0] = argClass;
+      parameterTypes[1] = String.class;
+      Arrays.fill(parameterTypes, 2, parameterTypes.length, Object.class);
+
+      Object[] parameters = new Object[2 + i];
+      parameters[0] = goodValue;
+      parameters[1] = template;
+      System.arraycopy(templateArguments, 0, parameters, 2, i);
+
+      // Check successful call
+      invokeMethod(Preconditions.class, methodName, parameterTypes, null,
+          parameters);
+
+      try {
+        parameters[0] = badValue;
+        invokeMethod(Preconditions.class, methodName, parameterTypes, null,
+            parameters);
+        fail();
+      } catch (InvocationTargetException e) {
+        Throwable cause = e.getCause();
+        assertEquals(exceptionClass, cause.getClass());
+        assertEquals(message, cause.getMessage());
+      }
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  private static <T> T invokeMethod(Class<?> clazz, String methodName,
+      Class<?>[] argumentTypes, Object object, Object... arguments)
+      throws Exception {
+    Method method = clazz.getMethod(methodName, argumentTypes);
+    return (T) method.invoke(object, arguments);
+  }
+
+}
diff --git a/src/main/resources/checkstyle-config.xml 
b/src/main/resources/checkstyle-config.xml
index f444cc4..488985d 100644
--- a/src/main/resources/checkstyle-config.xml
+++ b/src/main/resources/checkstyle-config.xml
@@ -41,6 +41,7 @@
     <module name="EmptyStatement"/>
     <module name="NoWhitespaceBefore"/>
     <module name="OneStatementPerLine"/>
+    <module name="SuppressionCommentFilter"/>
 
   </module>
 

Reply via email to