You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@avro.apache.org by "nielsbasjes (via GitHub)" <gi...@apache.org> on 2023/02/10 12:46:24 UTC

[GitHub] [avro] nielsbasjes opened a new pull request, #2090: [AVRO-3713]: [Java] Fix Map synchronization regression

nielsbasjes opened a new pull request, #2090:
URL: https://github.com/apache/avro/pull/2090

   ## What is the purpose of the change
   
   *Fix synchronization regression*
   
   https://issues.apache.org/jira/browse/AVRO-3713
   
   ## Verifying this change
   
   All tests pass.
   I'm in doubt about dropping the "weak keys".
   
   ## Documentation
   
   - Does this pull request introduce a new feature? no
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: dev-unsubscribe@avro.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [avro] nielsbasjes commented on pull request #2090: [AVRO-3713]: [Java] Fix Map synchronization regression

Posted by "nielsbasjes (via GitHub)" <gi...@apache.org>.
nielsbasjes commented on PR #2090:
URL: https://github.com/apache/avro/pull/2090#issuecomment-1434475146

   @martin-g Because we got rid of Guava an old synchronization problem was reintroduced.
   My (brute) fix was to simply copy the correct implementation from Spring and simply include it.
   Note that most classes are package private and only the Map implementation itself has been made public.
   
   I put all of the copied classes into a `springframework` package to indicate where it came from.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@avro.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [avro] nielsbasjes commented on pull request #2090: [AVRO-3713]: [Java] Fix Map synchronization regression

Posted by "nielsbasjes (via GitHub)" <gi...@apache.org>.
nielsbasjes commented on PR #2090:
URL: https://github.com/apache/avro/pull/2090#issuecomment-1433117798

   I agree with the "bighammer" remark. Yet when it comes to tricky software like this I prefer to rely on the proven code by others.
   
   I put it in a separate package `springframework` because I first wanted to see it this actually works and I like to be open about having fully copied a class from a separate project.
   I see this as a "manual shading" of this "dependency".
   
   I'm totally fine with putting it all in utils or some other package.
   
   I'll copy the tests for this class also.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@avro.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [avro] github-code-scanning[bot] commented on a diff in pull request #2090: [AVRO-3713]: [Java] Fix Map synchronization regression

Posted by "github-code-scanning[bot] (via GitHub)" <gi...@apache.org>.
github-code-scanning[bot] commented on code in PR #2090:
URL: https://github.com/apache/avro/pull/2090#discussion_r1106539893


