You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2020/02/16 20:18:31 UTC

[commons-collections] branch master updated: [COLLECTIONS-747] MultiKey.getKeys class cast exception.

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

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-collections.git


The following commit(s) were added to refs/heads/master by this push:
     new 7d06bd7  [COLLECTIONS-747] MultiKey.getKeys class cast exception.
7d06bd7 is described below

commit 7d06bd77e9ebda68c79eac20075560d488a28364
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Sun Feb 16 15:18:27 2020 -0500

    [COLLECTIONS-747] MultiKey.getKeys class cast exception.
---
 src/changes/changes.xml                            |   3 +
 .../commons/collections4/keyvalue/MultiKey.java    | 192 +++++++++++++--------
 .../collections4/keyvalue/MultiKeyTest.java        |  12 +-
 3 files changed, 136 insertions(+), 71 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index ead842a..5ad5517 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -129,6 +129,9 @@
     <action dev="ggregory" type="update" due-to="Gary Gregory">
       [test] Update org.easymock:easymock 4.1 -> 4.2.
     </action>
+    <action issue="COLLECTIONS-747" dev="ggregory" type="fix" due-to="Gary Gregory, Walter Laan">
+      MultiKey.getKeys class cast exception.
+    </action>
   </release>
   <release version="4.4" date="2019-07-05" description="Maintenance release.">
     <action issue="COLLECTIONS-710" dev="ggregory" type="fix" due-to="Yu Shi, Gary Gregory">
diff --git a/src/main/java/org/apache/commons/collections4/keyvalue/MultiKey.java b/src/main/java/org/apache/commons/collections4/keyvalue/MultiKey.java
index 1261a06..c1080f8 100644
--- a/src/main/java/org/apache/commons/collections4/keyvalue/MultiKey.java
+++ b/src/main/java/org/apache/commons/collections4/keyvalue/MultiKey.java
@@ -17,6 +17,7 @@
 package org.apache.commons.collections4.keyvalue;
 
 import java.io.Serializable;
+import java.lang.reflect.Array;
 import java.util.Arrays;
 import java.util.Objects;
 
