classlib: fix for hashcode implementation in various classes, add other minor fixes/improvements (#752)

This commit is contained in:
Ivan Hetman 2023-09-21 16:30:41 +03:00 committed by GitHub
parent e13746a650
commit 887528531f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 111 additions and 70 deletions

View File

@ -21,7 +21,7 @@ public class TBoolean extends TObject implements TSerializable, TComparable<TBoo
public static final TBoolean TRUE = new TBoolean(true);
public static final TBoolean FALSE = new TBoolean(false);
public static final Class<Boolean> TYPE = boolean.class;
private boolean value;
private final boolean value;
public TBoolean(boolean value) {
this.value = value;
@ -37,16 +37,7 @@ public class TBoolean extends TObject implements TSerializable, TComparable<TBoo
}
public static int compare(boolean x, boolean y) {
if (x) {
if (!y) {
return 1;
}
} else {
if (y) {
return -1;
}
}
return 0;
return x == y ? 0 : x ? 1 : -1;
}
public static boolean parseBoolean(String s) {

View File

@ -21,7 +21,7 @@ public class TByte extends TNumber implements TComparable<TByte> {
public static final Class<Byte> TYPE = byte.class;
public static final int SIZE = 8;
public static final int BYTES = 1;
private byte value;
private final byte value;
public TByte(byte value) {
this.value = value;

View File

@ -91,7 +91,7 @@ public class TCharacter extends TObject implements TComparable<TCharacter> {
private static int[] upperCaseMapping;
private static int[] lowerCaseMapping;
private static UnicodeHelper.Range[] classMapping;
private char value;
private final char value;
private static TCharacter[] characterCache = new TCharacter[128];
private static final int SURROGATE_NEUTRAL_BIT_MASK = 0xF800;
private static final int SURROGATE_BITS = 0xD800;
@ -136,6 +136,10 @@ public class TCharacter extends TObject implements TComparable<TCharacter> {
@Override
public int hashCode() {
return hashCode(value);
}
public static int hashCode(char value) {
return value;
}

View File

@ -35,7 +35,7 @@ public class TDouble extends TNumber implements TComparable<TDouble> {
public static final int SIZE = 64;
public static final int BYTES = SIZE / Byte.SIZE;
public static final Class<Double> TYPE = double.class;
private double value;
private final double value;
public TDouble(double value) {
this.value = value;

View File

@ -33,7 +33,7 @@ public class TFloat extends TNumber implements TComparable<TFloat> {
public static final int SIZE = 32;
public static final int BYTES = SIZE / Byte.SIZE;
public static final Class<Float> TYPE = float.class;
private float value;
private final float value;
public TFloat(float value) {
this.value = value;

View File

@ -27,7 +27,7 @@ public class TInteger extends TNumber implements TComparable<TInteger> {
public static final int MAX_VALUE = 0x7FFFFFFF;
public static final Class<Integer> TYPE = int.class;
private static TInteger[] integerCache;
private int value;
private final int value;
public TInteger(int value) {
this.value = value;
@ -45,7 +45,7 @@ public class TInteger extends TNumber implements TComparable<TInteger> {
}
public static int hashCode(int value) {
return (value >>> 4) ^ (value << 28) ^ (value << 8) ^ (value >>> 24);
return value;
}
public static String toHexString(int i) {

View File

@ -26,7 +26,7 @@ public class TLong extends TNumber implements TComparable<TLong> {
public static final Class<Long> TYPE = long.class;
public static final int SIZE = 64;
public static final int BYTES = SIZE / Byte.SIZE;
private long value;
private final long value;
public TLong(long value) {
this.value = value;

View File

@ -21,7 +21,7 @@ public class TShort extends TNumber implements TComparable<TShort> {
public static final Class<Short> TYPE = short.class;
public static final int SIZE = 16;
public static final int BYTES = SIZE / Byte.SIZE;
private short value;
private final short value;
public TShort(short value) {
this.value = value;

View File

@ -15,6 +15,7 @@
*/
package org.teavm.classlib.java.util;
import java.util.Objects;
import org.teavm.classlib.java.lang.*;
import org.teavm.interop.Rename;
@ -161,7 +162,7 @@ public abstract class TAbstractList<E> extends TAbstractCollection<E> implements
int hashCode = 1;
for (TIterator<? extends E> iter = iterator(); iter.hasNext();) {
E elem = iter.next();
hashCode = 31 * hashCode + (elem != null ? elem.hashCode() : 0);
hashCode = 31 * hashCode + Objects.hashCode(elem);
}
return hashCode;
}

View File

@ -16,6 +16,7 @@
package org.teavm.classlib.java.util;
import java.util.Arrays;
import java.util.Objects;
import java.util.function.Consumer;
import org.teavm.classlib.java.io.TSerializable;
import org.teavm.classlib.java.lang.*;
@ -212,6 +213,15 @@ public class TArrayList<E> extends TAbstractList<E> implements TCloneable, TSeri
return buffer.append(']').toString();
}
@Override
public int hashCode() {
int result = 1;
for (int i = 0; i < size; i++) {
result = 31 * result + Objects.hashCode(array[i]);
}
return result;
}
@Override
public void sort(TComparator<? super E> comp) {
TArrays.sort(array, 0, size, comp);

View File

@ -22,10 +22,7 @@ import java.util.function.IntFunction;
import java.util.function.IntToDoubleFunction;
import java.util.function.IntToLongFunction;
import java.util.function.IntUnaryOperator;
import org.teavm.classlib.java.lang.TDouble;
import org.teavm.classlib.java.lang.TFloat;
import org.teavm.classlib.java.lang.TIllegalArgumentException;
import org.teavm.classlib.java.lang.TInteger;
import org.teavm.classlib.java.lang.TMath;
import org.teavm.classlib.java.lang.TObject;
import org.teavm.classlib.java.lang.TStringBuilder;
@ -1395,10 +1392,9 @@ public class TArrays extends TObject {
if (a == null) {
return 0;
}
int hash = 0xA5A537FC;
int hash = 1;
for (int i = 0; i < a.length; ++i) {
int h = a[i] ? 0x12345678 : 0x87654321;
hash = TInteger.rotateLeft(h, 4) ^ TInteger.rotateRight(h, 7) ^ TInteger.rotateLeft(hash, 13);
hash = 31 * hash + Boolean.hashCode(a[i]);
}
return hash;
}
@ -1407,10 +1403,9 @@ public class TArrays extends TObject {
if (a == null) {
return 0;
}
int hash = 0xA5A537FC;
int hash = 1;
for (int i = 0; i < a.length; ++i) {
int h = (int) (a[i] >>> 32) ^ (int) a[i];
hash = TInteger.rotateLeft(h, 4) ^ TInteger.rotateRight(h, 7) ^ TInteger.rotateLeft(hash, 13);
hash = 31 * hash + Long.hashCode(a[i]);
}
return hash;
}
@ -1421,7 +1416,7 @@ public class TArrays extends TObject {
}
int hash = 1;
for (int i = 0; i < a.length; ++i) {
hash = 31 * hash + a[i];
hash = 31 * hash + Integer.hashCode(a[i]);
}
return hash;
}
@ -1432,7 +1427,7 @@ public class TArrays extends TObject {
}
int hash = 1;
for (int i = 0; i < a.length; ++i) {
hash = 31 * hash + a[i];
hash = 31 * hash + Byte.hashCode(a[i]);
}
return hash;
}
@ -1443,7 +1438,7 @@ public class TArrays extends TObject {
}
int hash = 1;
for (int i = 0; i < a.length; ++i) {
hash = 31 * hash + a[i];
hash = 31 * hash + Short.hashCode(a[i]);
}
return hash;
}
@ -1454,7 +1449,7 @@ public class TArrays extends TObject {
}
int hash = 1;
for (int i = 0; i < a.length; ++i) {
hash = 31 * hash + a[i];
hash = 31 * hash + Character.hashCode(a[i]);
}
return hash;
}
@ -1463,10 +1458,9 @@ public class TArrays extends TObject {
if (a == null) {
return 0;
}
int hash = 0xA5A537FC;
int hash = 1;
for (int i = 0; i < a.length; ++i) {
int h = TFloat.floatToIntBits(a[i]);
hash = TInteger.rotateLeft(h, 4) ^ TInteger.rotateRight(h, 7) ^ TInteger.rotateLeft(hash, 13);
hash = 31 * hash + Float.hashCode(a[i]);
}
return hash;
}
@ -1475,11 +1469,9 @@ public class TArrays extends TObject {
if (a == null) {
return 0;
}
int hash = 0xA5A537FC;
int hash = 1;
for (int i = 0; i < a.length; ++i) {
long lh = TDouble.doubleToLongBits(a[i]);
int h = (int) lh ^ (int) (lh >> 32);
hash = TInteger.rotateLeft(h, 4) ^ TInteger.rotateRight(h, 7) ^ TInteger.rotateLeft(hash, 13);
hash = 31 * hash + Double.hashCode(a[i]);
}
return hash;
}
@ -1488,10 +1480,9 @@ public class TArrays extends TObject {
if (a == null) {
return 0;
}
int hash = 0xA5A537FC;
int hash = 1;
for (int i = 0; i < a.length; ++i) {
int h = TObjects.hashCode(a[i]) ^ 0x1F7A58E0;
hash = TInteger.rotateLeft(h, 4) ^ TInteger.rotateRight(h, 7) ^ TInteger.rotateLeft(hash, 13);
hash = 31 * hash + Objects.hashCode(a[i]);
}
return hash;
}
@ -1500,7 +1491,7 @@ public class TArrays extends TObject {
if (a == null) {
return 0;
}
int hash = 0xA5A537FC;
int hash = 1;
for (int i = 0; i < a.length; ++i) {
Object el = a[i];
int h;
@ -1523,9 +1514,9 @@ public class TArrays extends TObject {
} else if (a[i] instanceof Object[]) {
h = deepHashCode((Object[]) el);
} else {
h = TObjects.hashCode(el) ^ 0x1F7A58E0;
h = Objects.hashCode(el);
}
hash = TInteger.rotateLeft(h, 4) ^ TInteger.rotateRight(h, 7) ^ TInteger.rotateLeft(hash, 13);
hash = 31 * hash + h;
}
return hash;
}

View File

@ -33,6 +33,7 @@
package org.teavm.classlib.java.util;
import java.util.Arrays;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
@ -60,7 +61,7 @@ public class THashMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
HashEntry(K theKey, V theValue) {
super(theKey, theValue);
origKeyHash = theKey == null ? 0 : computeHashCode(theKey);
origKeyHash = Objects.hashCode(theKey);
}
@Override
@ -395,7 +396,7 @@ public class THashMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
if (key == null) {
m = findNullKeyEntry();
} else {
int hash = computeHashCode(key);
int hash = key.hashCode();
int index = hash & (elementData.length - 1);
m = findNonNullKeyEntry(key, index, hash);
}
@ -480,7 +481,7 @@ public class THashMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
}
}
} else {
int hash = computeHashCode(key);
int hash = key.hashCode();
int index = hash & (elementData.length - 1);
entry = findNonNullKeyEntry(key, index, hash);
if (entry == null) {
@ -576,7 +577,7 @@ public class THashMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
HashEntry<K, V> entry;
HashEntry<K, V> last = null;
if (key != null) {
int hash = computeHashCode(key);
int hash = key.hashCode();
index = hash & (elementData.length - 1);
entry = elementData[index];
while (entry != null && !(entry.origKeyHash == hash && areEqualKeys(key, entry.key))) {
@ -678,10 +679,6 @@ public class THashMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
}
}
static int computeHashCode(Object key) {
return key.hashCode();
}
static boolean areEqualKeys(Object key1, Object key2) {
return (key1 == key2) || key1.equals(key2);
}

View File

@ -17,6 +17,7 @@ package org.teavm.classlib.java.util;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Objects;
import org.teavm.classlib.java.io.TSerializable;
import org.teavm.classlib.java.lang.TCloneNotSupportedException;
import org.teavm.classlib.java.lang.TCloneable;
@ -194,7 +195,7 @@ public class TVector<E> extends TAbstractList<E> implements TList<E>, TRandomAcc
while (it.hasNext()) {
Object e1 = elementData[index++];
Object e2 = it.next();
if (!(e1 == null ? e2 == null : e1.equals(e2))) {
if (!Objects.equals(e1, e2)) {
return false;
}
}
@ -268,7 +269,7 @@ public class TVector<E> extends TAbstractList<E> implements TList<E>, TRandomAcc
public synchronized int hashCode() {
int result = 1;
for (int i = 0; i < elementCount; i++) {
result = (31 * result) + (elementData[i] == null ? 0 : elementData[i].hashCode());
result = 31 * result + Objects.hashCode(elementData[i]);
}
return result;
}

View File

@ -421,7 +421,7 @@ public class TConcurrentHashMap<K, V> extends TAbstractMap<K, V>
@Override
public V putIfAbsent(K key, V value) {
var hash = computeHashCode(key);
var hash = Objects.hashCode(key);
var entry = getEntry(key, hash);
if (entry != null) {
@ -453,7 +453,7 @@ public class TConcurrentHashMap<K, V> extends TAbstractMap<K, V>
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
Objects.requireNonNull(mappingFunction);
var hash = computeHashCode(key);
var hash = Objects.hashCode(key);
var entry = getEntry(key, hash);
if (entry != null) {
return entry.getValue();
@ -472,7 +472,7 @@ public class TConcurrentHashMap<K, V> extends TAbstractMap<K, V>
@Override
public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
int hash = computeHashCode(key);
int hash = Objects.hashCode(key);
V newValue = null;
var newValueComputed = false;
@ -499,7 +499,7 @@ public class TConcurrentHashMap<K, V> extends TAbstractMap<K, V>
@Override
public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
var hash = computeHashCode(key);
var hash = Objects.hashCode(key);
while (true) {
var entry = getEntry(key, hash);
@ -530,7 +530,7 @@ public class TConcurrentHashMap<K, V> extends TAbstractMap<K, V>
}
private HashEntry<K, V> getEntry(Object key) {
return getEntry(key, computeHashCode(key));
return getEntry(key, Objects.hashCode(key));
}
private HashEntry<K, V> getEntry(Object key, int hash) {
@ -575,7 +575,7 @@ public class TConcurrentHashMap<K, V> extends TAbstractMap<K, V>
}
private HashEntry<K, V> getEntryByKeyAndValue(Object key, Object value) {
var hash = computeHashCode(key);
var hash = Objects.hashCode(key);
repeatTable:
do {
var table = elementData;
@ -667,7 +667,7 @@ public class TConcurrentHashMap<K, V> extends TAbstractMap<K, V>
}
private V putImpl(K key, V value) {
var hash = computeHashCode(key);
var hash = Objects.hashCode(key);
var entry = getEntry(key, hash);
var index = computeIndex(hash);
@ -789,10 +789,6 @@ public class TConcurrentHashMap<K, V> extends TAbstractMap<K, V>
return cachedValues;
}
private static int computeHashCode(Object key) {
return key == null ? 0 : key.hashCode();
}
private static boolean areEqualKeys(Object key1, Object key2) {
return (key1 == key2) || key1.equals(key2);
}

View File

@ -26,9 +26,9 @@ import org.teavm.junit.TeaVMTestRunner;
public class BooleanTest {
@Test
public void parsesBoolean() {
assertEquals(true, new Boolean("TruE"));
assertEquals(false, new Boolean("False"));
assertEquals(false, new Boolean("True15"));
assertEquals(true, Boolean.parseBoolean("TruE"));
assertEquals(false, Boolean.parseBoolean("False"));
assertEquals(false, Boolean.parseBoolean("True15"));
}
@Test
@ -65,4 +65,12 @@ public class BooleanTest {
assertEquals(Boolean.hashCode(true), Boolean.TRUE.hashCode());
assertEquals(Boolean.hashCode(false), Boolean.FALSE.hashCode());
}
@Test
public void compares() {
assertEquals(0, Boolean.compare(false, false));
assertEquals(-1, Boolean.compare(false, true));
assertEquals(1, Boolean.compare(true, false));
assertEquals(0, Boolean.compare(true, true));
}
}

View File

@ -16,10 +16,13 @@
package org.teavm.classlib.java.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.ConcurrentModificationException;
import java.util.LinkedList;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -198,4 +201,11 @@ public class ArrayListTest {
assertEquals(i, list.get(i).intValue());
}
}
@Test
public void hashCodeEquals() {
assertEquals(956197, new ArrayList<>(Arrays.asList(1, 3, null, 2)).hashCode());
assertEquals(new LinkedList<>(Arrays.asList(1, 3, null, 2)), new ArrayList<>(Arrays.asList(1, 3, null, 2)));
assertNotEquals(new ArrayList<>(Arrays.asList(1, 3, 2)), new ArrayList<>(Arrays.asList(1, 3, null, 2)));
}
}

View File

@ -16,11 +16,14 @@
package org.teavm.classlib.java.util;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.teavm.classlib.java.lang.DoubleTest;
import org.teavm.junit.TeaVMTestRunner;
import org.teavm.junit.WholeClassCompilation;
@ -124,4 +127,33 @@ public class ArraysTest {
result = Arrays.stream(array, 0, 2).mapToObj(Double::toString).collect(Collectors.joining(","));
assertEquals("23.0,42.0", result);
}
@Test
public void testHashcode() {
assertEquals(List.of(1, 2, 3).hashCode(), Arrays.hashCode(new int[] { 1, 2, 3 }));
assertEquals(List.of(1L, 2L, 3L).hashCode(), Arrays.hashCode(new long[] { 1L, 2L, 3L }));
assertEquals(List.of(1.0f, 2.0f, 3.0f).hashCode(), Arrays.hashCode(new float[] { 1.0f, 2.0f, 3.0f }));
assertEquals(List.of(1.0, 2.0, 3.0).hashCode(), Arrays.hashCode(new double[] { 1.0, 2.0, 3.0 }));
assertEquals(List.of(false, true, false).hashCode(), Arrays.hashCode(new boolean[] { false, true, false }));
assertEquals(List.of((byte) 1, (byte) 2).hashCode(), Arrays.hashCode(new byte[] { (byte) 1, (byte) 2 }));
assertEquals(List.of((short) 1, (short) 2).hashCode(), Arrays.hashCode(new short[] { (short) 1, (short) 2 }));
assertEquals(List.of('a', 'b', 'c').hashCode(), Arrays.hashCode(new char[] { 'a', 'b', 'c' }));
assertEquals(List.of(List.of('a', 'b'), List.of('c')).hashCode(),
Arrays.deepHashCode(new char[][] { { 'a', 'b' }, { 'c' } }));
List<Object> testList = new ArrayList<>();
List<Optional<Integer>> innerList = new ArrayList<>();
innerList.add(Optional.empty());
innerList.add(null);
innerList.add(Optional.of(5));
testList.add(innerList);
testList.add(null);
testList.add(List.of(Optional.of(3), Optional.empty()));
testList.add(6);
assertEquals(testList.hashCode(), Arrays.deepHashCode(new Object[] {
new Object[] { Optional.empty(), null, Optional.of(5) },
null,
new Object[] { Optional.of(3), Optional.empty() },
6
}));
}
}