##########
lang/java/avro/src/main/java/org/apache/avro/util/springframework/ObjectUtils.java:
##########
@@ -0,0 +1,322 @@
+/*
+ * Copyright 2002-2021 the original author or authors.
+ *
+ * Licensed 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
+ *
+ *      https://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.avro.util.springframework;
+
+import org.apache.avro.reflect.Nullable;
+import org.apache.avro.util.ClassUtils;
+
+import java.util.Arrays;
+
+/**
+ * Miscellaneous object utility methods.
+ *
+ * <p>Mainly for internal use within the framework.
+ *
+ * <p>Thanks to Alex Ruiz for contributing several enhancements to this class!
+ *
+ * @author Juergen Hoeller
+ * @author Keith Donald
+ * @author Rod Johnson
+ * @author Rob Harrop
+ * @author Chris Beams
+ * @author Sam Brannen
+ * @see ClassUtils
+ * see CollectionUtils
+ * see StringUtils
+ * @since 19.03.2004
+ */
+class ObjectUtils {
+  private ObjectUtils() {
+  }
+
+  private static final int INITIAL_HASH = 7;
+  private static final int MULTIPLIER = 31;
+
+
+  /**
+   * Determine whether the given array is empty:
+   * i.e. {@code null} or of zero length.
+   *
+   * @param array the array to check
+   */
+  public static boolean isEmpty(@Nullable Object[] array) {
+    return (array == null || array.length == 0);
+  }
+
+
+  //---------------------------------------------------------------------
+  // Convenience methods for content-based equality/hash-code handling
+  //---------------------------------------------------------------------
+
+  /**
+   * Determine if the given objects are equal, returning {@code true} if
+   * both are {@code null} or {@code false} if only one is {@code null}.
+   * <p>Compares arrays with {@code Arrays.equals}, performing an equality
+   * check based on the array elements rather than the array reference.
+   *
+   * @param o1 first Object to compare
+   * @param o2 second Object to compare
+   * @return whether the given objects are equal
+   * @see Object#equals(Object)
+   * @see Arrays#equals
+   */
+  public static boolean nullSafeEquals(@Nullable Object o1, @Nullable Object o2) {
+    if (o1 == o2) {
+      return true;
+    }
+    if (o1 == null || o2 == null) {
+      return false;
+    }
+    if (o1.equals(o2)) {
+      return true;
+    }
+    if (o1.getClass().isArray() && o2.getClass().isArray()) {
+      return arrayEquals(o1, o2);
+    }
+    return false;
+  }
+
+  /**
+   * Compare the given arrays with {@code Arrays.equals}, performing an equality
+   * check based on the array elements rather than the array reference.
+   *
+   * @param o1 first array to compare
+   * @param o2 second array to compare
+   * @return whether the given objects are equal
+   * @see #nullSafeEquals(Object, Object)
+   * @see Arrays#equals
+   */
+  private static boolean arrayEquals(Object o1, Object o2) {
+    if (o1 instanceof Object[] && o2 instanceof Object[]) {
+      return Arrays.equals((Object[]) o1, (Object[]) o2);
+    }
+    if (o1 instanceof boolean[] && o2 instanceof boolean[]) {
+      return Arrays.equals((boolean[]) o1, (boolean[]) o2);
+    }
+    if (o1 instanceof byte[] && o2 instanceof byte[]) {
+      return Arrays.equals((byte[]) o1, (byte[]) o2);
+    }
+    if (o1 instanceof char[] && o2 instanceof char[]) {
+      return Arrays.equals((char[]) o1, (char[]) o2);
+    }
+    if (o1 instanceof double[] && o2 instanceof double[]) {
+      return Arrays.equals((double[]) o1, (double[]) o2);
+    }
+    if (o1 instanceof float[] && o2 instanceof float[]) {
+      return Arrays.equals((float[]) o1, (float[]) o2);
+    }
+    if (o1 instanceof int[] && o2 instanceof int[]) {
+      return Arrays.equals((int[]) o1, (int[]) o2);
+    }
+    if (o1 instanceof long[] && o2 instanceof long[]) {
+      return Arrays.equals((long[]) o1, (long[]) o2);
+    }
+    if (o1 instanceof short[] && o2 instanceof short[]) {
+      return Arrays.equals((short[]) o1, (short[]) o2);
+    }
+    return false;
+  }
+
+  /**
+   * Return as hash code for the given object; typically the value of
+   * {@code Object#hashCode()}}. If the object is an array,
+   * this method will delegate to any of the {@code nullSafeHashCode}
+   * methods for arrays in this class. If the object is {@code null},
+   * this method returns 0.
+   *
+   * @see Object#hashCode()
+   * @see #nullSafeHashCode(Object[])
+   * @see #nullSafeHashCode(boolean[])
+   * @see #nullSafeHashCode(byte[])
+   * @see #nullSafeHashCode(char[])
+   * @see #nullSafeHashCode(double[])
+   * @see #nullSafeHashCode(float[])
+   * @see #nullSafeHashCode(int[])
+   * @see #nullSafeHashCode(long[])
+   * @see #nullSafeHashCode(short[])
+   */
+  public static int nullSafeHashCode(@Nullable Object obj) {
+    if (obj == null) {
+      return 0;
+    }
+    if (obj.getClass().isArray()) {
+      if (obj instanceof Object[]) {
+        return nullSafeHashCode((Object[]) obj);
+      }
+      if (obj instanceof boolean[]) {
+        return nullSafeHashCode((boolean[]) obj);
+      }
+      if (obj instanceof byte[]) {
+        return nullSafeHashCode((byte[]) obj);
+      }
+      if (obj instanceof char[]) {
+        return nullSafeHashCode((char[]) obj);
+      }
+      if (obj instanceof double[]) {
+        return nullSafeHashCode((double[]) obj);
+      }
+      if (obj instanceof float[]) {
+        return nullSafeHashCode((float[]) obj);
+      }
+      if (obj instanceof int[]) {
+        return nullSafeHashCode((int[]) obj);
+      }
+      if (obj instanceof long[]) {
+        return nullSafeHashCode((long[]) obj);
+      }
+      if (obj instanceof short[]) {
+        return nullSafeHashCode((short[]) obj);
+      }
+    }
+    return obj.hashCode();
+  }
+
+  /**
+   * Return a hash code based on the contents of the specified array.
+   * If {@code array} is {@code null}, this method returns 0.
+   */
+  public static int nullSafeHashCode(@Nullable Object[] array) {
+    if (array == null) {
+      return 0;
+    }
+    int hash = INITIAL_HASH;
+    for (Object element : array) {
+      hash = MULTIPLIER * hash + nullSafeHashCode(element);
+    }
+    return hash;
+  }
+
+  /**
+   * Return a hash code based on the contents of the specified array.
+   * If {@code array} is {@code null}, this method returns 0.
+   */
+  public static int nullSafeHashCode(@Nullable boolean[] array) {
+    if (array == null) {
+      return 0;
+    }
+    int hash = INITIAL_HASH;
+    for (boolean element : array) {
+      hash = MULTIPLIER * hash + Boolean.hashCode(element);
+    }
+    return hash;
+  }
+
+  /**
+   * Return a hash code based on the contents of the specified array.
+   * If {@code array} is {@code null}, this method returns 0.
+   */
+  public static int nullSafeHashCode(@Nullable byte[] array) {
+    if (array == null) {
+      return 0;
+    }
+    int hash = INITIAL_HASH;
+    for (byte element : array) {
+      hash = MULTIPLIER * hash + element;
+    }
+    return hash;
+  }
+
+  /**
+   * Return a hash code based on the contents of the specified array.
+   * If {@code array} is {@code null}, this method returns 0.
+   */
+  public static int nullSafeHashCode(@Nullable char[] array) {
+    if (array == null) {
+      return 0;
+    }
+    int hash = INITIAL_HASH;
+    for (char element : array) {
+      hash = MULTIPLIER * hash + element;
+    }
+    return hash;
+  }
+
+  /**
+   * Return a hash code based on the contents of the specified array.
+   * If {@code array} is {@code null}, this method returns 0.
+   */
+  public static int nullSafeHashCode(@Nullable double[] array) {
+    if (array == null) {
+      return 0;
+    }
+    int hash = INITIAL_HASH;
+    for (double element : array) {
+      hash = MULTIPLIER * hash + Double.hashCode(element);
+    }
+    return hash;
+  }
+
+  /**
+   * Return a hash code based on the contents of the specified array.
+   * If {@code array} is {@code null}, this method returns 0.
+   */
+  public static int nullSafeHashCode(@Nullable float[] array) {

Review Comment:
   ## Confusing overloading of methods
   
   Method ObjectUtils.nullSafeHashCode(..) could be confused with overloaded method [nullSafeHashCode](1), since dispatch depends on static types.
   
   [Show more details](https://github.com/apache/avro/security/code-scanning/2969)



##########
lang/java/avro/src/main/java/org/apache/avro/util/springframework/ObjectUtils.java:
##########
@@ -0,0 +1,322 @@
+/*
+ * Copyright 2002-2021 the original author or authors.
+ *
+ * Licensed 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
+ *
+ *      https://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.avro.util.springframework;
+
+import org.apache.avro.reflect.Nullable;
+import org.apache.avro.util.ClassUtils;
+
+import java.util.Arrays;
+
+/**
+ * Miscellaneous object utility methods.
+ *
+ * <p>Mainly for internal use within the framework.
+ *
+ * <p>Thanks to Alex Ruiz for contributing several enhancements to this class!
+ *
+ * @author Juergen Hoeller
+ * @author Keith Donald
+ * @author Rod Johnson
+ * @author Rob Harrop
+ * @author Chris Beams
+ * @author Sam Brannen
+ * @see ClassUtils
+ * see CollectionUtils
+ * see StringUtils
+ * @since 19.03.2004
+ */
+class ObjectUtils {
+  private ObjectUtils() {
+  }
+
+  private static final int INITIAL_HASH = 7;
+  private static final int MULTIPLIER = 31;
+
+
+  /**
+   * Determine whether the given array is empty:
+   * i.e. {@code null} or of zero length.
+   *
+   * @param array the array to check
+   */
+  public static boolean isEmpty(@Nullable Object[] array) {
+    return (array == null || array.length == 0);
+  }
+
+
+  //---------------------------------------------------------------------
+  // Convenience methods for content-based equality/hash-code handling
+  //---------------------------------------------------------------------
+
+  /**
+   * Determine if the given objects are equal, returning {@code true} if
+   * both are {@code null} or {@code false} if only one is {@code null}.
+   * <p>Compares arrays with {@code Arrays.equals}, performing an equality
+   * check based on the array elements rather than the array reference.
+   *
+   * @param o1 first Object to compare
+   * @param o2 second Object to compare
+   * @return whether the given objects are equal
+   * @see Object#equals(Object)
+   * @see Arrays#equals
+   */
+  public static boolean nullSafeEquals(@Nullable Object o1, @Nullable Object o2) {
+    if (o1 == o2) {
+      return true;
+    }
+    if (o1 == null || o2 == null) {
+      return false;
+    }
+    if (o1.equals(o2)) {
+      return true;
+    }
+    if (o1.getClass().isArray() && o2.getClass().isArray()) {
+      return arrayEquals(o1, o2);
+    }
+    return false;
+  }
+
+  /**
+   * Compare the given arrays with {@code Arrays.equals}, performing an equality
+   * check based on the array elements rather than the array reference.
+   *
+   * @param o1 first array to compare
+   * @param o2 second array to compare
+   * @return whether the given objects are equal
+   * @see #nullSafeEquals(Object, Object)
+   * @see Arrays#equals
+   */
+  private static boolean arrayEquals(Object o1, Object o2) {
+    if (o1 instanceof Object[] && o2 instanceof Object[]) {
+      return Arrays.equals((Object[]) o1, (Object[]) o2);
+    }
+    if (o1 instanceof boolean[] && o2 instanceof boolean[]) {
+      return Arrays.equals((boolean[]) o1, (boolean[]) o2);
+    }
+    if (o1 instanceof byte[] && o2 instanceof byte[]) {
+      return Arrays.equals((byte[]) o1, (byte[]) o2);
+    }
+    if (o1 instanceof char[] && o2 instanceof char[]) {
+      return Arrays.equals((char[]) o1, (char[]) o2);
+    }
+    if (o1 instanceof double[] && o2 instanceof double[]) {
+      return Arrays.equals((double[]) o1, (double[]) o2);
+    }
+    if (o1 instanceof float[] && o2 instanceof float[]) {
+      return Arrays.equals((float[]) o1, (float[]) o2);
+    }
+    if (o1 instanceof int[] && o2 instanceof int[]) {
+      return Arrays.equals((int[]) o1, (int[]) o2);
+    }
+    if (o1 instanceof long[] && o2 instanceof long[]) {
+      return Arrays.equals((long[]) o1, (long[]) o2);
+    }
+    if (o1 instanceof short[] && o2 instanceof short[]) {
+      return Arrays.equals((short[]) o1, (short[]) o2);
+    }
+    return false;
+  }
+
+  /**
+   * Return as hash code for the given object; typically the value of
+   * {@code Object#hashCode()}}. If the object is an array,
+   * this method will delegate to any of the {@code nullSafeHashCode}
+   * methods for arrays in this class. If the object is {@code null},
+   * this method returns 0.
+   *
+   * @see Object#hashCode()
+   * @see #nullSafeHashCode(Object[])
+   * @see #nullSafeHashCode(boolean[])
+   * @see #nullSafeHashCode(byte[])
+   * @see #nullSafeHashCode(char[])
+   * @see #nullSafeHashCode(double[])
+   * @see #nullSafeHashCode(float[])
+   * @see #nullSafeHashCode(int[])
+   * @see #nullSafeHashCode(long[])
+   * @see #nullSafeHashCode(short[])
+   */
+  public static int nullSafeHashCode(@Nullable Object obj) {
+    if (obj == null) {
+      return 0;
+    }
+    if (obj.getClass().isArray()) {
+      if (obj instanceof Object[]) {
+        return nullSafeHashCode((Object[]) obj);
+      }
+      if (obj instanceof boolean[]) {
+        return nullSafeHashCode((boolean[]) obj);
+      }
+      if (obj instanceof byte[]) {
+        return nullSafeHashCode((byte[]) obj);
+      }
+      if (obj instanceof char[]) {
+        return nullSafeHashCode((char[]) obj);
+      }
+      if (obj instanceof double[]) {
+        return nullSafeHashCode((double[]) obj);
+      }
+      if (obj instanceof float[]) {
+        return nullSafeHashCode((float[]) obj);
+      }
+      if (obj instanceof int[]) {
+        return nullSafeHashCode((int[]) obj);
+      }
+      if (obj instanceof long[]) {
+        return nullSafeHashCode((long[]) obj);
+      }
+      if (obj instanceof short[]) {
+        return nullSafeHashCode((short[]) obj);
+      }
+    }
+    return obj.hashCode();
+  }
+
+  /**
+   * Return a hash code based on the contents of the specified array.
+   * If {@code array} is {@code null}, this method returns 0.
+   */
+  public static int nullSafeHashCode(@Nullable Object[] array) {
+    if (array == null) {
+      return 0;
+    }
+    int hash = INITIAL_HASH;
+    for (Object element : array) {
+      hash = MULTIPLIER * hash + nullSafeHashCode(element);
+    }
+    return hash;
+  }
+
+  /**
+   * Return a hash code based on the contents of the specified array.
+   * If {@code array} is {@code null}, this method returns 0.
+   */
+  public static int nullSafeHashCode(@Nullable boolean[] array) {
+    if (array == null) {
+      return 0;
+    }
+    int hash = INITIAL_HASH;
+    for (boolean element : array) {
+      hash = MULTIPLIER * hash + Boolean.hashCode(element);
+    }
+    return hash;
+  }
+
+  /**
+   * Return a hash code based on the contents of the specified array.
+   * If {@code array} is {@code null}, this method returns 0.
+   */
+  public static int nullSafeHashCode(@Nullable byte[] array) {
+    if (array == null) {
+      return 0;
+    }
+    int hash = INITIAL_HASH;
+    for (byte element : array) {
+      hash = MULTIPLIER * hash + element;
+    }
+    return hash;
+  }
+
+  /**
+   * Return a hash code based on the contents of the specified array.
+   * If {@code array} is {@code null}, this method returns 0.
+   */
+  public static int nullSafeHashCode(@Nullable char[] array) {
+    if (array == null) {
+      return 0;
+    }
+    int hash = INITIAL_HASH;
+    for (char element : array) {
+      hash = MULTIPLIER * hash + element;
+    }
+    return hash;
+  }
+
+  /**
+   * Return a hash code based on the contents of the specified array.
+   * If {@code array} is {@code null}, this method returns 0.
+   */
+  public static int nullSafeHashCode(@Nullable double[] array) {
+    if (array == null) {
+      return 0;
+    }
+    int hash = INITIAL_HASH;
+    for (double element : array) {
+      hash = MULTIPLIER * hash + Double.hashCode(element);
+    }
+    return hash;
+  }
+
+  /**
+   * Return a hash code based on the contents of the specified array.
+   * If {@code array} is {@code null}, this method returns 0.
+   */
+  public static int nullSafeHashCode(@Nullable float[] array) {
+    if (array == null) {
+      return 0;
+    }
+    int hash = INITIAL_HASH;
+    for (float element : array) {
+      hash = MULTIPLIER * hash + Float.hashCode(element);
+    }
+    return hash;
+  }
+
+  /**
+   * Return a hash code based on the contents of the specified array.
+   * If {@code array} is {@code null}, this method returns 0.
+   */
+  public static int nullSafeHashCode(@Nullable int[] array) {

Review Comment:
   ## Confusing overloading of methods
   
   Method ObjectUtils.nullSafeHashCode(..) could be confused with overloaded method [nullSafeHashCode](1), since dispatch depends on static types.
   
   [Show more details](https://github.com/apache/avro/security/code-scanning/2968)



##########
lang/java/avro/src/main/java/org/apache/avro/util/springframework/ObjectUtils.java:
##########
@@ -0,0 +1,322 @@
+/*
+ * Copyright 2002-2021 the original author or authors.
+ *
+ * Licensed 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
+ *
+ *      https://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.avro.util.springframework;
+
+import org.apache.avro.reflect.Nullable;
+import org.apache.avro.util.ClassUtils;
+
+import java.util.Arrays;
+
+/**
+ * Miscellaneous object utility methods.
+ *
+ * <p>Mainly for internal use within the framework.
+ *
+ * <p>Thanks to Alex Ruiz for contributing several enhancements to this class!
+ *
+ * @author Juergen Hoeller
+ * @author Keith Donald
+ * @author Rod Johnson
+ * @author Rob Harrop
+ * @author Chris Beams
+ * @author Sam Brannen
+ * @see ClassUtils
+ * see CollectionUtils
+ * see StringUtils
+ * @since 19.03.2004
+ */
+class ObjectUtils {
+  private ObjectUtils() {
+  }
+
+  private static final int INITIAL_HASH = 7;
+  private static final int MULTIPLIER = 31;
+
+
+  /**
+   * Determine whether the given array is empty:
+   * i.e. {@code null} or of zero length.
+   *
+   * @param array the array to check
+   */
+  public static boolean isEmpty(@Nullable Object[] array) {
+    return (array == null || array.length == 0);
+  }
+
+
+  //---------------------------------------------------------------------
+  // Convenience methods for content-based equality/hash-code handling
+  //---------------------------------------------------------------------
+
+  /**
+   * Determine if the given objects are equal, returning {@code true} if
+   * both are {@code null} or {@code false} if only one is {@code null}.
+   * <p>Compares arrays with {@code Arrays.equals}, performing an equality
+   * check based on the array elements rather than the array reference.
+   *
+   * @param o1 first Object to compare
+   * @param o2 second Object to compare
+   * @return whether the given objects are equal
+   * @see Object#equals(Object)
+   * @see Arrays#equals
+   */
+  public static boolean nullSafeEquals(@Nullable Object o1, @Nullable Object o2) {
+    if (o1 == o2) {
+      return true;
+    }
+    if (o1 == null || o2 == null) {
+      return false;
+    }
+    if (o1.equals(o2)) {
+      return true;
+    }
+    if (o1.getClass().isArray() && o2.getClass().isArray()) {
+      return arrayEquals(o1, o2);
+    }
+    return false;
+  }
+
+  /**
+   * Compare the given arrays with {@code Arrays.equals}, performing an equality
+   * check based on the array elements rather than the array reference.
+   *
+   * @param o1 first array to compare
+   * @param o2 second array to compare
+   * @return whether the given objects are equal
+   * @see #nullSafeEquals(Object, Object)
+   * @see Arrays#equals
+   */
+  private static boolean arrayEquals(Object o1, Object o2) {
+    if (o1 instanceof Object[] && o2 instanceof Object[]) {
+      return Arrays.equals((Object[]) o1, (Object[]) o2);
+    }
+    if (o1 instanceof boolean[] && o2 instanceof boolean[]) {
+      return Arrays.equals((boolean[]) o1, (boolean[]) o2);
+    }
+    if (o1 instanceof byte[] && o2 instanceof byte[]) {
+      return Arrays.equals((byte[]) o1, (byte[]) o2);
+    }
+    if (o1 instanceof char[] && o2 instanceof char[]) {
+      return Arrays.equals((char[]) o1, (char[]) o2);
+    }
+    if (o1 instanceof double[] && o2 instanceof double[]) {
+      return Arrays.equals((double[]) o1, (double[]) o2);
+    }
+    if (o1 instanceof float[] && o2 instanceof float[]) {
+      return Arrays.equals((float[]) o1, (float[]) o2);
+    }
+    if (o1 instanceof int[] && o2 instanceof int[]) {
+      return Arrays.equals((int[]) o1, (int[]) o2);
+    }
+    if (o1 instanceof long[] && o2 instanceof long[]) {
+      return Arrays.equals((long[]) o1, (long[]) o2);
+    }
+    if (o1 instanceof short[] && o2 instanceof short[]) {
+      return Arrays.equals((short[]) o1, (short[]) o2);
+    }
+    return false;
+  }
+
+  /**
+   * Return as hash code for the given object; typically the value of
+   * {@code Object#hashCode()}}. If the object is an array,
+   * this method will delegate to any of the {@code nullSafeHashCode}
+   * methods for arrays in this class. If the object is {@code null},
+   * this method returns 0.
+   *
+   * @see Object#hashCode()
+   * @see #nullSafeHashCode(Object[])
+   * @see #nullSafeHashCode(boolean[])
+   * @see #nullSafeHashCode(byte[])
+   * @see #nullSafeHashCode(char[])
+   * @see #nullSafeHashCode(double[])
+   * @see #nullSafeHashCode(float[])
+   * @see #nullSafeHashCode(int[])
+   * @see #nullSafeHashCode(long[])
+   * @see #nullSafeHashCode(short[])
+   */
+  public static int nullSafeHashCode(@Nullable Object obj) {
+    if (obj == null) {
+      return 0;
+    }
+    if (obj.getClass().isArray()) {
+      if (obj instanceof Object[]) {
+        return nullSafeHashCode((Object[]) obj);
+      }
+      if (obj instanceof boolean[]) {
+        return nullSafeHashCode((boolean[]) obj);
+      }
+      if (obj instanceof byte[]) {
+        return nullSafeHashCode((byte[]) obj);
+      }
+      if (obj instanceof char[]) {
+        return nullSafeHashCode((char[]) obj);
+      }
+      if (obj instanceof double[]) {
+        return nullSafeHashCode((double[]) obj);
+      }
+      if (obj instanceof float[]) {
+        return nullSafeHashCode((float[]) obj);
+      }
+      if (obj instanceof int[]) {
+        return nullSafeHashCode((int[]) obj);
+      }
+      if (obj instanceof long[]) {
+        return nullSafeHashCode((long[]) obj);
+      }
+      if (obj instanceof short[]) {
+        return nullSafeHashCode((short[]) obj);
+      }
+    }
+    return obj.hashCode();
+  }
+
+  /**
+   * Return a hash code based on the contents of the specified array.
+   * If {@code array} is {@code null}, this method returns 0.
+   */
+  public static int nullSafeHashCode(@Nullable Object[] array) {
+    if (array == null) {
+      return 0;
+    }
+    int hash = INITIAL_HASH;
+    for (Object element : array) {
+      hash = MULTIPLIER * hash + nullSafeHashCode(element);
+    }
+    return hash;
+  }
+
+  /**
+   * Return a hash code based on the contents of the specified array.
+   * If {@code array} is {@code null}, this method returns 0.
+   */
+  public static int nullSafeHashCode(@Nullable boolean[] array) {

Review Comment:
   ## Confusing overloading of methods
   
   Method ObjectUtils.nullSafeHashCode(..) could be confused with overloaded method [nullSafeHashCode](1), since dispatch depends on static types.
   
   [Show more details](https://github.com/apache/avro/security/code-scanning/2973)



##########
lang/java/avro/src/main/java/org/apache/avro/util/springframework/ConcurrentReferenceHashMap.java:
##########
@@ -0,0 +1,1112 @@
+/*
+ * Copyright 2002-2021 the original author or authors.
+ *
+ * Licensed 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
+ *
+ *      https://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.avro.util.springframework;
+
+import org.apache.avro.reflect.Nullable;
+
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Array;
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * A {@link ConcurrentHashMap} that uses {@link ReferenceType#SOFT soft} or
+ * {@linkplain ReferenceType#WEAK weak} references for both {@code keys} and {@code values}.
+ *
+ * <p>This class can be used as an alternative to
+ * {@code Collections.synchronizedMap(new WeakHashMap<K, Reference<V>>())} in order to
+ * support better performance when accessed concurrently. This implementation follows the
+ * same design constraints as {@link ConcurrentHashMap} with the exception that
+ * {@code null} values and {@code null} keys are supported.
+ *
+ * <p><b>NOTE:</b> The use of references means that there is no guarantee that items
+ * placed into the map will be subsequently available. The garbage collector may discard
+ * references at any time, so it may appear that an unknown thread is silently removing
+ * entries.
+ *
+ * <p>If not explicitly specified, this implementation will use
+ * {@linkplain SoftReference soft entry references}.
+ *
+ * @param <K> the key type
+ * @param <V> the value type
+ * @author Phillip Webb
+ * @author Juergen Hoeller
+ * @since 3.2
+ */
+public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V> {
+
+  private static final int DEFAULT_INITIAL_CAPACITY = 16;
+
+  private static final float DEFAULT_LOAD_FACTOR = 0.75f;
+
+  private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
+
+  private static final ReferenceType DEFAULT_REFERENCE_TYPE = ReferenceType.SOFT;
+
+  private static final int MAXIMUM_CONCURRENCY_LEVEL = 1 << 16;
+
+  private static final int MAXIMUM_SEGMENT_SIZE = 1 << 30;
+
+
+  /**
+   * Array of segments indexed using the high order bits from the hash.
+   */
+  private final Segment[] segments;
+
+  /**
+   * When the average number of references per table exceeds this value resize will be attempted.
+   */
+  private final float loadFactor;
+
+  /**
+   * The reference type: SOFT or WEAK.
+   */
+  private final ReferenceType referenceType;
+
+  /**
+   * The shift value used to calculate the size of the segments array and an index from the hash.
+   */
+  private final int shift;
+
+  /**
+   * Late binding entry set.
+   */
+  @Nullable
+  private volatile Set<Map.Entry<K, V>> entrySet;
+
+
+  /**
+   * Create a new {@code ConcurrentReferenceHashMap} instance.
+   */
+  public ConcurrentReferenceHashMap() {
+    this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE);
+  }
+
+  /**
+   * Create a new {@code ConcurrentReferenceHashMap} instance.
+   *
+   * @param initialCapacity the initial capacity of the map
+   */
+  public ConcurrentReferenceHashMap(int initialCapacity) {
+    this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE);
+  }
+
+  /**
+   * Create a new {@code ConcurrentReferenceHashMap} instance.
+   *
+   * @param initialCapacity the initial capacity of the map
+   * @param loadFactor      the load factor. When the average number of references per table
+   *                        exceeds this value resize will be attempted
+   */
+  public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor) {
+    this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE);
+  }
+
+  /**
+   * Create a new {@code ConcurrentReferenceHashMap} instance.
+   *
+   * @param initialCapacity  the initial capacity of the map
+   * @param concurrencyLevel the expected number of threads that will concurrently
+   *                         write to the map
+   */
+  public ConcurrentReferenceHashMap(int initialCapacity, int concurrencyLevel) {
+    this(initialCapacity, DEFAULT_LOAD_FACTOR, concurrencyLevel, DEFAULT_REFERENCE_TYPE);
+  }
+
+  /**
+   * Create a new {@code ConcurrentReferenceHashMap} instance.
+   *
+   * @param initialCapacity the initial capacity of the map
+   * @param referenceType   the reference type used for entries (soft or weak)
+   */
+  public ConcurrentReferenceHashMap(int initialCapacity, ReferenceType referenceType) {
+    this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, referenceType);
+  }
+
+  /**
+   * Create a new {@code ConcurrentReferenceHashMap} instance.
+   *
+   * @param initialCapacity  the initial capacity of the map
+   * @param loadFactor       the load factor. When the average number of references per
+   *                         table exceeds this value, resize will be attempted.
+   * @param concurrencyLevel the expected number of threads that will concurrently
+   *                         write to the map
+   */
+  public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {
+    this(initialCapacity, loadFactor, concurrencyLevel, DEFAULT_REFERENCE_TYPE);
+  }
+
+  /**
+   * Create a new {@code ConcurrentReferenceHashMap} instance.
+   *
+   * @param initialCapacity  the initial capacity of the map
+   * @param loadFactor       the load factor. When the average number of references per
+   *                         table exceeds this value, resize will be attempted.
+   * @param concurrencyLevel the expected number of threads that will concurrently
+   *                         write to the map
+   * @param referenceType    the reference type used for entries (soft or weak)
+   */
+  @SuppressWarnings("unchecked")
+  public ConcurrentReferenceHashMap(
+    int initialCapacity, float loadFactor, int concurrencyLevel, ReferenceType referenceType) {
+
+    Assert.isTrue(initialCapacity >= 0, "Initial capacity must not be negative");
+    Assert.isTrue(loadFactor > 0f, "Load factor must be positive");
+    Assert.isTrue(concurrencyLevel > 0, "Concurrency level must be positive");
+    Assert.notNull(referenceType, "Reference type must not be null");
+    this.loadFactor = loadFactor;
+    this.shift = calculateShift(concurrencyLevel, MAXIMUM_CONCURRENCY_LEVEL);
+    int size = 1 << this.shift;
+    this.referenceType = referenceType;
+    int roundedUpSegmentCapacity = (int) ((initialCapacity + size - 1L) / size);
+    int initialSize = 1 << calculateShift(roundedUpSegmentCapacity, MAXIMUM_SEGMENT_SIZE);
+    Segment[] segments = (Segment[]) Array.newInstance(Segment.class, size);
+    int resizeThreshold = (int) (initialSize * getLoadFactor());
+    for (int i = 0; i < segments.length; i++) {
+      segments[i] = new Segment(initialSize, resizeThreshold);
+    }
+    this.segments = segments;
+  }
+
+
+  protected final float getLoadFactor() {
+    return this.loadFactor;
+  }
+
+  protected final int getSegmentsSize() {
+    return this.segments.length;
+  }
+
+  protected final Segment getSegment(int index) {
+    return this.segments[index];
+  }
+
+  /**
+   * Factory method that returns the {@link ReferenceManager}.
+   * This method will be called once for each {@link Segment}.
+   *
+   * @return a new reference manager
+   */
+  protected ReferenceManager createReferenceManager() {
+    return new ReferenceManager();
+  }
+
+  /**
+   * Get the hash for a given object, apply an additional hash function to reduce
+   * collisions. This implementation uses the same Wang/Jenkins algorithm as
+   * {@link ConcurrentHashMap}. Subclasses can override to provide alternative hashing.
+   *
+   * @param o the object to hash (may be null)
+   * @return the resulting hash code
+   */
+  protected int getHash(@Nullable Object o) {
+    int hash = (o != null ? o.hashCode() : 0);
+    hash += (hash << 15) ^ 0xffffcd7d;
+    hash ^= (hash >>> 10);
+    hash += (hash << 3);
+    hash ^= (hash >>> 6);
+    hash += (hash << 2) + (hash << 14);
+    hash ^= (hash >>> 16);
+    return hash;
+  }
+
+  @Override
+  @Nullable
+  public V get(@Nullable Object key) {
+    Reference<K, V> ref = getReference(key, Restructure.WHEN_NECESSARY);
+    Entry<K, V> entry = (ref != null ? ref.get() : null);
+    return (entry != null ? entry.getValue() : null);
+  }
+
+  @Override
+  @Nullable
+  public V getOrDefault(@Nullable Object key, @Nullable V defaultValue) {
+    Reference<K, V> ref = getReference(key, Restructure.WHEN_NECESSARY);
+    Entry<K, V> entry = (ref != null ? ref.get() : null);
+    return (entry != null ? entry.getValue() : defaultValue);
+  }
+
+  @Override
+  public boolean containsKey(@Nullable Object key) {
+    Reference<K, V> ref = getReference(key, Restructure.WHEN_NECESSARY);
+    Entry<K, V> entry = (ref != null ? ref.get() : null);
+    return (entry != null && ObjectUtils.nullSafeEquals(entry.getKey(), key));
+  }
+
+  /**
+   * Return a {@link Reference} to the {@link Entry} for the specified {@code key},
+   * or {@code null} if not found.
+   *
+   * @param key         the key (can be {@code null})
+   * @param restructure types of restructure allowed during this call
+   * @return the reference, or {@code null} if not found
+   */
+  @Nullable
+  protected final Reference<K, V> getReference(@Nullable Object key, Restructure restructure) {
+    int hash = getHash(key);
+    return getSegmentForHash(hash).getReference(key, hash, restructure);
+  }
+
+  @Override
+  @Nullable
+  public V put(@Nullable K key, @Nullable V value) {
+    return put(key, value, true);
+  }
+
+  @Override
+  @Nullable
+  public V putIfAbsent(@Nullable K key, @Nullable V value) {
+    return put(key, value, false);
+  }
+
+  @Nullable
+  private V put(@Nullable final K key, @Nullable final V value, final boolean overwriteExisting) {
+    return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.RESIZE) {
+      @Override
+      @Nullable
+      protected V execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry, @Nullable Entries<V> entries) {
+        if (entry != null) {
+          V oldValue = entry.getValue();
+          if (overwriteExisting) {
+            entry.setValue(value);
+          }
+          return oldValue;
+        }
+        Assert.state(entries != null, "No entries segment");
+        entries.add(value);
+        return null;
+      }
+    });
+  }
+
+  @Override
+  @Nullable
+  public V remove(@Nullable Object key) {
+    return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) {
+      @Override
+      @Nullable
+      protected V execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry) {
+        if (entry != null) {
+          if (ref != null) {
+            ref.release();
+          }
+          return entry.value;
+        }
+        return null;
+      }
+    });
+  }
+
+  @Override
+  public boolean remove(@Nullable Object key, final @Nullable Object value) {
+    Boolean result = doTask(key, new Task<Boolean>(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) {
+      @Override
+      protected Boolean execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry) {
+        if (entry != null && ObjectUtils.nullSafeEquals(entry.getValue(), value)) {
+          if (ref != null) {
+            ref.release();
+          }
+          return true;
+        }
+        return false;
+      }
+    });
+    return (Boolean.TRUE.equals(result));
+  }
+
+  @Override
+  public boolean replace(@Nullable K key, final @Nullable V oldValue, final @Nullable V newValue) {
+    Boolean result = doTask(key, new Task<Boolean>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) {
+      @Override
+      protected Boolean execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry) {
+        if (entry != null && ObjectUtils.nullSafeEquals(entry.getValue(), oldValue)) {
+          entry.setValue(newValue);
+          return true;
+        }
+        return false;
+      }
+    });
+    return (Boolean.TRUE.equals(result));
+  }
+
+  @Override
+  @Nullable
+  public V replace(@Nullable K key, final @Nullable V value) {
+    return doTask(key, new Task<V>(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) {
+      @Override
+      @Nullable
+      protected V execute(@Nullable Reference<K, V> ref, @Nullable Entry<K, V> entry) {
+        if (entry != null) {
+          V oldValue = entry.getValue();
+          entry.setValue(value);
+          return oldValue;
+        }
+        return null;
+      }
+    });
+  }
+
+  @Override
+  public void clear() {
+    for (Segment segment : this.segments) {
+      segment.clear();
+    }
+  }
+
+  /**
+   * Remove any entries that have been garbage collected and are no longer referenced.
+   * Under normal circumstances garbage collected entries are automatically purged as
+   * items are added or removed from the Map. This method can be used to force a purge,
+   * and is useful when the Map is read frequently but updated less often.
+   */
+  public void purgeUnreferencedEntries() {
+    for (Segment segment : this.segments) {
+      segment.restructureIfNecessary(false);
+    }
+  }
+
+
+  @Override
+  public int size() {
+    int size = 0;
+    for (Segment segment : this.segments) {
+      size += segment.getCount();
+    }
+    return size;
+  }
+
+  @Override
+  public boolean isEmpty() {
+    for (Segment segment : this.segments) {
+      if (segment.getCount() > 0) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @Override
+  public Set<Map.Entry<K, V>> entrySet() {
+    Set<Map.Entry<K, V>> entrySet = this.entrySet;
+    if (entrySet == null) {
+      entrySet = new EntrySet();
+      this.entrySet = entrySet;
+    }
+    return entrySet;
+  }
+
+  @Nullable
+  private <T> T doTask(@Nullable Object key, Task<T> task) {
+    int hash = getHash(key);
+    return getSegmentForHash(hash).doTask(hash, key, task);
+  }
+
+  private Segment getSegmentForHash(int hash) {
+    return this.segments[(hash >>> (32 - this.shift)) & (this.segments.length - 1)];
+  }
+
+  /**
+   * Calculate a shift value that can be used to create a power-of-two value between
+   * the specified maximum and minimum values.
+   *
+   * @param minimumValue the minimum value
+   * @param maximumValue the maximum value
+   * @return the calculated shift (use {@code 1 << shift} to obtain a value)
+   */
+  protected static int calculateShift(int minimumValue, int maximumValue) {
+    int shift = 0;
+    int value = 1;
+    while (value < minimumValue && value < maximumValue) {
+      value <<= 1;
+      shift++;
+    }
+    return shift;
+  }
+
+
+  /**
+   * Various reference types supported by this map.
+   */
+  public enum ReferenceType {
+
+    /**
+     * Use {@link SoftReference SoftReferences}.
+     */
+    SOFT,
+
+    /**
+     * Use {@link WeakReference WeakReferences}.
+     */
+    WEAK
+  }
+
+
+  /**
+   * A single segment used to divide the map to allow better concurrent performance.
+   */
+  @SuppressWarnings("serial")
+  protected final class Segment extends ReentrantLock {
+
+    private final ReferenceManager referenceManager;
+
+    private final int initialSize;
+
+    /**
+     * Array of references indexed using the low order bits from the hash.
+     * This property should only be set along with {@code resizeThreshold}.
+     */
+    private volatile Reference<K, V>[] references;
+
+    /**
+     * The total number of references contained in this segment. This includes chained
+     * references and references that have been garbage collected but not purged.
+     */
+    private final AtomicInteger count = new AtomicInteger();
+
+    /**
+     * The threshold when resizing of the references should occur. When {@code count}
+     * exceeds this value references will be resized.
+     */
+    private int resizeThreshold;
+
+    public Segment(int initialSize, int resizeThreshold) {
+      this.referenceManager = createReferenceManager();
+      this.initialSize = initialSize;
+      this.references = createReferenceArray(initialSize);
+      this.resizeThreshold = resizeThreshold;
+    }
+
+    @Nullable
+    public Reference<K, V> getReference(@Nullable Object key, int hash, Restructure restructure) {
+      if (restructure == Restructure.WHEN_NECESSARY) {
+        restructureIfNecessary(false);
+      }
+      if (this.count.get() == 0) {
+        return null;
+      }
+      // Use a local copy to protect against other threads writing
+      Reference<K, V>[] references = this.references;
+      int index = getIndex(hash, references);
+      Reference<K, V> head = references[index];
+      return findInChain(head, key, hash);
+    }
+
+    /**
+     * Apply an update operation to this segment.
+     * The segment will be locked during the update.
+     *
+     * @param hash the hash of the key
+     * @param key  the key
+     * @param task the update operation
+     * @return the result of the operation
+     */
+    @Nullable
+    public <T> T doTask(final int hash, @Nullable final Object key, final Task<T> task) {
+      boolean resize = task.hasOption(TaskOption.RESIZE);
+      if (task.hasOption(TaskOption.RESTRUCTURE_BEFORE)) {
+        restructureIfNecessary(resize);
+      }
+      if (task.hasOption(TaskOption.SKIP_IF_EMPTY) && this.count.get() == 0) {
+        return task.execute(null, null, null);
+      }
+      lock();
+      try {
+        final int index = getIndex(hash, this.references);
+        final Reference<K, V> head = this.references[index];
+        Reference<K, V> ref = findInChain(head, key, hash);
+        Entry<K, V> entry = (ref != null ? ref.get() : null);
+        Entries<V> entries = value -> {
+          @SuppressWarnings("unchecked")
+          Entry<K, V> newEntry = new Entry<>((K) key, value);
+          Reference<K, V> newReference = Segment.this.referenceManager.createReference(newEntry, hash, head);
+          Segment.this.references[index] = newReference;
+          Segment.this.count.incrementAndGet();
+        };
+        return task.execute(ref, entry, entries);
+      } finally {
+        unlock();
+        if (task.hasOption(TaskOption.RESTRUCTURE_AFTER)) {
+          restructureIfNecessary(resize);
+        }
+      }
+    }
+
+    /**
+     * Clear all items from this segment.
+     */
+    public void clear() {
+      if (this.count.get() == 0) {
+        return;
+      }
+      lock();
+      try {
+        this.references = createReferenceArray(this.initialSize);
+        this.resizeThreshold = (int) (this.references.length * getLoadFactor());
+        this.count.set(0);
+      } finally {
+        unlock();
+      }
+    }
+
+    /**
+     * Restructure the underlying data structure when it becomes necessary. This
+     * method can increase the size of the references table as well as purge any
+     * references that have been garbage collected.
+     *
+     * @param allowResize if resizing is permitted
+     */
+    private void restructureIfNecessary(boolean allowResize) {
+      int currCount = this.count.get();
+      boolean needsResize = allowResize && (currCount > 0 && currCount >= this.resizeThreshold);
+      Reference<K, V> ref = this.referenceManager.pollForPurge();
+      if (ref != null || (needsResize)) {
+        restructure(allowResize, ref);
+      }
+    }
+
+    private void restructure(boolean allowResize, @Nullable Reference<K, V> ref) {
+      boolean needsResize;
+      lock();
+      try {
+        int countAfterRestructure = this.count.get();
+        Set<Reference<K, V>> toPurge = Collections.emptySet();
+        if (ref != null) {
+          toPurge = new HashSet<>();
+          while (ref != null) {
+            toPurge.add(ref);
+            ref = this.referenceManager.pollForPurge();
+          }
+        }
+        countAfterRestructure -= toPurge.size();
+
+        // Recalculate taking into account count inside lock and items that
+        // will be purged
+        needsResize = (countAfterRestructure > 0 && countAfterRestructure >= this.resizeThreshold);
+        boolean resizing = false;
+        int restructureSize = this.references.length;
+        if (allowResize && needsResize && restructureSize < MAXIMUM_SEGMENT_SIZE) {
+          restructureSize <<= 1;
+          resizing = true;
+        }
+
+        // Either create a new table or reuse the existing one
+        Reference<K, V>[] restructured =
+          (resizing ? createReferenceArray(restructureSize) : this.references);
+
+        // Restructure
+        for (int i = 0; i < this.references.length; i++) {
+          ref = this.references[i];
+          if (!resizing) {
+            restructured[i] = null;
+          }
+          while (ref != null) {
+            if (!toPurge.contains(ref)) {
+              Entry<K, V> entry = ref.get();
+              if (entry != null) {
+                int index = getIndex(ref.getHash(), restructured);
+                restructured[index] = this.referenceManager.createReference(
+                  entry, ref.getHash(), restructured[index]);
+              }
+            }
+            ref = ref.getNext();
+          }
+        }
+
+        // Replace volatile members
+        if (resizing) {
+          this.references = restructured;
+          this.resizeThreshold = (int) (this.references.length * getLoadFactor());
+        }
+        this.count.set(Math.max(countAfterRestructure, 0));
+      } finally {
+        unlock();
+      }
+    }
+
+    @Nullable
+    private Reference<K, V> findInChain(Reference<K, V> ref, @Nullable Object key, int hash) {
+      Reference<K, V> currRef = ref;
+      while (currRef != null) {
+        if (currRef.getHash() == hash) {
+          Entry<K, V> entry = currRef.get();
+          if (entry != null) {
+            K entryKey = entry.getKey();
+            if (ObjectUtils.nullSafeEquals(entryKey, key)) {
+              return currRef;
+            }
+          }
+        }
+        currRef = currRef.getNext();
+      }
+      return null;
+    }
+
+    @SuppressWarnings({"unchecked"})
+    private Reference<K, V>[] createReferenceArray(int size) {
+      return new Reference[size];
+    }
+
+    private int getIndex(int hash, Reference<K, V>[] references) {
+      return (hash & (references.length - 1));
+    }
+
+    /**
+     * Return the size of the current references array.
+     */
+    public int getSize() {
+      return this.references.length;
+    }
+
+    /**
+     * Return the total number of references in this segment.
+     */
+    public int getCount() {
+      return this.count.get();
+    }
+  }
+
+
+  /**
+   * A reference to an {@link Entry} contained in the map. Implementations are usually
+   * wrappers around specific Java reference implementations (e.g., {@link SoftReference}).
+   *
+   * @param <K> the key type
+   * @param <V> the value type
+   */
+  protected interface Reference<K, V> {
+
+    /**
+     * Return the referenced entry, or {@code null} if the entry is no longer available.
+     */
+    @Nullable
+    Entry<K, V> get();
+
+    /**
+     * Return the hash for the reference.
+     */
+    int getHash();
+
+    /**
+     * Return the next reference in the chain, or {@code null} if none.
+     */
+    @Nullable
+    Reference<K, V> getNext();
+
+    /**
+     * Release this entry and ensure that it will be returned from
+     * {@code ReferenceManager#pollForPurge()}.
+     */
+    void release();
+  }
+
+
+  /**
+   * A single map entry.
+   *
+   * @param <K> the key type
+   * @param <V> the value type
+   */
+  protected static final class Entry<K, V> implements Map.Entry<K, V> {
+
+    @Nullable
+    private final K key;
+
+    @Nullable
+    private volatile V value;
+
+    public Entry(@Nullable K key, @Nullable V value) {
+      this.key = key;
+      this.value = value;
+    }
+
+    @Override
+    @Nullable
+    public K getKey() {
+      return this.key;
+    }
+
+    @Override
+    @Nullable
+    public V getValue() {
+      return this.value;
+    }
+
+    @Override
+    @Nullable
+    public V setValue(@Nullable V value) {
+      V previous = this.value;
+      this.value = value;
+      return previous;
+    }
+
+    @Override
+    public String toString() {
+      return (this.key + "=" + this.value);
+    }
+
+    @Override
+    @SuppressWarnings("rawtypes")
+    public boolean equals(@Nullable Object other) {
+      if (this == other) {
+        return true;
+      }
+      if (!(other instanceof Map.Entry)) {
+        return false;
+      }
+      Map.Entry otherEntry = (Map.Entry) other;
+      return (ObjectUtils.nullSafeEquals(getKey(), otherEntry.getKey()) &&
+        ObjectUtils.nullSafeEquals(getValue(), otherEntry.getValue()));
+    }
+
+    @Override
+    public int hashCode() {
+      return (ObjectUtils.nullSafeHashCode(this.key) ^ ObjectUtils.nullSafeHashCode(this.value));
+    }
+  }
+
+
+  /**
+   * A task that can be {@link Segment#doTask run} against a {@link Segment}.
+   */
+  private abstract class Task<T> {

Review Comment:
   ## Inner class could be static
   
   Task should be made static, since the enclosing instance is not used.
   
   [Show more details](https://github.com/apache/avro/security/code-scanning/2967)



##########
lang/java/avro/src/main/java/org/apache/avro/util/springframework/ObjectUtils.java:
##########
@@ -0,0 +1,322 @@
+/*
+ * Copyright 2002-2021 the original author or authors.
+ *
+ * Licensed 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
+ *
+ *      https://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.avro.util.springframework;
+
+import org.apache.avro.reflect.Nullable;
+import org.apache.avro.util.ClassUtils;
+
+import java.util.Arrays;
+
+/**
+ * Miscellaneous object utility methods.
+ *
+ * <p>Mainly for internal use within the framework.
+ *
+ * <p>Thanks to Alex Ruiz for contributing several enhancements to this class!
+ *
+ * @author Juergen Hoeller
+ * @author Keith Donald
+ * @author Rod Johnson
+ * @author Rob Harrop
+ * @author Chris Beams
+ * @author Sam Brannen
+ * @see ClassUtils
+ * see CollectionUtils
+ * see StringUtils
+ * @since 19.03.2004
+ */
+class ObjectUtils {
+  private ObjectUtils() {
+  }
+
+  private static final int INITIAL_HASH = 7;
+  private static final int MULTIPLIER = 31;
+
+
+  /**
+   * Determine whether the given array is empty:
+   * i.e. {@code null} or of zero length.
+   *
+   * @param array the array to check
+   */
+  public static boolean isEmpty(@Nullable Object[] array) {
+    return (array == null || array.length == 0);
+  }
+
+
+  //---------------------------------------------------------------------
+  // Convenience methods for content-based equality/hash-code handling
+  //---------------------------------------------------------------------
+
+  /**
+   * Determine if the given objects are equal, returning {@code true} if
+   * both are {@code null} or {@code false} if only one is {@code null}.
+   * <p>Compares arrays with {@code Arrays.equals}, performing an equality
+   * check based on the array elements rather than the array reference.
+   *
+   * @param o1 first Object to compare
+   * @param o2 second Object to compare
+   * @return whether the given objects are equal
+   * @see Object#equals(Object)
+   * @see Arrays#equals
+   */
+  public static boolean nullSafeEquals(@Nullable Object o1, @Nullable Object o2) {
+    if (o1 == o2) {
+      return true;
+    }
+    if (o1 == null || o2 == null) {
+      return false;
+    }
+    if (o1.equals(o2)) {
+      return true;
+    }
+    if (o1.getClass().isArray() && o2.getClass().isArray()) {
+      return arrayEquals(o1, o2);
+    }
+    return false;
+  }
+
+  /**
+   * Compare the given arrays with {@code Arrays.equals}, performing an equality
+   * check based on the array elements rather than the array reference.
+   *
+   * @param o1 first array to compare
+   * @param o2 second array to compare
+   * @return whether the given objects are equal
+   * @see #nullSafeEquals(Object, Object)
+   * @see Arrays#equals
+   */
+  private static boolean arrayEquals(Object o1, Object o2) {
+    if (o1 instanceof Object[] && o2 instanceof Object[]) {
+      return Arrays.equals((Object[]) o1, (Object[]) o2);
+    }
+    if (o1 instanceof boolean[] && o2 instanceof boolean[]) {
+      return Arrays.equals((boolean[]) o1, (boolean[]) o2);
+    }
+    if (o1 instanceof byte[] && o2 instanceof byte[]) {
+      return Arrays.equals((byte[]) o1, (byte[]) o2);
+    }
+    if (o1 instanceof char[] && o2 instanceof char[]) {
+      return Arrays.equals((char[]) o1, (char[]) o2);
+    }
+    if (o1 instanceof double[] && o2 instanceof double[]) {
+      return Arrays.equals((double[]) o1, (double[]) o2);
+    }
+    if (o1 instanceof float[] && o2 instanceof float[]) {
+      return Arrays.equals((float[]) o1, (float[]) o2);
+    }
+    if (o1 instanceof int[] && o2 instanceof int[]) {
+      return Arrays.equals((int[]) o1, (int[]) o2);
+    }
+    if (o1 instanceof long[] && o2 instanceof long[]) {
+      return Arrays.equals((long[]) o1, (long[]) o2);
+    }
+    if (o1 instanceof short[] && o2 instanceof short[]) {
+      return Arrays.equals((short[]) o1, (short[]) o2);
+    }
+    return false;
+  }
+
+  /**
+   * Return as hash code for the given object; typically the value of
+   * {@code Object#hashCode()}}. If the object is an array,
+   * this method will delegate to any of the {@code nullSafeHashCode}
+   * methods for arrays in this class. If the object is {@code null},
+   * this method returns 0.
+   *
+   * @see Object#hashCode()
+   * @see #nullSafeHashCode(Object[])
+   * @see #nullSafeHashCode(boolean[])
+   * @see #nullSafeHashCode(byte[])
+   * @see #nullSafeHashCode(char[])
+   * @see #nullSafeHashCode(double[])
+   * @see #nullSafeHashCode(float[])
+   * @see #nullSafeHashCode(int[])
+   * @see #nullSafeHashCode(long[])
+   * @see #nullSafeHashCode(short[])
+   */
+  public static int nullSafeHashCode(@Nullable Object obj) {
+    if (obj == null) {
+      return 0;
+    }
+    if (obj.getClass().isArray()) {
+      if (obj instanceof Object[]) {
+        return nullSafeHashCode((Object[]) obj);
+      }
+      if (obj instanceof boolean[]) {
+        return nullSafeHashCode((boolean[]) obj);
+      }
+      if (obj instanceof byte[]) {
+        return nullSafeHashCode((byte[]) obj);
+      }
+      if (obj instanceof char[]) {
+        return nullSafeHashCode((char[]) obj);
+      }
+      if (obj instanceof double[]) {
+        return nullSafeHashCode((double[]) obj);
+      }
+      if (obj instanceof float[]) {
+        return nullSafeHashCode((float[]) obj);
+      }
+      if (obj instanceof int[]) {
+        return nullSafeHashCode((int[]) obj);
+      }
+      if (obj instanceof long[]) {
+        return nullSafeHashCode((long[]) obj);
+      }
+      if (obj instanceof short[]) {
+        return nullSafeHashCode((short[]) obj);
+      }
+    }
+    return obj.hashCode();
+  }
+
+  /**
+   * Return a hash code based on the contents of the specified array.
+   * If {@code array} is {@code null}, this method returns 0.
+   */
+  public static int nullSafeHashCode(@Nullable Object[] array) {
+    if (array == null) {
+      return 0;
+    }
+    int hash = INITIAL_HASH;
+    for (Object element : array) {
+      hash = MULTIPLIER * hash + nullSafeHashCode(element);
+    }
+    return hash;
+  }
+
+  /**
+   * Return a hash code based on the contents of the specified array.
+   * If {@code array} is {@code null}, this method returns 0.
+   */
+  public static int nullSafeHashCode(@Nullable boolean[] array) {
+    if (array == null) {
+      return 0;
+    }
+    int hash = INITIAL_HASH;
+    for (boolean element : array) {
+      hash = MULTIPLIER * hash + Boolean.hashCode(element);
+    }
+    return hash;
+  }
+
+  /**
+   * Return a hash code based on the contents of the specified array.
+   * If {@code array} is {@code null}, this method returns 0.
+   */
+  public static int nullSafeHashCode(@Nullable byte[] array) {
+    if (array == null) {
+      return 0;
+    }
+    int hash = INITIAL_HASH;
+    for (byte element : array) {
+      hash = MULTIPLIER * hash + element;
+    }
+    return hash;
+  }
+
+  /**
+   * Return a hash code based on the contents of the specified array.
+   * If {@code array} is {@code null}, this method returns 0.
+   */
+  public static int nullSafeHashCode(@Nullable char[] array) {

Review Comment:
   ## Confusing overloading of methods
   
   Method ObjectUtils.nullSafeHashCode(..) could be confused with overloaded method [nullSafeHashCode](1), since dispatch depends on static types.
   
   [Show more details](https://github.com/apache/avro/security/code-scanning/2971)



##########
lang/java/avro/src/main/java/org/apache/avro/util/springframework/ObjectUtils.java:
##########
@@ -0,0 +1,322 @@
+/*
+ * Copyright 2002-2021 the original author or authors.
+ *
+ * Licensed 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
+ *
+ *      https://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.avro.util.springframework;
+
+import org.apache.avro.reflect.Nullable;
+import org.apache.avro.util.ClassUtils;
+
+import java.util.Arrays;
+
+/**
+ * Miscellaneous object utility methods.
+ *
+ * <p>Mainly for internal use within the framework.
+ *
+ * <p>Thanks to Alex Ruiz for contributing several enhancements to this class!
+ *
+ * @author Juergen Hoeller
+ * @author Keith Donald
+ * @author Rod Johnson
+ * @author Rob Harrop
+ * @author Chris Beams
+ * @author Sam Brannen
+ * @see ClassUtils
+ * see CollectionUtils
+ * see StringUtils
+ * @since 19.03.2004
+ */
+class ObjectUtils {
+  private ObjectUtils() {
+  }
+
+  private static final int INITIAL_HASH = 7;
+  private static final int MULTIPLIER = 31;
+
+
+  /**
+   * Determine whether the given array is empty:
+   * i.e. {@code null} or of zero length.
+   *
+   * @param array the array to check
+   */
+  public static boolean isEmpty(@Nullable Object[] array) {
+    return (array == null || array.length == 0);
+  }
+
+
+  //---------------------------------------------------------------------
+  // Convenience methods for content-based equality/hash-code handling
+  //---------------------------------------------------------------------
+
+  /**
+   * Determine if the given objects are equal, returning {@code true} if
+   * both are {@code null} or {@code false} if only one is {@code null}.
+   * <p>Compares arrays with {@code Arrays.equals}, performing an equality
+   * check based on the array elements rather than the array reference.
+   *
+   * @param o1 first Object to compare
+   * @param o2 second Object to compare
+   * @return whether the given objects are equal
+   * @see Object#equals(Object)
+   * @see Arrays#equals
+   */
+  public static boolean nullSafeEquals(@Nullable Object o1, @Nullable Object o2) {
+    if (o1 == o2) {
+      return true;
+    }
+    if (o1 == null || o2 == null) {
+      return false;
+    }
+    if (o1.equals(o2)) {
+      return true;
+    }
+    if (o1.getClass().isArray() && o2.getClass().isArray()) {
+      return arrayEquals(o1, o2);
+    }
+    return false;
+  }
+
+  /**
+   * Compare the given arrays with {@code Arrays.equals}, performing an equality
+   * check based on the array elements rather than the array reference.
+   *
+   * @param o1 first array to compare
+   * @param o2 second array to compare
+   * @return whether the given objects are equal
+   * @see #nullSafeEquals(Object, Object)
+   * @see Arrays#equals
+   */
+  private static boolean arrayEquals(Object o1, Object o2) {
+    if (o1 instanceof Object[] && o2 instanceof Object[]) {
+      return Arrays.equals((Object[]) o1, (Object[]) o2);
+    }
+    if (o1 instanceof boolean[] && o2 instanceof boolean[]) {
+      return Arrays.equals((boolean[]) o1, (boolean[]) o2);
+    }
+    if (o1 instanceof byte[] && o2 instanceof byte[]) {
+      return Arrays.equals((byte[]) o1, (byte[]) o2);
+    }
+    if (o1 instanceof char[] && o2 instanceof char[]) {
+      return Arrays.equals((char[]) o1, (char[]) o2);
+    }
+    if (o1 instanceof double[] && o2 instanceof double[]) {
+      return Arrays.equals((double[]) o1, (double[]) o2);
+    }
+    if (o1 instanceof float[] && o2 instanceof float[]) {
+      return Arrays.equals((float[]) o1, (float[]) o2);
+    }
+    if (o1 instanceof int[] && o2 instanceof int[]) {
+      return Arrays.equals((int[]) o1, (int[]) o2);
+    }
+    if (o1 instanceof long[] && o2 instanceof long[]) {
+      return Arrays.equals((long[]) o1, (long[]) o2);
+    }
+    if (o1 instanceof short[] && o2 instanceof short[]) {
+      return Arrays.equals((short[]) o1, (short[]) o2);
+    }
+    return false;
+  }
+
+  /**
+   * Return as hash code for the given object; typically the value of
+   * {@code Object#hashCode()}}. If the object is an array,
+   * this method will delegate to any of the {@code nullSafeHashCode}
+   * methods for arrays in this class. If the object is {@code null},
+   * this method returns 0.
+   *
+   * @see Object#hashCode()
+   * @see #nullSafeHashCode(Object[])
+   * @see #nullSafeHashCode(boolean[])
+   * @see #nullSafeHashCode(byte[])
+   * @see #nullSafeHashCode(char[])
+   * @see #nullSafeHashCode(double[])
+   * @see #nullSafeHashCode(float[])
+   * @see #nullSafeHashCode(int[])
+   * @see #nullSafeHashCode(long[])
+   * @see #nullSafeHashCode(short[])
+   */
+  public static int nullSafeHashCode(@Nullable Object obj) {
+    if (obj == null) {
+      return 0;
+    }
+    if (obj.getClass().isArray()) {
+      if (obj instanceof Object[]) {
+        return nullSafeHashCode((Object[]) obj);
+      }
+      if (obj instanceof boolean[]) {
+        return nullSafeHashCode((boolean[]) obj);
+      }
+      if (obj instanceof byte[]) {
+        return nullSafeHashCode((byte[]) obj);
+      }
+      if (obj instanceof char[]) {
+        return nullSafeHashCode((char[]) obj);
+      }
+      if (obj instanceof double[]) {
+        return nullSafeHashCode((double[]) obj);
+      }
+      if (obj instanceof float[]) {
+        return nullSafeHashCode((float[]) obj);
+      }
+      if (obj instanceof int[]) {
+        return nullSafeHashCode((int[]) obj);
+      }
+      if (obj instanceof long[]) {
+        return nullSafeHashCode((long[]) obj);
+      }
+      if (obj instanceof short[]) {
+        return nullSafeHashCode((short[]) obj);
+      }
+    }
+    return obj.hashCode();
+  }
+
+  /**
+   * Return a hash code based on the contents of the specified array.
+   * If {@code array} is {@code null}, this method returns 0.
+   */
+  public static int nullSafeHashCode(@Nullable Object[] array) {
+    if (array == null) {
+      return 0;
+    }
+    int hash = INITIAL_HASH;
+    for (Object element : array) {
+      hash = MULTIPLIER * hash + nullSafeHashCode(element);
+    }
+    return hash;
+  }
+
+  /**
+   * Return a hash code based on the contents of the specified array.
+   * If {@code array} is {@code null}, this method returns 0.
+   */
+  public static int nullSafeHashCode(@Nullable boolean[] array) {
+    if (array == null) {
+      return 0;
+    }
+    int hash = INITIAL_HASH;
+    for (boolean element : array) {
+      hash = MULTIPLIER * hash + Boolean.hashCode(element);
+    }
+    return hash;
+  }
+
+  /**
+   * Return a hash code based on the contents of the specified array.
+   * If {@code array} is {@code null}, this method returns 0.
+   */
+  public static int nullSafeHashCode(@Nullable byte[] array) {
+    if (array == null) {
+      return 0;
+    }
+    int hash = INITIAL_HASH;
+    for (byte element : array) {
+      hash = MULTIPLIER * hash + element;
+    }
+    return hash;
+  }
+
+  /**
+   * Return a hash code based on the contents of the specified array.
+   * If {@code array} is {@code null}, this method returns 0.
+   */
+  public static int nullSafeHashCode(@Nullable char[] array) {
+    if (array == null) {
+      return 0;
+    }
+    int hash = INITIAL_HASH;
+    for (char element : array) {
+      hash = MULTIPLIER * hash + element;
+    }
+    return hash;
+  }
+
+  /**
+   * Return a hash code based on the contents of the specified array.
+   * If {@code array} is {@code null}, this method returns 0.
+   */
+  public static int nullSafeHashCode(@Nullable double[] array) {

Review Comment:
   ## Confusing overloading of methods
   
   Method ObjectUtils.nullSafeHashCode(..) could be confused with overloaded method [nullSafeHashCode](1), since dispatch depends on static types.
   
   [Show more details](https://github.com/apache/avro/security/code-scanning/2970)



##########
lang/java/avro/src/main/java/org/apache/avro/util/springframework/ObjectUtils.java:
##########
@@ -0,0 +1,322 @@
+/*
+ * Copyright 2002-2021 the original author or authors.
+ *
+ * Licensed 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
+ *
+ *      https://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.avro.util.springframework;
+
+import org.apache.avro.reflect.Nullable;
+import org.apache.avro.util.ClassUtils;
+
+import java.util.Arrays;
+
+/**
+ * Miscellaneous object utility methods.
+ *
+ * <p>Mainly for internal use within the framework.
+ *
+ * <p>Thanks to Alex Ruiz for contributing several enhancements to this class!
+ *
+ * @author Juergen Hoeller
+ * @author Keith Donald
+ * @author Rod Johnson
+ * @author Rob Harrop
+ * @author Chris Beams
+ * @author Sam Brannen
+ * @see ClassUtils
+ * see CollectionUtils
+ * see StringUtils
+ * @since 19.03.2004
+ */
+class ObjectUtils {
+  private ObjectUtils() {
+  }
+
+  private static final int INITIAL_HASH = 7;
+  private static final int MULTIPLIER = 31;
+
+
+  /**
+   * Determine whether the given array is empty:
+   * i.e. {@code null} or of zero length.
+   *
+   * @param array the array to check
+   */
+  public static boolean isEmpty(@Nullable Object[] array) {
+    return (array == null || array.length == 0);
+  }
+
+
+  //---------------------------------------------------------------------
+  // Convenience methods for content-based equality/hash-code handling
+  //---------------------------------------------------------------------
+
+  /**
+   * Determine if the given objects are equal, returning {@code true} if
+   * both are {@code null} or {@code false} if only one is {@code null}.
+   * <p>Compares arrays with {@code Arrays.equals}, performing an equality
+   * check based on the array elements rather than the array reference.
+   *
+   * @param o1 first Object to compare
+   * @param o2 second Object to compare
+   * @return whether the given objects are equal
+   * @see Object#equals(Object)
+   * @see Arrays#equals
+   */
+  public static boolean nullSafeEquals(@Nullable Object o1, @Nullable Object o2) {
+    if (o1 == o2) {
+      return true;
+    }
+    if (o1 == null || o2 == null) {
+      return false;
+    }
+    if (o1.equals(o2)) {
+      return true;
+    }
+    if (o1.getClass().isArray() && o2.getClass().isArray()) {
+      return arrayEquals(o1, o2);
+    }
+    return false;
+  }
+
+  /**
+   * Compare the given arrays with {@code Arrays.equals}, performing an equality
+   * check based on the array elements rather than the array reference.
+   *
+   * @param o1 first array to compare
+   * @param o2 second array to compare
+   * @return whether the given objects are equal
+   * @see #nullSafeEquals(Object, Object)
+   * @see Arrays#equals
+   */
+  private static boolean arrayEquals(Object o1, Object o2) {
+    if (o1 instanceof Object[] && o2 instanceof Object[]) {
+      return Arrays.equals((Object[]) o1, (Object[]) o2);
+    }
+    if (o1 instanceof boolean[] && o2 instanceof boolean[]) {
+      return Arrays.equals((boolean[]) o1, (boolean[]) o2);
+    }
+    if (o1 instanceof byte[] && o2 instanceof byte[]) {
+      return Arrays.equals((byte[]) o1, (byte[]) o2);
+    }
+    if (o1 instanceof char[] && o2 instanceof char[]) {
+      return Arrays.equals((char[]) o1, (char[]) o2);
+    }
+    if (o1 instanceof double[] && o2 instanceof double[]) {
+      return Arrays.equals((double[]) o1, (double[]) o2);
+    }
+    if (o1 instanceof float[] && o2 instanceof float[]) {
+      return Arrays.equals((float[]) o1, (float[]) o2);
+    }
+    if (o1 instanceof int[] && o2 instanceof int[]) {
+      return Arrays.equals((int[]) o1, (int[]) o2);
+    }
+    if (o1 instanceof long[] && o2 instanceof long[]) {
+      return Arrays.equals((long[]) o1, (long[]) o2);
+    }
+    if (o1 instanceof short[] && o2 instanceof short[]) {
+      return Arrays.equals((short[]) o1, (short[]) o2);
+    }
+    return false;
+  }
+
+  /**
+   * Return as hash code for the given object; typically the value of
+   * {@code Object#hashCode()}}. If the object is an array,
+   * this method will delegate to any of the {@code nullSafeHashCode}
+   * methods for arrays in this class. If the object is {@code null},
+   * this method returns 0.
+   *
+   * @see Object#hashCode()
+   * @see #nullSafeHashCode(Object[])
+   * @see #nullSafeHashCode(boolean[])
+   * @see #nullSafeHashCode(byte[])
+   * @see #nullSafeHashCode(char[])
+   * @see #nullSafeHashCode(double[])
+   * @see #nullSafeHashCode(float[])
+   * @see #nullSafeHashCode(int[])
+   * @see #nullSafeHashCode(long[])
+   * @see #nullSafeHashCode(short[])
+   */
+  public static int nullSafeHashCode(@Nullable Object obj) {
+    if (obj == null) {
+      return 0;
+    }
+    if (obj.getClass().isArray()) {
+      if (obj instanceof Object[]) {
+        return nullSafeHashCode((Object[]) obj);
+      }
+      if (obj instanceof boolean[]) {
+        return nullSafeHashCode((boolean[]) obj);
+      }
+      if (obj instanceof byte[]) {
+        return nullSafeHashCode((byte[]) obj);
+      }
+      if (obj instanceof char[]) {
+        return nullSafeHashCode((char[]) obj);
+      }
+      if (obj instanceof double[]) {
+        return nullSafeHashCode((double[]) obj);
+      }
+      if (obj instanceof float[]) {
+        return nullSafeHashCode((float[]) obj);
+      }
+      if (obj instanceof int[]) {
+        return nullSafeHashCode((int[]) obj);
+      }
+      if (obj instanceof long[]) {
+        return nullSafeHashCode((long[]) obj);
+      }
+      if (obj instanceof short[]) {
+        return nullSafeHashCode((short[]) obj);
+      }
+    }
+    return obj.hashCode();
+  }
+
+  /**
+   * Return a hash code based on the contents of the specified array.
+   * If {@code array} is {@code null}, this method returns 0.
+   */
+  public static int nullSafeHashCode(@Nullable Object[] array) {
+    if (array == null) {
+      return 0;
+    }
+    int hash = INITIAL_HASH;
+    for (Object element : array) {
+      hash = MULTIPLIER * hash + nullSafeHashCode(element);
+    }
+    return hash;
+  }
+
+  /**
+   * Return a hash code based on the contents of the specified array.
+   * If {@code array} is {@code null}, this method returns 0.
+   */
+  public static int nullSafeHashCode(@Nullable boolean[] array) {
+    if (array == null) {
+      return 0;
+    }
+    int hash = INITIAL_HASH;
+    for (boolean element : array) {
+      hash = MULTIPLIER * hash + Boolean.hashCode(element);
+    }
+    return hash;
+  }
+
+  /**
+   * Return a hash code based on the contents of the specified array.
+   * If {@code array} is {@code null}, this method returns 0.
+   */
+  public static int nullSafeHashCode(@Nullable byte[] array) {

Review Comment:
   ## Confusing overloading of methods
   
   Method ObjectUtils.nullSafeHashCode(..) could be confused with overloaded method [nullSafeHashCode](1), since dispatch depends on static types.
   
   [Show more details](https://github.com/apache/avro/security/code-scanning/2972)



##########
lang/java/avro/src/main/java/org/apache/avro/util/springframework/ObjectUtils.java:
##########
@@ -0,0 +1,322 @@
+/*
+ * Copyright 2002-2021 the original author or authors.
+ *
+ * Licensed 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
+ *
+ *      https://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.avro.util.springframework;
+
+import org.apache.avro.reflect.Nullable;
+import org.apache.avro.util.ClassUtils;
+
+import java.util.Arrays;
+
+/**
+ * Miscellaneous object utility methods.
+ *
+ * <p>Mainly for internal use within the framework.
+ *
+ * <p>Thanks to Alex Ruiz for contributing several enhancements to this class!
+ *
+ * @author Juergen Hoeller
+ * @author Keith Donald
+ * @author Rod Johnson
+ * @author Rob Harrop
+ * @author Chris Beams
+ * @author Sam Brannen
+ * @see ClassUtils
+ * see CollectionUtils
+ * see StringUtils
+ * @since 19.03.2004
+ */
+class ObjectUtils {
+  private ObjectUtils() {
+  }
+
+  private static final int INITIAL_HASH = 7;
+  private static final int MULTIPLIER = 31;
+
+
+  /**
+   * Determine whether the given array is empty:
+   * i.e. {@code null} or of zero length.
+   *
+   * @param array the array to check
+   */
+  public static boolean isEmpty(@Nullable Object[] array) {
+    return (array == null || array.length == 0);
+  }
+
+
+  //---------------------------------------------------------------------
+  // Convenience methods for content-based equality/hash-code handling
+  //---------------------------------------------------------------------
+
+  /**
+   * Determine if the given objects are equal, returning {@code true} if
+   * both are {@code null} or {@code false} if only one is {@code null}.
+   * <p>Compares arrays with {@code Arrays.equals}, performing an equality
+   * check based on the array elements rather than the array reference.
+   *
+   * @param o1 first Object to compare
+   * @param o2 second Object to compare
+   * @return whether the given objects are equal
+   * @see Object#equals(Object)
+   * @see Arrays#equals
+   */
+  public static boolean nullSafeEquals(@Nullable Object o1, @Nullable Object o2) {
+    if (o1 == o2) {
+      return true;
+    }
+    if (o1 == null || o2 == null) {
+      return false;
+    }
+    if (o1.equals(o2)) {
+      return true;
+    }
+    if (o1.getClass().isArray() && o2.getClass().isArray()) {
+      return arrayEquals(o1, o2);
+    }
+    return false;
+  }
+
+  /**
+   * Compare the given arrays with {@code Arrays.equals}, performing an equality
+   * check based on the array elements rather than the array reference.
+   *
+   * @param o1 first array to compare
+   * @param o2 second array to compare
+   * @return whether the given objects are equal
+   * @see #nullSafeEquals(Object, Object)
+   * @see Arrays#equals
+   */
+  private static boolean arrayEquals(Object o1, Object o2) {
+    if (o1 instanceof Object[] && o2 instanceof Object[]) {
+      return Arrays.equals((Object[]) o1, (Object[]) o2);
+    }
+    if (o1 instanceof boolean[] && o2 instanceof boolean[]) {
+      return Arrays.equals((boolean[]) o1, (boolean[]) o2);
+    }
+    if (o1 instanceof byte[] && o2 instanceof byte[]) {
+      return Arrays.equals((byte[]) o1, (byte[]) o2);
+    }
+    if (o1 instanceof char[] && o2 instanceof char[]) {
+      return Arrays.equals((char[]) o1, (char[]) o2);
+    }
+    if (o1 instanceof double[] && o2 instanceof double[]) {
+      return Arrays.equals((double[]) o1, (double[]) o2);
+    }
+    if (o1 instanceof float[] && o2 instanceof float[]) {
+      return Arrays.equals((float[]) o1, (float[]) o2);
+    }
+    if (o1 instanceof int[] && o2 instanceof int[]) {
+      return Arrays.equals((int[]) o1, (int[]) o2);
+    }
+    if (o1 instanceof long[] && o2 instanceof long[]) {
+      return Arrays.equals((long[]) o1, (long[]) o2);
+    }
+    if (o1 instanceof short[] && o2 instanceof short[]) {
+      return Arrays.equals((short[]) o1, (short[]) o2);
+    }
+    return false;
+  }
+
+  /**
+   * Return as hash code for the given object; typically the value of
+   * {@code Object#hashCode()}}. If the object is an array,
+   * this method will delegate to any of the {@code nullSafeHashCode}
+   * methods for arrays in this class. If the object is {@code null},
+   * this method returns 0.
+   *
+   * @see Object#hashCode()
+   * @see #nullSafeHashCode(Object[])
+   * @see #nullSafeHashCode(boolean[])
+   * @see #nullSafeHashCode(byte[])
+   * @see #nullSafeHashCode(char[])
+   * @see #nullSafeHashCode(double[])
+   * @see #nullSafeHashCode(float[])
+   * @see #nullSafeHashCode(int[])
+   * @see #nullSafeHashCode(long[])
+   * @see #nullSafeHashCode(short[])
+   */
+  public static int nullSafeHashCode(@Nullable Object obj) {

Review Comment:
   ## Confusing overloading of methods
   
   Method ObjectUtils.nullSafeHashCode(..) could be confused with overloaded method [nullSafeHashCode](1), since dispatch depends on static types.
   Method ObjectUtils.nullSafeHashCode(..) could be confused with overloaded method [nullSafeHashCode](2), since dispatch depends on static types.
   Method ObjectUtils.nullSafeHashCode(..) could be confused with overloaded method [nullSafeHashCode](3), since dispatch depends on static types.
   
   [Show more details](https://github.com/apache/avro/security/code-scanning/2974)



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@avro.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [avro] nielsbasjes commented on a diff in pull request #2090: AVRO-3713: [Java] Fix Map synchronization regression

Posted by "nielsbasjes (via GitHub)" <gi...@apache.org>.
nielsbasjes commented on code in PR #2090:
URL: https://github.com/apache/avro/pull/2090#discussion_r1109931510


##########
lang/java/avro/src/test/java/org/apache/avro/util/springframework/ConcurrentReferenceHashMapTests.java:
##########
@@ -0,0 +1,688 @@
+/*
+ * Copyright 2002-2021 the original author or authors.
+ *
+ * Licensed 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
+ *
+ *      https://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.avro.util.springframework;
+
+import org.apache.avro.reflect.Nullable;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.apache.avro.util.springframework.ConcurrentReferenceHashMap.Entry;
+import org.apache.avro.util.springframework.ConcurrentReferenceHashMap.Reference;
+import org.apache.avro.util.springframework.ConcurrentReferenceHashMap.Restructure;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests for {@link ConcurrentReferenceHashMap}.
+ *
+ * @author Phillip Webb
+ * @author Juergen Hoeller
+ */
+class ConcurrentReferenceHashMapTests {

Review Comment:
   You are right. Fixed it.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@avro.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [avro] nielsbasjes commented on pull request #2090: [AVRO-3713]: [Java] Fix Map synchronization regression

Posted by "nielsbasjes (via GitHub)" <gi...@apache.org>.
nielsbasjes commented on PR #2090:
URL: https://github.com/apache/avro/pull/2090#issuecomment-1428072156

   I want to add a test to hammer out the risk of running into problems.
   Something like run 10 threads at full speed against a single instance a few million times and fail on any issues. That way we can prove if your suggestion is safe to use.
   So we already do something like that already?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@avro.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [avro] nielsbasjes commented on pull request #2090: [AVRO-3713]: [Java] Fix Map synchronization regression

Posted by "nielsbasjes (via GitHub)" <gi...@apache.org>.
nielsbasjes commented on PR #2090:
URL: https://github.com/apache/avro/pull/2090#issuecomment-1427938921

   @clesaec 
   I think I misunderstand your feedback.
   On the one hand I see "LGTM" and on the other hand you say we should keep the "weak keys".
   My problem is that I do not see the reason for using the weak keys here.
   To me it seems like it is used to "auto expire" this cache.
   
   Do you know of a threadsafe weak keys map implementation without synchronization on the `get ` we can use?
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@avro.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [avro] clesaec commented on pull request #2090: [AVRO-3713]: [Java] Fix Map synchronization regression

Posted by "clesaec (via GitHub)" <gi...@apache.org>.
clesaec commented on PR #2090:
URL: https://github.com/apache/avro/pull/2090#issuecomment-1428269501

   For tests, [Nondex](https://github.com/TestingResearchIllinois/NonDex) was suggested in [this JIRA](https://issues.apache.org/jira/browse/AVRO-3689), i don't know if it could find this kind of issue. Otherwise, yes, use with one millions Fields on 10 thread should have a sufficient rate of failure in case of bug.
   For solution, few month ago, i tested Map with ReentrantReadWriteLock ([see this commit](https://github.com/apache/avro/pull/1719/commits/2ba9abf294815b9f9f5abaf511c48ecd3eaf96c7)), but was finally not revelant for this case.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@avro.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [avro] martin-g commented on a diff in pull request #2090: AVRO-3713: [Java] Fix Map synchronization regression

Posted by "martin-g (via GitHub)" <gi...@apache.org>.
martin-g commented on code in PR #2090:
URL: https://github.com/apache/avro/pull/2090#discussion_r1109770023


##########
lang/java/avro/src/test/java/org/apache/avro/util/springframework/ConcurrentReferenceHashMapTests.java:
##########
@@ -0,0 +1,688 @@
+/*
+ * Copyright 2002-2021 the original author or authors.
+ *
+ * Licensed 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
+ *
+ *      https://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.avro.util.springframework;
+
+import org.apache.avro.reflect.Nullable;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.apache.avro.util.springframework.ConcurrentReferenceHashMap.Entry;
+import org.apache.avro.util.springframework.ConcurrentReferenceHashMap.Reference;
+import org.apache.avro.util.springframework.ConcurrentReferenceHashMap.Restructure;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests for {@link ConcurrentReferenceHashMap}.
+ *
+ * @author Phillip Webb
+ * @author Juergen Hoeller
+ */
+class ConcurrentReferenceHashMapTests {

Review Comment:
   I think this new test is not executed because Avro's tests are named as `TestXyz`



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@avro.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [avro] github-code-scanning[bot] commented on a diff in pull request #2090: [AVRO-3713]: [Java] Fix Map synchronization regression

Posted by "github-code-scanning[bot] (via GitHub)" <gi...@apache.org>.
github-code-scanning[bot] commented on code in PR #2090:
URL: https://github.com/apache/avro/pull/2090#discussion_r1108526785


##########
lang/java/avro/src/test/java/org/apache/avro/util/springframework/ConcurrentReferenceHashMapTests.java:
##########
@@ -0,0 +1,688 @@
+/*
+ * Copyright 2002-2021 the original author or authors.
+ *
+ * Licensed 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
+ *
+ *      https://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.avro.util.springframework;
+
+import org.apache.avro.reflect.Nullable;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.apache.avro.util.springframework.ConcurrentReferenceHashMap.Entry;
+import org.apache.avro.util.springframework.ConcurrentReferenceHashMap.Reference;
+import org.apache.avro.util.springframework.ConcurrentReferenceHashMap.Restructure;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests for {@link ConcurrentReferenceHashMap}.
+ *
+ * @author Phillip Webb
+ * @author Juergen Hoeller
+ */
+class ConcurrentReferenceHashMapTests {
+
+  private static final Comparator<? super String> NULL_SAFE_STRING_SORT = new NullSafeComparator<>(
+      new ComparableComparator<String>(), true);
+
+  private TestWeakConcurrentCache<Integer, String> map = new TestWeakConcurrentCache<>();
+
+  @Test
+  void shouldCreateWithDefaults() {
+    ConcurrentReferenceHashMap<Integer, String> map = new ConcurrentReferenceHashMap<>();
+    assertThat(map.getSegmentsSize(), equalTo(16));
+    assertThat(map.getSegment(0).getSize(), equalTo(1));
+    assertThat(map.getLoadFactor(), equalTo(0.75f));
+  }
+
+  @Test
+  void shouldCreateWithInitialCapacity() {
+    ConcurrentReferenceHashMap<Integer, String> map = new ConcurrentReferenceHashMap<>(32);
+    assertThat(map.getSegmentsSize(), equalTo(16));
+    assertThat(map.getSegment(0).getSize(), equalTo(2));
+    assertThat(map.getLoadFactor(), equalTo(0.75f));
+  }
+
+  @Test
+  void shouldCreateWithInitialCapacityAndLoadFactor() {
+    ConcurrentReferenceHashMap<Integer, String> map = new ConcurrentReferenceHashMap<>(32, 0.5f);
+    assertThat(map.getSegmentsSize(), equalTo(16));
+    assertThat(map.getSegment(0).getSize(), equalTo(2));
+    assertThat(map.getLoadFactor(), equalTo(0.5f));
+  }
+
+  @Test
+  void shouldCreateWithInitialCapacityAndConcurrentLevel() {
+    ConcurrentReferenceHashMap<Integer, String> map = new ConcurrentReferenceHashMap<>(16, 2);
+    assertThat(map.getSegmentsSize(), equalTo(2));
+    assertThat(map.getSegment(0).getSize(), equalTo(8));
+    assertThat(map.getLoadFactor(), equalTo(0.75f));
+  }
+
+  @Test
+  void shouldCreateFullyCustom() {
+    ConcurrentReferenceHashMap<Integer, String> map = new ConcurrentReferenceHashMap<>(5, 0.5f, 3);
+    // concurrencyLevel of 3 ends up as 4 (nearest power of 2)
+    assertThat(map.getSegmentsSize(), equalTo(4));
+    // initialCapacity is 5/4 (rounded up, to nearest power of 2)
+    assertThat(map.getSegment(0).getSize(), equalTo(2));
+    assertThat(map.getLoadFactor(), equalTo(0.5f));
+  }
+
+  @Test
+  void shouldNeedNonNegativeInitialCapacity() {
+    new ConcurrentReferenceHashMap<Integer, String>(0, 1);
+    IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+        () -> new TestWeakConcurrentCache<Integer, String>(-1, 1));
+    assertTrue(e.getMessage().contains("Initial capacity must not be negative"));
+  }
+
+  @Test
+  void shouldNeedPositiveLoadFactor() {
+    new ConcurrentReferenceHashMap<Integer, String>(0, 0.1f, 1);
+    IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+        () -> new TestWeakConcurrentCache<Integer, String>(0, 0.0f, 1));
+    assertTrue(e.getMessage().contains("Load factor must be positive"));
+  }
+
+  @Test
+  void shouldNeedPositiveConcurrencyLevel() {
+    new ConcurrentReferenceHashMap<Integer, String>(1, 1);
+    IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+        () -> new TestWeakConcurrentCache<Integer, String>(1, 0));
+    assertTrue(e.getMessage().contains("Concurrency level must be positive"));
+  }
+
+  @Test
+  void shouldPutAndGet() {
+    // NOTE we are using mock references so we don't need to worry about GC
+    assertEquals(0, this.map.size());
+    this.map.put(123, "123");
+    assertThat(this.map.get(123), equalTo("123"));
+    assertEquals(1, this.map.size());
+    this.map.put(123, "123b");
+    assertEquals(1, this.map.size());
+    this.map.put(123, null);
+    assertEquals(1, this.map.size());
+  }
+
+  @Test
+  void shouldReplaceOnDoublePut() {
+    this.map.put(123, "321");
+    this.map.put(123, "123");
+    assertThat(this.map.get(123), equalTo("123"));
+  }
+
+  @Test
+  void shouldPutNullKey() {
+    assertNull(this.map.get(null));
+    assertThat(this.map.getOrDefault(null, "456"), equalTo("456"));
+    this.map.put(null, "123");
+    assertThat(this.map.get(null), equalTo("123"));
+    assertThat(this.map.getOrDefault(null, "456"), equalTo("123"));
+  }
+
+  @Test
+  void shouldPutNullValue() {
+    assertNull(this.map.get(123));
+    assertThat(this.map.getOrDefault(123, "456"), equalTo("456"));
+    this.map.put(123, "321");
+    assertThat(this.map.get(123), equalTo("321"));
+    assertThat(this.map.getOrDefault(123, "456"), equalTo("321"));
+    this.map.put(123, null);
+    assertNull(this.map.get(123));
+    assertNull(this.map.getOrDefault(123, "456"));
+  }
+
+  @Test
+  void shouldGetWithNoItems() {
+    assertNull(this.map.get(123));
+  }
+
+  @Test
+  void shouldApplySupplementalHash() {
+    Integer key = 123;

Review Comment:
   ## Boxed variable is never null
   
   The variable 'key' is only assigned values of primitive type and is never 'null', but it is declared with the boxed type 'Integer'.
   
   [Show more details](https://github.com/apache/avro/security/code-scanning/2975)



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@avro.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [avro] nielsbasjes commented on pull request #2090: [AVRO-3713]: [Java] Fix Map synchronization regression

Posted by "nielsbasjes (via GitHub)" <gi...@apache.org>.
nielsbasjes commented on PR #2090:
URL: https://github.com/apache/avro/pull/2090#issuecomment-1433333975

   > This all fine to me; so need now a commiter to read/merge this code.
   
   I'm not worried about that ... ;)
   https://people.apache.org/phonebook.html?uid=nielsbasjes
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@avro.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [avro] github-code-scanning[bot] commented on a diff in pull request #2090: AVRO-3713: [Java] Fix Map synchronization regression

Posted by "github-code-scanning[bot] (via GitHub)" <gi...@apache.org>.
github-code-scanning[bot] commented on code in PR #2090:
URL: https://github.com/apache/avro/pull/2090#discussion_r1109936630


##########
lang/java/avro/src/test/java/org/apache/avro/util/springframework/TestConcurrentReferenceHashMap.java:
##########
@@ -0,0 +1,688 @@
+/*
+ * Copyright 2002-2021 the original author or authors.
+ *
+ * Licensed 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
+ *
+ *      https://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.avro.util.springframework;
+
+import org.apache.avro.reflect.Nullable;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.apache.avro.util.springframework.ConcurrentReferenceHashMap.Entry;
+import org.apache.avro.util.springframework.ConcurrentReferenceHashMap.Reference;
+import org.apache.avro.util.springframework.ConcurrentReferenceHashMap.Restructure;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests for {@link ConcurrentReferenceHashMap}.
+ *
+ * @author Phillip Webb
+ * @author Juergen Hoeller
+ */
+class TestConcurrentReferenceHashMap {
+
+  private static final Comparator<? super String> NULL_SAFE_STRING_SORT = new NullSafeComparator<>(
+      new ComparableComparator<String>(), true);
+
+  private TestWeakConcurrentCache<Integer, String> map = new TestWeakConcurrentCache<>();
+
+  @Test
+  void shouldCreateWithDefaults() {
+    ConcurrentReferenceHashMap<Integer, String> map = new ConcurrentReferenceHashMap<>();
+    assertThat(map.getSegmentsSize(), equalTo(16));
+    assertThat(map.getSegment(0).getSize(), equalTo(1));
+    assertThat(map.getLoadFactor(), equalTo(0.75f));
+  }
+
+  @Test
+  void shouldCreateWithInitialCapacity() {
+    ConcurrentReferenceHashMap<Integer, String> map = new ConcurrentReferenceHashMap<>(32);
+    assertThat(map.getSegmentsSize(), equalTo(16));
+    assertThat(map.getSegment(0).getSize(), equalTo(2));
+    assertThat(map.getLoadFactor(), equalTo(0.75f));
+  }
+
+  @Test
+  void shouldCreateWithInitialCapacityAndLoadFactor() {
+    ConcurrentReferenceHashMap<Integer, String> map = new ConcurrentReferenceHashMap<>(32, 0.5f);
+    assertThat(map.getSegmentsSize(), equalTo(16));
+    assertThat(map.getSegment(0).getSize(), equalTo(2));
+    assertThat(map.getLoadFactor(), equalTo(0.5f));
+  }
+
+  @Test
+  void shouldCreateWithInitialCapacityAndConcurrentLevel() {
+    ConcurrentReferenceHashMap<Integer, String> map = new ConcurrentReferenceHashMap<>(16, 2);
+    assertThat(map.getSegmentsSize(), equalTo(2));
+    assertThat(map.getSegment(0).getSize(), equalTo(8));
+    assertThat(map.getLoadFactor(), equalTo(0.75f));
+  }
+
+  @Test
+  void shouldCreateFullyCustom() {
+    ConcurrentReferenceHashMap<Integer, String> map = new ConcurrentReferenceHashMap<>(5, 0.5f, 3);
+    // concurrencyLevel of 3 ends up as 4 (nearest power of 2)
+    assertThat(map.getSegmentsSize(), equalTo(4));
+    // initialCapacity is 5/4 (rounded up, to nearest power of 2)
+    assertThat(map.getSegment(0).getSize(), equalTo(2));
+    assertThat(map.getLoadFactor(), equalTo(0.5f));
+  }
+
+  @Test
+  void shouldNeedNonNegativeInitialCapacity() {
+    new ConcurrentReferenceHashMap<Integer, String>(0, 1);
+    IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+        () -> new TestWeakConcurrentCache<Integer, String>(-1, 1));
+    assertTrue(e.getMessage().contains("Initial capacity must not be negative"));
+  }
+
+  @Test
+  void shouldNeedPositiveLoadFactor() {
+    new ConcurrentReferenceHashMap<Integer, String>(0, 0.1f, 1);
+    IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+        () -> new TestWeakConcurrentCache<Integer, String>(0, 0.0f, 1));
+    assertTrue(e.getMessage().contains("Load factor must be positive"));
+  }
+
+  @Test
+  void shouldNeedPositiveConcurrencyLevel() {
+    new ConcurrentReferenceHashMap<Integer, String>(1, 1);
+    IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+        () -> new TestWeakConcurrentCache<Integer, String>(1, 0));
+    assertTrue(e.getMessage().contains("Concurrency level must be positive"));
+  }
+
+  @Test
+  void shouldPutAndGet() {
+    // NOTE we are using mock references so we don't need to worry about GC
+    assertEquals(0, this.map.size());
+    this.map.put(123, "123");
+    assertThat(this.map.get(123), equalTo("123"));
+    assertEquals(1, this.map.size());
+    this.map.put(123, "123b");
+    assertEquals(1, this.map.size());
+    this.map.put(123, null);
+    assertEquals(1, this.map.size());
+  }
+
+  @Test
+  void shouldReplaceOnDoublePut() {
+    this.map.put(123, "321");
+    this.map.put(123, "123");
+    assertThat(this.map.get(123), equalTo("123"));
+  }
+
+  @Test
+  void shouldPutNullKey() {
+    assertNull(this.map.get(null));
+    assertThat(this.map.getOrDefault(null, "456"), equalTo("456"));
+    this.map.put(null, "123");
+    assertThat(this.map.get(null), equalTo("123"));
+    assertThat(this.map.getOrDefault(null, "456"), equalTo("123"));
+  }
+
+  @Test
+  void shouldPutNullValue() {
+    assertNull(this.map.get(123));
+    assertThat(this.map.getOrDefault(123, "456"), equalTo("456"));
+    this.map.put(123, "321");
+    assertThat(this.map.get(123), equalTo("321"));
+    assertThat(this.map.getOrDefault(123, "456"), equalTo("321"));
+    this.map.put(123, null);
+    assertNull(this.map.get(123));
+    assertNull(this.map.getOrDefault(123, "456"));
+  }
+
+  @Test
+  void shouldGetWithNoItems() {
+    assertNull(this.map.get(123));
+  }
+
+  @Test
+  void shouldApplySupplementalHash() {
+    Integer key = 123;

Review Comment:
   ## Boxed variable is never null
   
   The variable 'key' is only assigned values of primitive type and is never 'null', but it is declared with the boxed type 'Integer'.
   
   [Show more details](https://github.com/apache/avro/security/code-scanning/2976)



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@avro.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [avro] clesaec commented on pull request #2090: [AVRO-3713]: [Java] Fix Map synchronization regression

Posted by "clesaec (via GitHub)" <gi...@apache.org>.
clesaec commented on PR #2090:
URL: https://github.com/apache/avro/pull/2090#issuecomment-1428000122

   Yes, sorry, i put LGTM may be little fast, as i was agree with usage of "computeIfAbsent is OK, but indeed, Weak keys are useful here, because the Map is owned by a Singleton, so, even if a field instance goes out of the scope, it wouldn't be garbaged because of this Map.
   "Threadsafe weak keys map implementation " : No i don't know any one, but as it's a private field only used by one method, and value is only set once, there is may be another way, like 
   
   `private final Map<Field, Object> defaultValueCache = new WeakHashMap<>();`
   and
   ```java
       final Object defaultValue;
       if (!defaultValueCache.containsKey(field)) {
         defaultValue = ... calculate value here ... 
         synchronized (this.defaultValueCache) {
            this.defaultValueCache.put(field, defaultValue);
         }
       }
       else {
           defaultValue = defaultValueCache.get(field);
       }
   ```
   Map without "delete", don't have to be synchronized on "get" ?
   
   
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@avro.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [avro] nielsbasjes commented on pull request #2090: [AVRO-3713]: [Java] Fix Map synchronization regression

Posted by "nielsbasjes (via GitHub)" <gi...@apache.org>.
nielsbasjes commented on PR #2090:
URL: https://github.com/apache/avro/pull/2090#issuecomment-1430570488

   The simplest solution I have found so far is to simply copy the ConcurrentReferenceHashMap from the Springframework and simply use that implementation.
   It is Apache licensed and is only a single file.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@avro.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [avro] nielsbasjes merged pull request #2090: AVRO-3713: [Java] Fix Map synchronization regression

Posted by "nielsbasjes (via GitHub)" <gi...@apache.org>.
nielsbasjes merged PR #2090:
URL: https://github.com/apache/avro/pull/2090


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: dev-unsubscribe@avro.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


[GitHub] [avro] clesaec commented on pull request #2090: [AVRO-3713]: [Java] Fix Map synchronization regression

Posted by "clesaec (via GitHub)" <gi...@apache.org>.
clesaec commented on PR #2090:
URL: https://github.com/apache/avro/pull/2090#issuecomment-1431023236

   Big hammer for small part of code that could be fixed by simple ReentrantReadWriteLock ... But, at least, it will be able to be used by other part of Avro.
   - Do we keep "springframework" in package name ? (i don't know what is the usual practice on it ? @RyanSkraba ?
   - as CI result say, run "'mvn spotless:apply'"
   - May be copy also some unit tests from springframework for this new collection class, as [this one for example](https://github.com/spring-projects/spring-framework/blob/main/spring-core/src/test/java/org/springframework/util/ConcurrentReferenceHashMapTests.java) ?


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@avro.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org