@@ -51,8 +52,69 @@ public class MultiKey<K> implements Serializable {
     /** Serialisation version */
     private static final long serialVersionUID = 4465448607415788805L;
 
+    @SuppressWarnings("unchecked")
+    private static <T> Class<? extends T> getClass(final T value) {
+        return (Class<? extends T>) (value == null ? Object.class : value.getClass());
+    }
+
+    private static <T> Class<? extends T> getComponentType(final T... values) {
+        @SuppressWarnings("unchecked")
+        final Class<? extends T> rootClass = (Class<? extends T>) Object.class;
+        if (values == null) {
+            return rootClass;
+        }
+        Class<? extends T> prevClass = values.length > 0 ? getClass(values[0]) : rootClass;
+        for (int i = 1; i < values.length; i++) {
+            final Class<? extends T> classI = getClass(values[i]);
+            if (prevClass != classI) {
+                return rootClass;
+            }
+            prevClass = classI;
+        }
+        return prevClass;
+    }
+
+    private static <T> T[] newArray(final T key1, final T key2) {
+        @SuppressWarnings("unchecked")
+        final T[] array = (T[]) Array.newInstance(getComponentType(key1, key2), 2);
+        array[0] = key1;
+        array[1] = key2;
+        return array;
+    }
+
+    private static <T> T[] newArray(final T key1, final T key2, final T key3) {
+        @SuppressWarnings("unchecked")
+        final T[] array = (T[]) Array.newInstance(getComponentType(key1, key2, key3), 3);
+        array[0] = key1;
+        array[1] = key2;
+        array[2] = key3;
+        return array;
+    }
+
+    private static <T> T[] newArray(final T key1, final T key2, final T key3, final T key4) {
+        @SuppressWarnings("unchecked")
+        final T[] array = (T[]) Array.newInstance(getComponentType(key1, key2, key3, key4), 4);
+        array[0] = key1;
+        array[1] = key2;
+        array[2] = key3;
+        array[3] = key4;
+        return array;
+    }
+
+    private static <T> T[] newArray(final T key1, final T key2, final T key3, final T key4, final T key5) {
+        @SuppressWarnings("unchecked")
+        final T[] array = (T[]) Array.newInstance(getComponentType(key1, key2, key3, key4, key5), 5);
+        array[0] = key1;
+        array[1] = key2;
+        array[2] = key3;
+        array[3] = key4;
+        array[4] = key5;
+        return array;
+    }
+
     /** The individual keys */
     private final K[] keys;
+
     /** The cached hashCode */
     private transient int hashCode;
 
@@ -65,11 +127,11 @@ public class MultiKey<K> implements Serializable {
      * @param key1  the first key
      * @param key2  the second key
      */
-    @SuppressWarnings("unchecked")
     public MultiKey(final K key1, final K key2) {
-        this((K[]) new Object[] { key1, key2 }, false);
+        this(newArray(key1, key2), false);
     }
 
+
     /**
      * Constructor taking three keys.
      * <p>
@@ -80,9 +142,8 @@ public class MultiKey<K> implements Serializable {
      * @param key2  the second key
      * @param key3  the third key
      */
-    @SuppressWarnings("unchecked")
     public MultiKey(final K key1, final K key2, final K key3) {
-        this((K[]) new Object[] {key1, key2, key3}, false);
+        this(newArray(key1, key2, key3), false);
     }
 
     /**
@@ -96,9 +157,8 @@ public class MultiKey<K> implements Serializable {
      * @param key3  the third key
      * @param key4  the fourth key
      */
-    @SuppressWarnings("unchecked")
     public MultiKey(final K key1, final K key2, final K key3, final K key4) {
-        this((K[]) new Object[] {key1, key2, key3, key4}, false);
+        this(newArray(key1, key2, key3, key4), false);
     }
 
     /**
@@ -113,9 +173,8 @@ public class MultiKey<K> implements Serializable {
      * @param key4  the fourth key
      * @param key5  the fifth key
      */
-    @SuppressWarnings("unchecked")
     public MultiKey(final K key1, final K key2, final K key3, final K key4, final K key5) {
-        this((K[]) new Object[] {key1, key2, key3, key4, key5}, false);
+        this(newArray(key1, key2, key3, key4, key5), false);
     }
 
     /**
@@ -160,26 +219,45 @@ public class MultiKey<K> implements Serializable {
     public MultiKey(final K[] keys, final boolean makeClone) {
         super();
         Objects.requireNonNull(keys, "keys");
-        if (makeClone) {
-            this.keys = keys.clone();
-        } else {
-            this.keys = keys;
-        }
-
+        this.keys = makeClone ? keys.clone() : keys;
         calculateHashCode(keys);
     }
 
+    /**
+     * Calculate the hash code of the instance using the provided keys.
+     * @param keys the keys to calculate the hash code for
+     */
+    private void calculateHashCode(final Object[] keys)
+    {
+        int total = 0;
+        for (final Object key : keys) {
+            if (key != null) {
+                total ^= key.hashCode();
+            }
+        }
+        hashCode = total;
+    }
+
     //-----------------------------------------------------------------------
     /**
-     * Gets a clone of the array of keys.
+     * Compares this object to another.
      * <p>
-     * The keys should be immutable
-     * If they are not then they must not be changed.
+     * To be equal, the other object must be a {@code MultiKey} with the
+     * same number of keys which are also equal.
      *
-     * @return the individual keys
+     * @param other  the other object to compare to
+     * @return true if equal
      */
-    public K[] getKeys() {
-        return keys.clone();
+    @Override
+    public boolean equals(final Object other) {
+        if (other == this) {
+            return true;
+        }
+        if (other instanceof MultiKey) {
+            final MultiKey<?> otherMulti = (MultiKey<?>) other;
+            return Arrays.equals(keys, otherMulti.keys);
+        }
+        return false;
     }
 
     /**
@@ -197,36 +275,17 @@ public class MultiKey<K> implements Serializable {
         return keys[index];
     }
 
-    /**
-     * Gets the size of the list of keys.
-     *
-     * @return the size of the list of keys
-     * @since 3.1
-     */
-    public int size() {
-        return keys.length;
-    }
-
     //-----------------------------------------------------------------------
     /**
-     * Compares this object to another.
+     * Gets a clone of the array of keys.
      * <p>
-     * To be equal, the other object must be a {@code MultiKey} with the
-     * same number of keys which are also equal.
+     * The keys should be immutable
+     * If they are not then they must not be changed.
      *
-     * @param other  the other object to compare to
-     * @return true if equal
+     * @return the individual keys
      */
-    @Override
-    public boolean equals(final Object other) {
-        if (other == this) {
-            return true;
-        }
-        if (other instanceof MultiKey) {
-            final MultiKey<?> otherMulti = (MultiKey<?>) other;
-            return Arrays.equals(keys, otherMulti.keys);
-        }
-        return false;
+    public K[] getKeys() {
+        return keys.clone();
     }
 
     /**
@@ -245,38 +304,33 @@ public class MultiKey<K> implements Serializable {
     }
 
     /**
-     * Gets a debugging string version of the key.
-     *
-     * @return a debugging string
+     * Recalculate the hash code after deserialization. The hash code of some
+     * keys might have change (hash codes based on the system hash code are
+     * only stable for the same process).
+     * @return the instance with recalculated hash code
      */
-    @Override
-    public String toString() {
-        return "MultiKey" + Arrays.toString(keys);
+    protected Object readResolve() {
+        calculateHashCode(keys);
+        return this;
     }
 
     /**
-     * Calculate the hash code of the instance using the provided keys.
-     * @param keys the keys to calculate the hash code for
+     * Gets the size of the list of keys.
+     *
+     * @return the size of the list of keys
+     * @since 3.1
      */
-    private void calculateHashCode(final Object[] keys)
-    {
-        int total = 0;
-        for (final Object key : keys) {
-            if (key != null) {
-                total ^= key.hashCode();
-            }
-        }
-        hashCode = total;
+    public int size() {
+        return keys.length;
     }
 
     /**
-     * Recalculate the hash code after deserialization. The hash code of some
-     * keys might have change (hash codes based on the system hash code are
-     * only stable for the same process).
-     * @return the instance with recalculated hash code
+     * Gets a debugging string version of the key.
+     *
+     * @return a debugging string
      */
-    protected Object readResolve() {
-        calculateHashCode(keys);
-        return this;
+    @Override
+    public String toString() {
+        return "MultiKey" + Arrays.toString(keys);
     }
 }
diff --git a/src/test/java/org/apache/commons/collections4/keyvalue/MultiKeyTest.java b/src/test/java/org/apache/commons/collections4/keyvalue/MultiKeyTest.java
index f7e224d..2ba04b6 100644
--- a/src/test/java/org/apache/commons/collections4/keyvalue/MultiKeyTest.java
+++ b/src/test/java/org/apache/commons/collections4/keyvalue/MultiKeyTest.java
@@ -53,6 +53,7 @@ public class MultiKeyTest {
         }
 
     }
+    
     static class SystemHashCodeSimulatingKey implements Serializable {
 
         private static final long serialVersionUID = -1736147315703444603L;
@@ -82,13 +83,12 @@ public class MultiKeyTest {
             return this;
         }
     }
-
     Integer ONE = Integer.valueOf(1);
+
     Integer TWO = Integer.valueOf(2);
     Integer THREE = Integer.valueOf(3);
     Integer FOUR = Integer.valueOf(4);
     Integer FIVE = Integer.valueOf(5);
-
     //-----------------------------------------------------------------------
     @Test
     public void testConstructors() throws Exception {
@@ -294,4 +294,12 @@ public class MultiKeyTest {
         assertEquals(7, new MultiKey<>(new Integer[] { ONE, TWO, ONE, TWO, ONE, TWO, ONE }).size());
     }
 
+    @Test
+    public void testTwoArgCtor() {
+        MultiKeyTest key1 = new MultiKeyTest();
+        MultiKeyTest key2 = new MultiKeyTest();
+        MultiKeyTest[] keys = new MultiKey<>(key1, key2).getKeys();
+        assertNotNull(keys);
+    }
+
 }