diff --git a/jre_emul/Classes/com/google/j2objc/util/ReflectionUtil.java b/jre_emul/Classes/com/google/j2objc/util/ReflectionUtil.java index 7f6b4832ec..fe2652a431 100644 --- a/jre_emul/Classes/com/google/j2objc/util/ReflectionUtil.java +++ b/jre_emul/Classes/com/google/j2objc/util/ReflectionUtil.java @@ -66,4 +66,12 @@ public static boolean isJreReflectionStripped() { // For more details about this behavior see {@link #matchClassNamePrefix}. return Boolean.TRUE.getClass().getName().equals("JavaLangBoolean"); } + + /** + * Checks if the given object is a NSNumber originating from ObjC by checking that it doesn't have + * class metadata. We use this for cross-language equality support, primarily for hashtable keys. + */ + public static native boolean isObjCNumber(Object obj) /*-[ + return [obj isKindOfClass:[NSNumber class]] && [[obj java_getClass] getMetadata] == NULL; + ]-*/; } diff --git a/jre_emul/android/platform/libcore/harmony-tests/src/test/java/org/apache/harmony/tests/java/lang/ByteTest.java b/jre_emul/android/platform/libcore/harmony-tests/src/test/java/org/apache/harmony/tests/java/lang/ByteTest.java index bbff653d67..c922cf5504 100644 --- a/jre_emul/android/platform/libcore/harmony-tests/src/test/java/org/apache/harmony/tests/java/lang/ByteTest.java +++ b/jre_emul/android/platform/libcore/harmony-tests/src/test/java/org/apache/harmony/tests/java/lang/ByteTest.java @@ -40,10 +40,10 @@ public void test_valueOfB() { * java.lang.Byte#hashCode() */ public void test_hashCode() { - assertEquals(1, new Byte((byte) 1).hashCode()); - assertEquals(2, new Byte((byte) 2).hashCode()); + assertEquals(-1640531535, new Byte((byte) 1).hashCode()); + assertEquals(1013904226, new Byte((byte) 2).hashCode()); assertEquals(0, new Byte((byte) 0).hashCode()); - assertEquals(-1, new Byte((byte) -1).hashCode()); + assertEquals(-1640531535, new Byte((byte) -1).hashCode()); } /** @@ -510,8 +510,8 @@ public void test_floatValue2() { * java.lang.Byte#hashCode() */ public void test_hashCode2() { - // Test for method int java.lang.Byte.hashCode() - assertEquals("Incorrect hash returned", 127, new Byte((byte) 127).hashCode()); + // Test for method int java.lang.Byte.hashCode() + assertEquals("Incorrect hash returned", 2105892559, new Byte((byte) 127).hashCode()); } /** diff --git a/jre_emul/android/platform/libcore/harmony-tests/src/test/java/org/apache/harmony/tests/java/lang/FloatTest.java b/jre_emul/android/platform/libcore/harmony-tests/src/test/java/org/apache/harmony/tests/java/lang/FloatTest.java index 0353068d09..28bcf1c944 100644 --- a/jre_emul/android/platform/libcore/harmony-tests/src/test/java/org/apache/harmony/tests/java/lang/FloatTest.java +++ b/jre_emul/android/platform/libcore/harmony-tests/src/test/java/org/apache/harmony/tests/java/lang/FloatTest.java @@ -207,16 +207,13 @@ public void test_floatValue() { public void test_hashCode() { // Test for method int java.lang.Float.hashCode() Float f = new Float(1908.8786f); - assertTrue("Returned invalid hash code for 1908.8786f", f.hashCode() == Float - .floatToIntBits(1908.8786f)); + assertEquals("Returned invalid hash code for 1908.8786f", -743541531, f.hashCode()); f = new Float(-1.112f); - assertTrue("Returned invalid hash code for -1.112", f.hashCode() == Float - .floatToIntBits(-1.112f)); + assertEquals("Returned invalid hash code for -1.112", -1640531535, f.hashCode()); f = new Float(0f); - assertTrue("Returned invalid hash code for 0", f.hashCode() == Float.floatToIntBits(0f)); - + assertEquals("Returned invalid hash code for 0", 0, f.hashCode()); } /** diff --git a/jre_emul/android/platform/libcore/harmony-tests/src/test/java/org/apache/harmony/tests/java/lang/IntegerTest.java b/jre_emul/android/platform/libcore/harmony-tests/src/test/java/org/apache/harmony/tests/java/lang/IntegerTest.java index 3c7faa304e..4a552da257 100644 --- a/jre_emul/android/platform/libcore/harmony-tests/src/test/java/org/apache/harmony/tests/java/lang/IntegerTest.java +++ b/jre_emul/android/platform/libcore/harmony-tests/src/test/java/org/apache/harmony/tests/java/lang/IntegerTest.java @@ -283,8 +283,8 @@ public void test_hashCode2() { Integer i1 = new Integer(1000); Integer i2 = new Integer(-1000); - assertTrue("Returned incorrect hashcode", i1.hashCode() == 1000 - && (i2.hashCode() == -1000)); + assertTrue("Returned incorrect hashcode", i1.hashCode() == 145972072 + && (i2.hashCode() == 145972072)); } /** @@ -688,10 +688,10 @@ public void test_valueOfI() { * java.lang.Integer#hashCode() */ public void test_hashCode() { - assertEquals(1, new Integer(1).hashCode()); - assertEquals(2, new Integer(2).hashCode()); + assertEquals(-1640531535, new Integer(1).hashCode()); + assertEquals(1013904226, new Integer(2).hashCode()); assertEquals(0, new Integer(0).hashCode()); - assertEquals(-1, new Integer(-1).hashCode()); + assertEquals(-1640531535, new Integer(-1).hashCode()); } /** diff --git a/jre_emul/android/platform/libcore/harmony-tests/src/test/java/org/apache/harmony/tests/java/lang/LongTest.java b/jre_emul/android/platform/libcore/harmony-tests/src/test/java/org/apache/harmony/tests/java/lang/LongTest.java index 9da75cfc42..330e1cc723 100644 --- a/jre_emul/android/platform/libcore/harmony-tests/src/test/java/org/apache/harmony/tests/java/lang/LongTest.java +++ b/jre_emul/android/platform/libcore/harmony-tests/src/test/java/org/apache/harmony/tests/java/lang/LongTest.java @@ -526,10 +526,10 @@ public void test_valueOfJ() { * java.lang.Long#hashCode() */ public void test_hashCode() { - assertEquals((int) (1L ^ (1L >>> 32)), new Long(1).hashCode()); - assertEquals((int) (2L ^ (2L >>> 32)), new Long(2).hashCode()); - assertEquals((int) (0L ^ (0L >>> 32)), new Long(0).hashCode()); - assertEquals((int) (-1L ^ (-1L >>> 32)), new Long(-1).hashCode()); + assertEquals(-1640531535, new Long(1).hashCode()); + assertEquals(1013904226, new Long(2).hashCode()); + assertEquals(0, new Long(0).hashCode()); + assertEquals(-1640531535, new Long(-1).hashCode()); } /** diff --git a/jre_emul/android/platform/libcore/harmony-tests/src/test/java/org/apache/harmony/tests/java/lang/ShortTest.java b/jre_emul/android/platform/libcore/harmony-tests/src/test/java/org/apache/harmony/tests/java/lang/ShortTest.java index bf747e7005..03a767fabe 100644 --- a/jre_emul/android/platform/libcore/harmony-tests/src/test/java/org/apache/harmony/tests/java/lang/ShortTest.java +++ b/jre_emul/android/platform/libcore/harmony-tests/src/test/java/org/apache/harmony/tests/java/lang/ShortTest.java @@ -361,10 +361,10 @@ public void test_valueOfS() { * java.lang.Short#hashCode() */ public void test_hashCode() { - assertEquals(1, new Short((short) 1).hashCode()); - assertEquals(2, new Short((short) 2).hashCode()); + assertEquals(-1640531535, new Short((short) 1).hashCode()); + assertEquals(1013904226, new Short((short) 2).hashCode()); assertEquals(0, new Short((short) 0).hashCode()); - assertEquals(-1, new Short((short) -1).hashCode()); + assertEquals(-1640531535, new Short((short) -1).hashCode()); } /** diff --git a/jre_emul/android/platform/libcore/harmony-tests/src/test/java/org/apache/harmony/tests/java/util/FormatterTest.java b/jre_emul/android/platform/libcore/harmony-tests/src/test/java/org/apache/harmony/tests/java/util/FormatterTest.java index b3f9452f55..028daac630 100644 --- a/jre_emul/android/platform/libcore/harmony-tests/src/test/java/org/apache/harmony/tests/java/util/FormatterTest.java +++ b/jre_emul/android/platform/libcore/harmony-tests/src/test/java/org/apache/harmony/tests/java/util/FormatterTest.java @@ -773,7 +773,7 @@ public void test_ioException() throws IOException { formatter = new Formatter(Locale.FRANCE); formatter.format( - "xx%1$s22%2$s%s%>> 32)); - } + public static native int hashCode(double value) /*-[ + return (jint)[@(value) hash]; + ]-*/; /** * Compares this object against the specified object. The result @@ -927,8 +927,8 @@ public static int hashCode(double value) { * @jls 15.21.1 Numerical Equality Operators == and != */ public boolean equals(Object obj) { - return (obj instanceof Double) - && (doubleToLongBits(((Double)obj).value) == + return (obj instanceof Double || ReflectionUtil.isObjCNumber(obj)) + && (doubleToLongBits(((Number) obj).doubleValue()) == doubleToLongBits(value)); } diff --git a/jre_emul/android/platform/libcore/ojluni/src/main/java/java/lang/Float.java b/jre_emul/android/platform/libcore/ojluni/src/main/java/java/lang/Float.java index 65a828c791..59c9ff5279 100644 --- a/jre_emul/android/platform/libcore/ojluni/src/main/java/java/lang/Float.java +++ b/jre_emul/android/platform/libcore/ojluni/src/main/java/java/lang/Float.java @@ -34,6 +34,7 @@ */ // END Android-removed: dynamic constants not supported on Android. +import com.google.j2objc.util.ReflectionUtil; import sun.misc.FloatingDecimal; /** @@ -720,9 +721,9 @@ public int hashCode() { * @return a hash code value for a {@code float} value. * @since 1.8 */ - public static int hashCode(float value) { - return floatToIntBits(value); - } + public static native int hashCode(float value) /*-[ + return (jint)[@(value) hash]; + ]-*/; /** * Compares this object against the specified object. The result @@ -752,8 +753,8 @@ public static int hashCode(float value) { * @jls 15.21.1 Numerical Equality Operators == and != */ public boolean equals(Object obj) { - return (obj instanceof Float) - && (floatToIntBits(((Float)obj).value) == floatToIntBits(value)); + return (obj instanceof Float || ReflectionUtil.isObjCNumber(obj)) + && (floatToIntBits(((Number) obj).floatValue()) == floatToIntBits(value)); } /** diff --git a/jre_emul/android/platform/libcore/ojluni/src/main/java/java/lang/Integer.java b/jre_emul/android/platform/libcore/ojluni/src/main/java/java/lang/Integer.java index c80daca4e7..6ca133ba0f 100644 --- a/jre_emul/android/platform/libcore/ojluni/src/main/java/java/lang/Integer.java +++ b/jre_emul/android/platform/libcore/ojluni/src/main/java/java/lang/Integer.java @@ -25,6 +25,8 @@ package java.lang; +import com.google.j2objc.util.ReflectionUtil; + /* J2ObjC removed import jdk.internal.vm.annotation.IntrinsicCandidate; */ @@ -1039,9 +1041,9 @@ public int hashCode() { * * @return a hash code value for an {@code int} value. */ - public static int hashCode(int value) { - return value; - } + public static native int hashCode(int value) /*-[ + return (jint)[@(value) hash]; + ]-*/; /** * Compares this object to the specified object. The result is @@ -1054,8 +1056,8 @@ public static int hashCode(int value) { * {@code false} otherwise. */ public boolean equals(Object obj) { - if (obj instanceof Integer) { - return value == ((Integer)obj).intValue(); + if (obj instanceof Integer || ReflectionUtil.isObjCNumber(obj)) { + return value == ((Number) obj).intValue(); } return false; } diff --git a/jre_emul/android/platform/libcore/ojluni/src/main/java/java/lang/Long.java b/jre_emul/android/platform/libcore/ojluni/src/main/java/java/lang/Long.java index ea619a5576..20bce5b722 100644 --- a/jre_emul/android/platform/libcore/ojluni/src/main/java/java/lang/Long.java +++ b/jre_emul/android/platform/libcore/ojluni/src/main/java/java/lang/Long.java @@ -25,6 +25,8 @@ package java.lang; +import com.google.j2objc.util.ReflectionUtil; + /* J2ObjC removed import jdk.internal.vm.annotation.IntrinsicCandidate; */ @@ -1265,9 +1267,9 @@ public int hashCode() { * @return a hash code value for a {@code long} value. * @since 1.8 */ - public static int hashCode(long value) { - return (int)(value ^ (value >>> 32)); - } + public static native int hashCode(long value) /*-[ + return (jint)[@(value) hash]; + ]-*/; /** * Compares this object to the specified object. The result is @@ -1280,8 +1282,8 @@ public static int hashCode(long value) { * {@code false} otherwise. */ public boolean equals(Object obj) { - if (obj instanceof Long) { - return value == ((Long)obj).longValue(); + if (obj instanceof Long || ReflectionUtil.isObjCNumber(obj)) { + return value == ((Number) obj).longValue(); } return false; } diff --git a/jre_emul/android/platform/libcore/ojluni/src/main/java/java/lang/Short.java b/jre_emul/android/platform/libcore/ojluni/src/main/java/java/lang/Short.java index 4be3273042..cf9be3751e 100644 --- a/jre_emul/android/platform/libcore/ojluni/src/main/java/java/lang/Short.java +++ b/jre_emul/android/platform/libcore/ojluni/src/main/java/java/lang/Short.java @@ -25,6 +25,8 @@ package java.lang; +import com.google.j2objc.util.ReflectionUtil; + /* J2ObjC removed import jdk.internal.vm.annotation.IntrinsicCandidate; */ @@ -479,17 +481,16 @@ public int hashCode() { return Short.hashCode(value); } - /** - * Returns a hash code for a {@code short} value; compatible with - * {@code Short.hashCode()}. - * - * @param value the value to hash - * @return a hash code value for a {@code short} value. - * @since 1.8 - */ - public static int hashCode(short value) { - return (int)value; - } + /** + * Returns a hash code for a {@code short} value; compatible with {@code Short.hashCode()}. + * + * @param value the value to hash + * @return a hash code value for a {@code short} value. + * @since 1.8 + */ + public static native int hashCode(short value) /*-[ + return (jint)[@(value) hash]; + ]-*/; /** * Compares this object to the specified object. The result is @@ -502,8 +503,8 @@ public static int hashCode(short value) { * {@code false} otherwise. */ public boolean equals(Object obj) { - if (obj instanceof Short) { - return value == ((Short)obj).shortValue(); + if (obj instanceof Short || ReflectionUtil.isObjCNumber(obj)) { + return value == ((Number) obj).shortValue(); } return false; } diff --git a/jre_emul/android/platform/libcore/ojluni/src/main/java/java/util/Arrays.java b/jre_emul/android/platform/libcore/ojluni/src/main/java/java/util/Arrays.java index 19770da14b..9a6669f859 100644 --- a/jre_emul/android/platform/libcore/ojluni/src/main/java/java/util/Arrays.java +++ b/jre_emul/android/platform/libcore/ojluni/src/main/java/java/util/Arrays.java @@ -4084,8 +4084,7 @@ public static int hashCode(long a[]) { int result = 1; for (long element : a) { - int elementHash = (int)(element ^ (element >>> 32)); - result = 31 * result + elementHash; + result = 31 * result + Long.hashCode(element); } return result; @@ -4113,7 +4112,7 @@ public static int hashCode(int a[]) { int result = 1; for (int element : a) - result = 31 * result + element; + result = 31 * result + Integer.hashCode(element); return result; } @@ -4140,7 +4139,7 @@ public static int hashCode(short a[]) { int result = 1; for (short element : a) - result = 31 * result + element; + result = 31 * result + Short.hashCode(element); return result; } @@ -4194,7 +4193,7 @@ public static int hashCode(byte a[]) { int result = 1; for (byte element : a) - result = 31 * result + element; + result = 31 * result + Byte.hashCode(element); return result; } @@ -4248,7 +4247,7 @@ public static int hashCode(float a[]) { int result = 1; for (float element : a) - result = 31 * result + Float.floatToIntBits(element); + result = 31 * result + Float.hashCode(element); return result; } @@ -4275,8 +4274,7 @@ public static int hashCode(double a[]) { int result = 1; for (double element : a) { - long bits = Double.doubleToLongBits(element); - result = 31 * result + (int)(bits ^ (bits >>> 32)); + result = 31 * result + Double.hashCode(element); } return result; } diff --git a/jre_emul/misc_tests/com/google/j2objc/ReflectionTest.java b/jre_emul/misc_tests/com/google/j2objc/ReflectionTest.java index cd9fb13eb5..041d131c14 100644 --- a/jre_emul/misc_tests/com/google/j2objc/ReflectionTest.java +++ b/jre_emul/misc_tests/com/google/j2objc/ReflectionTest.java @@ -201,4 +201,51 @@ public void testAnnotationInitializedWithDefaults() { assertEquals("hello", a.annotationValue().value()); assertEquals(Iterable.class, a.classValue()); } + + private static native Object getNativeNumber(long val) /*-[ + return [NSNumber numberWithLongLong:(long long)val]; + ]-*/; + + public void testIsObjCNumber() { + Object nativeNum = getNativeNumber(42); + Object javaNum = Integer.valueOf(42); + Object decimalNum = new java.math.BigDecimal("1.23"); + + assertTrue(com.google.j2objc.util.ReflectionUtil.isObjCNumber(nativeNum)); + assertFalse(com.google.j2objc.util.ReflectionUtil.isObjCNumber(javaNum)); + assertFalse(com.google.j2objc.util.ReflectionUtil.isObjCNumber(decimalNum)); + assertFalse(com.google.j2objc.util.ReflectionUtil.isObjCNumber("not a number")); + assertFalse(com.google.j2objc.util.ReflectionUtil.isObjCNumber(null)); + } + + public void testNumberHashMapCompatibility() { + java.util.HashMap map = new java.util.HashMap<>(); + + // Test with positive number (sign bit not set) + Object javaInt = Integer.valueOf(42); + Object objcInt = getNativeNumber(42); + + map.put((Number) javaInt, "success"); + assertEquals(1, map.size()); + assertEquals("success", map.get(objcInt)); + + // Overwrite existing key (proves equals() and hashCode() consistency in map bucket resolution) + map.put((Number) objcInt, "success_reverse"); + assertEquals(1, map.size()); + assertEquals("success_reverse", map.get(javaInt)); + + // Test with negative number (sign bit set in the hash code) + map.clear(); + Object javaIntMinus1 = Integer.valueOf(-1); + Object objcIntMinus1 = getNativeNumber(-1); + + map.put((Number) javaIntMinus1, "success_minus1"); + assertEquals(1, map.size()); + assertEquals("success_minus1", map.get(objcIntMinus1)); + + // Overwrite existing key + map.put((Number) objcIntMinus1, "success_minus1_reverse"); + assertEquals(1, map.size()); + assertEquals("success_minus1_reverse", map.get(javaIntMinus1)); + } }