mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 08:14:09 -08:00
parent
f668e27daa
commit
e069bc3a44
|
@ -22,6 +22,11 @@ public final class PlatformDetector {
|
||||||
private PlatformDetector() {
|
private PlatformDetector() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PlatformMarker
|
||||||
|
public static boolean isTeaVM() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@PlatformMarker(Platforms.WEBASSEMBLY)
|
@PlatformMarker(Platforms.WEBASSEMBLY)
|
||||||
public static boolean isWebAssembly() {
|
public static boolean isWebAssembly() {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -0,0 +1,596 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Alexey Andreev.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.teavm.classlib.java.util;
|
||||||
|
|
||||||
|
import java.lang.ref.ReferenceQueue;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.ConcurrentModificationException;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class TWeakHashMap<K, V> extends TAbstractMap<K, V> implements TMap<K, V> {
|
||||||
|
private static final int DEFAULT_SIZE = 16;
|
||||||
|
|
||||||
|
private final ReferenceQueue<K> referenceQueue;
|
||||||
|
private int elementCount;
|
||||||
|
private Entry<K, V>[] elementData;
|
||||||
|
private final int loadFactor;
|
||||||
|
private int threshold;
|
||||||
|
private int modCount;
|
||||||
|
|
||||||
|
// Simple utility method to isolate unchecked cast for array creation
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static <K, V> Entry<K, V>[] newEntryArray(int size) {
|
||||||
|
return new Entry[size];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class Entry<K, V> extends WeakReference<K> implements TMap.Entry<K, V> {
|
||||||
|
int hash;
|
||||||
|
boolean isNull;
|
||||||
|
V value;
|
||||||
|
Entry<K, V> next;
|
||||||
|
|
||||||
|
interface Type<R, K, V> {
|
||||||
|
R get(TMap.Entry<K, V> entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
Entry(K key, V object, ReferenceQueue<K> queue) {
|
||||||
|
super(key, queue);
|
||||||
|
isNull = key == null;
|
||||||
|
hash = isNull ? 0 : key.hashCode();
|
||||||
|
value = object;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public K getKey() {
|
||||||
|
return super.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V setValue(V object) {
|
||||||
|
V result = value;
|
||||||
|
value = object;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
if (!(other instanceof Map.Entry)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var entry = (Map.Entry<?, ?>) other;
|
||||||
|
Object key = super.get();
|
||||||
|
return Objects.equals(key, entry.getKey()) && Objects.equals(value, entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return hash ^ Objects.hashCode(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return super.get() + "=" + value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HashIterator<R> implements TIterator<R> {
|
||||||
|
private int position;
|
||||||
|
private int expectedModCount;
|
||||||
|
|
||||||
|
private Entry<K, V> currentEntry;
|
||||||
|
private Entry<K, V> nextEntry;
|
||||||
|
|
||||||
|
private K nextKey;
|
||||||
|
|
||||||
|
final Entry.Type<R, K, V> type;
|
||||||
|
|
||||||
|
HashIterator(Entry.Type<R, K, V> type) {
|
||||||
|
this.type = type;
|
||||||
|
expectedModCount = modCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
if (nextEntry != null && (nextKey != null || nextEntry.isNull)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
while (true) {
|
||||||
|
if (nextEntry == null) {
|
||||||
|
while (position < elementData.length) {
|
||||||
|
nextEntry = elementData[position++];
|
||||||
|
if (nextEntry != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nextEntry == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ensure key of next entry is not gc'ed
|
||||||
|
nextKey = nextEntry.get();
|
||||||
|
if (nextKey != null || nextEntry.isNull) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
nextEntry = nextEntry.next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public R next() {
|
||||||
|
if (expectedModCount == modCount) {
|
||||||
|
if (hasNext()) {
|
||||||
|
currentEntry = nextEntry;
|
||||||
|
nextEntry = currentEntry.next;
|
||||||
|
R result = type.get(currentEntry);
|
||||||
|
// free the key
|
||||||
|
nextKey = null;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
throw new ConcurrentModificationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove() {
|
||||||
|
if (expectedModCount == modCount) {
|
||||||
|
if (currentEntry != null) {
|
||||||
|
removeEntry(currentEntry);
|
||||||
|
currentEntry = null;
|
||||||
|
expectedModCount++;
|
||||||
|
// cannot poll() as that would change the expectedModCount
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new ConcurrentModificationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TWeakHashMap() {
|
||||||
|
this(DEFAULT_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TWeakHashMap(int capacity) {
|
||||||
|
if (capacity >= 0) {
|
||||||
|
elementCount = 0;
|
||||||
|
elementData = newEntryArray(capacity == 0 ? 1 : capacity);
|
||||||
|
loadFactor = 7500; // Default load factor of 0.75
|
||||||
|
computeMaxSize();
|
||||||
|
referenceQueue = new ReferenceQueue<>();
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TWeakHashMap(int capacity, float loadFactor) {
|
||||||
|
if (capacity >= 0 && loadFactor > 0) {
|
||||||
|
elementCount = 0;
|
||||||
|
elementData = newEntryArray(capacity == 0 ? 1 : capacity);
|
||||||
|
this.loadFactor = (int) (loadFactor * 10000);
|
||||||
|
computeMaxSize();
|
||||||
|
referenceQueue = new ReferenceQueue<>();
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TWeakHashMap(TMap<? extends K, ? extends V> map) {
|
||||||
|
this(map.size() < 6 ? 11 : map.size() * 2);
|
||||||
|
putAllImpl(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
if (elementCount > 0) {
|
||||||
|
elementCount = 0;
|
||||||
|
Arrays.fill(elementData, null);
|
||||||
|
modCount++;
|
||||||
|
while (referenceQueue.poll() != null) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void computeMaxSize() {
|
||||||
|
threshold = (int) ((long) elementData.length * loadFactor / 10000);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsKey(Object key) {
|
||||||
|
return getEntry(key) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TSet<TMap.Entry<K, V>> entrySet() {
|
||||||
|
poll();
|
||||||
|
return new TAbstractSet<>() {
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return TWeakHashMap.this.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
TWeakHashMap.this.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean remove(Object object) {
|
||||||
|
if (contains(object)) {
|
||||||
|
TWeakHashMap.this.remove(((Map.Entry<?, ?>) object).getKey());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(Object object) {
|
||||||
|
if (object instanceof TMap.Entry) {
|
||||||
|
var entry = getEntry(((TMap.Entry<?, ?>) object).getKey());
|
||||||
|
if (entry != null) {
|
||||||
|
Object key = entry.get();
|
||||||
|
if (key != null || entry.isNull) {
|
||||||
|
return object.equals(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TIterator<TMap.Entry<K, V>> iterator() {
|
||||||
|
return new HashIterator<>(entry -> entry);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TSet<K> keySet() {
|
||||||
|
poll();
|
||||||
|
if (cachedKeySet == null) {
|
||||||
|
cachedKeySet = new TAbstractSet<K>() {
|
||||||
|
@Override
|
||||||
|
public boolean contains(Object object) {
|
||||||
|
return containsKey(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return TWeakHashMap.this.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
TWeakHashMap.this.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean remove(Object key) {
|
||||||
|
if (containsKey(key)) {
|
||||||
|
TWeakHashMap.this.remove(key);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TIterator<K> iterator() {
|
||||||
|
return new HashIterator<>(TMap.Entry::getKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object[] toArray() {
|
||||||
|
var coll = new TArrayList<K>(size());
|
||||||
|
|
||||||
|
for (var iter = iterator(); iter.hasNext();) {
|
||||||
|
coll.add(iter.next());
|
||||||
|
}
|
||||||
|
return coll.toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T[] toArray(T[] contents) {
|
||||||
|
var coll = new ArrayList<K>(size());
|
||||||
|
|
||||||
|
for (var iter = iterator(); iter.hasNext();) {
|
||||||
|
coll.add(iter.next());
|
||||||
|
}
|
||||||
|
return coll.toArray(contents);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return cachedKeySet;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TCollection<V> values() {
|
||||||
|
poll();
|
||||||
|
if (cachedValues == null) {
|
||||||
|
cachedValues = new TAbstractCollection<V>() {
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return TWeakHashMap.this.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
TWeakHashMap.this.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(Object object) {
|
||||||
|
return containsValue(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TIterator<V> iterator() {
|
||||||
|
return new HashIterator<>(TMap.Entry::getValue);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return cachedValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V get(Object key) {
|
||||||
|
poll();
|
||||||
|
if (key != null) {
|
||||||
|
int index = (key.hashCode() & 0x7FFFFFFF) % elementData.length;
|
||||||
|
var entry = elementData[index];
|
||||||
|
while (entry != null) {
|
||||||
|
if (key.equals(entry.get())) {
|
||||||
|
return entry.value;
|
||||||
|
}
|
||||||
|
entry = entry.next;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var entry = elementData[0];
|
||||||
|
while (entry != null) {
|
||||||
|
if (entry.isNull) {
|
||||||
|
return entry.value;
|
||||||
|
}
|
||||||
|
entry = entry.next;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Entry<K, V> getEntry(Object key) {
|
||||||
|
poll();
|
||||||
|
if (key != null) {
|
||||||
|
int index = (key.hashCode() & 0x7FFFFFFF) % elementData.length;
|
||||||
|
Entry<K, V> entry = elementData[index];
|
||||||
|
while (entry != null) {
|
||||||
|
if (key.equals(entry.get())) {
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
entry = entry.next;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Entry<K, V> entry = elementData[0];
|
||||||
|
while (entry != null) {
|
||||||
|
if (entry.isNull) {
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
entry = entry.next;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsValue(Object value) {
|
||||||
|
poll();
|
||||||
|
if (value != null) {
|
||||||
|
for (int i = elementData.length; --i >= 0;) {
|
||||||
|
var entry = elementData[i];
|
||||||
|
while (entry != null) {
|
||||||
|
K key = entry.get();
|
||||||
|
if ((key != null || entry.isNull) && value.equals(entry.value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
entry = entry.next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int i = elementData.length; --i >= 0;) {
|
||||||
|
var entry = elementData[i];
|
||||||
|
while (entry != null) {
|
||||||
|
K key = entry.get();
|
||||||
|
if ((key != null || entry.isNull) && entry.value == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
entry = entry.next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return size() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private void poll() {
|
||||||
|
Entry<K, V> toRemove;
|
||||||
|
while ((toRemove = (Entry<K, V>) referenceQueue.poll()) != null) {
|
||||||
|
removeEntry(toRemove);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeEntry(Entry<K, V> toRemove) {
|
||||||
|
Entry<K, V> entry;
|
||||||
|
Entry<K, V> last = null;
|
||||||
|
int index = (toRemove.hash & 0x7FFFFFFF) % elementData.length;
|
||||||
|
entry = elementData[index];
|
||||||
|
// Ignore queued entries which cannot be found, the user could
|
||||||
|
// have removed them before they were queued, i.e. using clear()
|
||||||
|
while (entry != null) {
|
||||||
|
if (toRemove == entry) {
|
||||||
|
modCount++;
|
||||||
|
if (last == null) {
|
||||||
|
elementData[index] = entry.next;
|
||||||
|
} else {
|
||||||
|
last.next = entry.next;
|
||||||
|
}
|
||||||
|
elementCount--;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
last = entry;
|
||||||
|
entry = entry.next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V put(K key, V value) {
|
||||||
|
poll();
|
||||||
|
int index = 0;
|
||||||
|
Entry<K, V> entry;
|
||||||
|
if (key != null) {
|
||||||
|
index = (key.hashCode() & 0x7FFFFFFF) % elementData.length;
|
||||||
|
entry = elementData[index];
|
||||||
|
while (entry != null && !key.equals(entry.get())) {
|
||||||
|
entry = entry.next;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
entry = elementData[0];
|
||||||
|
while (entry != null && !entry.isNull) {
|
||||||
|
entry = entry.next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (entry == null) {
|
||||||
|
modCount++;
|
||||||
|
if (++elementCount > threshold) {
|
||||||
|
rehash();
|
||||||
|
index = key == null ? 0 : (key.hashCode() & 0x7FFFFFFF) % elementData.length;
|
||||||
|
}
|
||||||
|
entry = new Entry<>(key, value, referenceQueue);
|
||||||
|
entry.next = elementData[index];
|
||||||
|
elementData[index] = entry;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
V result = entry.value;
|
||||||
|
entry.value = value;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rehash() {
|
||||||
|
int length = elementData.length << 1;
|
||||||
|
if (length == 0) {
|
||||||
|
length = 1;
|
||||||
|
}
|
||||||
|
Entry<K, V>[] newData = newEntryArray(length);
|
||||||
|
for (var elementDatum : elementData) {
|
||||||
|
var entry = elementDatum;
|
||||||
|
while (entry != null) {
|
||||||
|
int index = entry.isNull ? 0 : (entry.hash & 0x7FFFFFFF) % length;
|
||||||
|
var next = entry.next;
|
||||||
|
entry.next = newData[index];
|
||||||
|
newData[index] = entry;
|
||||||
|
entry = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elementData = newData;
|
||||||
|
computeMaxSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void putAll(TMap<? extends K, ? extends V> map) {
|
||||||
|
putAllImpl(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the mapping with the specified key from this map.
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* the key of the mapping to remove.
|
||||||
|
* @return the value of the removed mapping or {@code null} if no mapping
|
||||||
|
* for the specified key was found.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public V remove(Object key) {
|
||||||
|
poll();
|
||||||
|
int index = 0;
|
||||||
|
Entry<K, V> entry;
|
||||||
|
Entry<K, V> last = null;
|
||||||
|
if (key != null) {
|
||||||
|
index = (key.hashCode() & 0x7FFFFFFF) % elementData.length;
|
||||||
|
entry = elementData[index];
|
||||||
|
while (entry != null && !key.equals(entry.get())) {
|
||||||
|
last = entry;
|
||||||
|
entry = entry.next;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
entry = elementData[0];
|
||||||
|
while (entry != null && !entry.isNull) {
|
||||||
|
last = entry;
|
||||||
|
entry = entry.next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (entry != null) {
|
||||||
|
modCount++;
|
||||||
|
if (last == null) {
|
||||||
|
elementData[index] = entry.next;
|
||||||
|
} else {
|
||||||
|
last.next = entry.next;
|
||||||
|
}
|
||||||
|
elementCount--;
|
||||||
|
return entry.value;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
poll();
|
||||||
|
return elementCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void putAllImpl(TMap<? extends K, ? extends V> map) {
|
||||||
|
if (map.entrySet() != null) {
|
||||||
|
super.putAll(map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,9 +16,9 @@
|
||||||
|
|
||||||
function init(target, queue) {
|
function init(target, queue) {
|
||||||
let supported = typeof teavm_globals.WeakRef !== 'undefined';
|
let supported = typeof teavm_globals.WeakRef !== 'undefined';
|
||||||
let value = supported ? new teavm_globals.WeakRef(target) : target;
|
let value = supported && target !== null ? new teavm_globals.WeakRef(target) : target;
|
||||||
this[teavm_javaField("java.lang.ref.WeakReference", "value")] = value;
|
this[teavm_javaField("java.lang.ref.WeakReference", "value")] = value;
|
||||||
if (queue !== null && supported) {
|
if (queue !== null && supported && target !== null) {
|
||||||
let registry = queue[teavm_javaField("java.lang.ref.ReferenceQueue", "registry")];
|
let registry = queue[teavm_javaField("java.lang.ref.ReferenceQueue", "registry")];
|
||||||
if (registry !== null) {
|
if (registry !== null) {
|
||||||
registry.register(target, this);
|
registry.register(target, this);
|
||||||
|
|
|
@ -58,9 +58,13 @@ public class WeakReferenceDependencyListener extends AbstractDependencyListener
|
||||||
|
|
||||||
private void queueMethodReached(DependencyAgent agent, MethodDependency method) {
|
private void queueMethodReached(DependencyAgent agent, MethodDependency method) {
|
||||||
switch (method.getMethod().getName()) {
|
switch (method.getMethod().getName()) {
|
||||||
case "poll":
|
case "poll": {
|
||||||
|
var reportMethod = agent.linkMethod(new MethodReference(ReferenceQueue.class,
|
||||||
|
"reportNext", Reference.class, boolean.class));
|
||||||
initRef.connect(method.getResult());
|
initRef.connect(method.getResult());
|
||||||
|
reportMethod.use();
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case "<init>":
|
case "<init>":
|
||||||
agent.linkField(new FieldReference(ReferenceQueue.class.getName(), "inner"));
|
agent.linkField(new FieldReference(ReferenceQueue.class.getName(), "inner"));
|
||||||
agent.linkField(new FieldReference(ReferenceQueue.class.getName(), "registry"));
|
agent.linkField(new FieldReference(ReferenceQueue.class.getName(), "registry"));
|
||||||
|
|
|
@ -15,66 +15,62 @@
|
||||||
*/
|
*/
|
||||||
package org.teavm.classlib.java.lang.ref;
|
package org.teavm.classlib.java.lang.ref;
|
||||||
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertSame;
|
import static org.junit.Assert.assertSame;
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
import java.lang.ref.ReferenceQueue;
|
import java.lang.ref.ReferenceQueue;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.concurrent.ArrayBlockingQueue;
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
import org.junit.Ignore;
|
import java.util.concurrent.TimeUnit;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.teavm.classlib.PlatformDetector;
|
||||||
|
import org.teavm.classlib.support.GCSupport;
|
||||||
import org.teavm.junit.EachTestCompiledSeparately;
|
import org.teavm.junit.EachTestCompiledSeparately;
|
||||||
import org.teavm.junit.SkipJVM;
|
import org.teavm.junit.SkipPlatform;
|
||||||
import org.teavm.junit.TeaVMTestRunner;
|
import org.teavm.junit.TeaVMTestRunner;
|
||||||
|
import org.teavm.junit.TestPlatform;
|
||||||
|
|
||||||
@RunWith(TeaVMTestRunner.class)
|
@RunWith(TeaVMTestRunner.class)
|
||||||
@SkipJVM
|
|
||||||
@EachTestCompiledSeparately
|
@EachTestCompiledSeparately
|
||||||
public class WeakReferenceTest {
|
public class WeakReferenceTest {
|
||||||
private Node lastNode;
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore
|
@SkipPlatform({ TestPlatform.JAVASCRIPT, TestPlatform.WEBASSEMBLY, TestPlatform.WASI })
|
||||||
public void deref() throws InterruptedException {
|
public void deref() {
|
||||||
var ref = createAndTestRef(null);
|
var ref = createAndTestRef(null);
|
||||||
|
GCSupport.tryToTriggerGC(ref);
|
||||||
for (var i = 0; i < 100; ++i) {
|
|
||||||
lastNode = createNodes(20);
|
|
||||||
Thread.sleep(1);
|
|
||||||
if (ref.get() == null) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
assertNotNull(lastNode);
|
|
||||||
}
|
|
||||||
assertNull(ref.get());
|
assertNull(ref.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore
|
@SkipPlatform({ TestPlatform.JAVASCRIPT, TestPlatform.WEBASSEMBLY, TestPlatform.WASI })
|
||||||
public void refQueue() throws InterruptedException {
|
public void refQueue() {
|
||||||
var queue = new ReferenceQueue<>();
|
var queue = new ReferenceQueue<>();
|
||||||
var ref = createAndTestRef(queue);
|
var ref = createAndTestRef(queue);
|
||||||
var hasValue = false;
|
GCSupport.tryToTriggerGC(ref);
|
||||||
for (var i = 0; i < 100; ++i) {
|
int attemptCount = 0;
|
||||||
lastNode = createNodes(20);
|
Object value;
|
||||||
Thread.sleep(1);
|
do {
|
||||||
var polledRef = queue.poll();
|
value = queue.poll();
|
||||||
if (polledRef != null) {
|
if (value != null) {
|
||||||
hasValue = true;
|
|
||||||
assertNull(ref.get());
|
|
||||||
break;
|
break;
|
||||||
} else {
|
|
||||||
assertNotNull(ref.get());
|
|
||||||
}
|
}
|
||||||
assertNotNull(lastNode);
|
waitInJVM();
|
||||||
|
} while (attemptCount++ < 50);
|
||||||
|
assertSame(ref, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void waitInJVM() {
|
||||||
|
if (!PlatformDetector.isTeaVM()) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(10);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
assertTrue(hasValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore
|
@SkipPlatform({ TestPlatform.C, TestPlatform.WEBASSEMBLY, TestPlatform.WASI })
|
||||||
public void queueRemove() throws InterruptedException {
|
public void queueRemove() throws InterruptedException {
|
||||||
var queue = new ReferenceQueue<>();
|
var queue = new ReferenceQueue<>();
|
||||||
var ref = createAndTestRef(queue);
|
var ref = createAndTestRef(queue);
|
||||||
|
@ -88,17 +84,10 @@ public class WeakReferenceTest {
|
||||||
});
|
});
|
||||||
thread.setDaemon(true);
|
thread.setDaemon(true);
|
||||||
thread.start();
|
thread.start();
|
||||||
Object value = null;
|
|
||||||
for (var i = 0; i < 100; ++i) {
|
GCSupport.tryToTriggerGC(ref);
|
||||||
lastNode = createNodes(20);
|
var result = threadQueue.poll(2, TimeUnit.SECONDS);
|
||||||
Thread.sleep(1);
|
assertSame(ref, result);
|
||||||
value = threadQueue.poll();
|
|
||||||
if (value != null) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
assertNotNull(lastNode);
|
|
||||||
}
|
|
||||||
assertSame(ref, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private WeakReference<Object> createAndTestRef(ReferenceQueue<Object> queue) {
|
private WeakReference<Object> createAndTestRef(ReferenceQueue<Object> queue) {
|
||||||
|
@ -117,22 +106,4 @@ public class WeakReferenceTest {
|
||||||
ref.clear();
|
ref.clear();
|
||||||
assertNull(ref.get());
|
assertNull(ref.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Node createNodes(int depth) {
|
|
||||||
if (depth == 0) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return new Node(createNodes(depth - 1), createNodes(depth - 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Node {
|
|
||||||
Node left;
|
|
||||||
Node right;
|
|
||||||
|
|
||||||
Node(Node left, Node right) {
|
|
||||||
this.left = left;
|
|
||||||
this.right = right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,385 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Alexey Andreev.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.teavm.classlib.java.util;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import java.util.AbstractMap;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.teavm.classlib.support.GCSupport;
|
||||||
|
import org.teavm.classlib.support.MapTest2Support;
|
||||||
|
import org.teavm.junit.SkipPlatform;
|
||||||
|
import org.teavm.junit.TeaVMTestRunner;
|
||||||
|
import org.teavm.junit.TestPlatform;
|
||||||
|
|
||||||
|
@RunWith(TeaVMTestRunner.class)
|
||||||
|
@SkipPlatform({TestPlatform.WEBASSEMBLY, TestPlatform.WASI})
|
||||||
|
public class WeakHashMapTest {
|
||||||
|
static class MockMap<K, V> extends AbstractMap<K, V> {
|
||||||
|
@Override
|
||||||
|
public Set<Entry<K, V>> entrySet() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object[] keyArray = new Object[100];
|
||||||
|
Object[] valueArray = new Object[100];
|
||||||
|
WeakHashMap<Object, Object> whm;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructor() {
|
||||||
|
new MapTest2Support(new WeakHashMap<>()).runTest();
|
||||||
|
|
||||||
|
whm = new WeakHashMap<>();
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
whm.put(keyArray[i], valueArray[i]);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
assertSame("Incorrect value retrieved", valueArray[i], whm.get(keyArray[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorI() {
|
||||||
|
whm = new WeakHashMap<>(50);
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
whm.put(keyArray[i], valueArray[i]);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
assertSame("Incorrect value retrieved", valueArray[i], whm.get(keyArray[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
var empty = new WeakHashMap<>(0);
|
||||||
|
assertNull("Empty weakhashmap access", empty.get("nothing"));
|
||||||
|
empty.put("something", "here");
|
||||||
|
assertSame("cannot get element", "here", empty.get("something"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorIF() {
|
||||||
|
whm = new WeakHashMap<>(50, 0.5f);
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
whm.put(keyArray[i], valueArray[i]);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
assertSame("Incorrect value retrieved", valueArray[i], whm.get(keyArray[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
var empty = new WeakHashMap<>(0, 0.75f);
|
||||||
|
assertNull("Empty hashtable access", empty.get("nothing"));
|
||||||
|
empty.put("something", "here");
|
||||||
|
assertSame("cannot get element", "here", empty.get("something"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorLjava_util_Map() {
|
||||||
|
var map = new WeakHashMap<>(new MockMap<>());
|
||||||
|
assertEquals("Size should be 0", 0, map.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void clearMethod() {
|
||||||
|
whm = new WeakHashMap<>();
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
whm.put(keyArray[i], valueArray[i]);
|
||||||
|
}
|
||||||
|
whm.clear();
|
||||||
|
assertTrue("Cleared map should be empty", whm.isEmpty());
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
assertNull("Cleared map should only return null", whm.get(keyArray[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void containsKey() {
|
||||||
|
whm = new WeakHashMap<>();
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
whm.put(keyArray[i], valueArray[i]);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
assertTrue("Should contain referenced key", whm.containsKey(keyArray[i]));
|
||||||
|
}
|
||||||
|
keyArray[25] = null;
|
||||||
|
keyArray[50] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void containsValue() {
|
||||||
|
whm = new WeakHashMap<>();
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
whm.put(keyArray[i], valueArray[i]);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
assertTrue("Should contain referenced value", whm.containsValue(valueArray[i]));
|
||||||
|
}
|
||||||
|
keyArray[25] = null;
|
||||||
|
keyArray[50] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void entrySet() {
|
||||||
|
var weakMap = new WeakHashMap<>();
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
weakMap.put(keyArray[i], valueArray[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var keys = Arrays.asList(keyArray);
|
||||||
|
var values = Arrays.asList(valueArray);
|
||||||
|
|
||||||
|
// Check the entry set has correct size & content
|
||||||
|
var entrySet = weakMap.entrySet();
|
||||||
|
assertEquals("Assert 0: Incorrect number of entries returned", 100, entrySet.size());
|
||||||
|
var it = entrySet.iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
var entry = it.next();
|
||||||
|
assertTrue("Assert 1: Invalid map entry key returned", keys.contains(entry.getKey()));
|
||||||
|
assertTrue("Assert 2: Invalid map entry value returned", values.contains(entry.getValue()));
|
||||||
|
assertTrue("Assert 3: Entry not in entry set", entrySet.contains(entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dereference a single key, then try to
|
||||||
|
// force a collection of the weak ref'd obj
|
||||||
|
keyArray[50] = null;
|
||||||
|
GCSupport.tryToTriggerGC();
|
||||||
|
|
||||||
|
assertEquals("Assert 4: Incorrect number of entries after gc", 99, entrySet.size());
|
||||||
|
assertSame("Assert 5: Entries not identical", entrySet.iterator().next(), entrySet.iterator().next());
|
||||||
|
|
||||||
|
// remove alternate entries using the iterator, and ensure the
|
||||||
|
// iteration count is consistent
|
||||||
|
int size = entrySet.size();
|
||||||
|
it = entrySet.iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
it.next();
|
||||||
|
it.remove();
|
||||||
|
size--;
|
||||||
|
if (it.hasNext()) {
|
||||||
|
it.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
assertEquals("Assert 6: entry set count mismatch", size, entrySet.size());
|
||||||
|
|
||||||
|
int entries = 0;
|
||||||
|
it = entrySet.iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
it.next();
|
||||||
|
entries++;
|
||||||
|
}
|
||||||
|
assertEquals("Assert 6: count mismatch", size, entries);
|
||||||
|
|
||||||
|
it = entrySet.iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
it.next();
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
assertEquals("Assert 7: entry set not empty", 0, entrySet.size());
|
||||||
|
assertFalse("Assert 8: iterator not empty", entrySet.iterator().hasNext());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void entrySet2() {
|
||||||
|
whm = new WeakHashMap<>();
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
whm.put(keyArray[i], valueArray[i]);
|
||||||
|
}
|
||||||
|
var keys = Arrays.asList(keyArray);
|
||||||
|
var values = Arrays.asList(valueArray);
|
||||||
|
var entrySet = whm.entrySet();
|
||||||
|
assertEquals("Incorrect number of entries returned--wanted 100, got: " + entrySet.size(),
|
||||||
|
100, entrySet.size());
|
||||||
|
for (var entry : entrySet) {
|
||||||
|
assertTrue("Invalid map entry returned--bad key", keys.contains(entry.getKey()));
|
||||||
|
assertTrue("Invalid map entry returned--bad key", values.contains(entry.getValue()));
|
||||||
|
}
|
||||||
|
keys = null;
|
||||||
|
values = null;
|
||||||
|
keyArray[50] = null;
|
||||||
|
|
||||||
|
GCSupport.tryToTriggerGC();
|
||||||
|
|
||||||
|
assertEquals("Incorrect number of entries returned after gc--wanted 99, got: " + entrySet.size(),
|
||||||
|
99, entrySet.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void get() {
|
||||||
|
assertTrue("Used to test", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isEmpty() {
|
||||||
|
whm = new WeakHashMap<>();
|
||||||
|
assertTrue("New map should be empty", whm.isEmpty());
|
||||||
|
Object myObject = new Object();
|
||||||
|
whm.put(myObject, myObject);
|
||||||
|
assertFalse("Map should not be empty", whm.isEmpty());
|
||||||
|
whm.remove(myObject);
|
||||||
|
assertTrue("Map with elements removed should be empty", whm.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void put() {
|
||||||
|
var map = new WeakHashMap<>();
|
||||||
|
map.put(null, "value"); // add null key
|
||||||
|
GCSupport.tryToTriggerGC();
|
||||||
|
map.remove("nothing"); // Cause objects in queue to be removed
|
||||||
|
assertEquals("null key was removed", 1, map.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void putAll() {
|
||||||
|
var mockMap = new MockMap<>();
|
||||||
|
var map = new WeakHashMap<>();
|
||||||
|
map.putAll(mockMap);
|
||||||
|
assertEquals("Size should be 0", 0, map.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void remove() {
|
||||||
|
whm = new WeakHashMap<>();
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
whm.put(keyArray[i], valueArray[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertSame("Remove returned incorrect value", valueArray[25], whm.remove(keyArray[25]));
|
||||||
|
assertNull("Remove returned incorrect value", whm.remove(keyArray[25]));
|
||||||
|
assertEquals("Size should be 99 after remove", 99, whm.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void size() {
|
||||||
|
assertTrue("Used to test", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void keySet() {
|
||||||
|
whm = new WeakHashMap<>();
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
whm.put(keyArray[i], valueArray[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var keys = Arrays.asList(keyArray);
|
||||||
|
var values = Arrays.asList(valueArray);
|
||||||
|
|
||||||
|
var keySet = whm.keySet();
|
||||||
|
assertEquals("Incorrect number of keys returned,", 100, keySet.size());
|
||||||
|
for (var key : keySet) {
|
||||||
|
assertTrue("Invalid map entry returned--bad key", keys.contains(key));
|
||||||
|
}
|
||||||
|
keys = null;
|
||||||
|
values = null;
|
||||||
|
keyArray[50] = null;
|
||||||
|
|
||||||
|
GCSupport.tryToTriggerGC();
|
||||||
|
|
||||||
|
assertEquals("Incorrect number of keys returned after gc,", 99, keySet.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void keySetHasNext() {
|
||||||
|
var map = new WeakHashMap<>();
|
||||||
|
var cl = new ConstantHashClass(2);
|
||||||
|
map.put(new ConstantHashClass(1), null);
|
||||||
|
map.put(cl, null);
|
||||||
|
map.put(new ConstantHashClass(3), null);
|
||||||
|
var iter = map.keySet().iterator();
|
||||||
|
iter.next();
|
||||||
|
iter.next();
|
||||||
|
GCSupport.tryToTriggerGC();
|
||||||
|
assertFalse("Wrong hasNext() value", iter.hasNext());
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ConstantHashClass {
|
||||||
|
private int id;
|
||||||
|
|
||||||
|
public ConstantHashClass(int id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hashCode() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
return "ConstantHashClass[id=" + id + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void values() {
|
||||||
|
whm = new WeakHashMap<>();
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
whm.put(keyArray[i], valueArray[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var keys = Arrays.asList(keyArray);
|
||||||
|
var values = Arrays.asList(valueArray);
|
||||||
|
|
||||||
|
var valuesCollection = whm.values();
|
||||||
|
assertEquals("Incorrect number of keys returned,", 100, valuesCollection.size());
|
||||||
|
for (Object value : valuesCollection) {
|
||||||
|
assertTrue("Invalid map entry returned--bad value", values.contains(value));
|
||||||
|
}
|
||||||
|
keys = null;
|
||||||
|
values = null;
|
||||||
|
keyArray[50] = null;
|
||||||
|
|
||||||
|
GCSupport.tryToTriggerGC();
|
||||||
|
|
||||||
|
assertEquals("Incorrect number of keys returned after gc", 99, valuesCollection.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
keyArray[i] = new Object();
|
||||||
|
valueArray[i] = new Object();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 konsoletyper.
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.teavm.classlib.support;
|
||||||
|
|
||||||
|
import java.lang.ref.Reference;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import org.teavm.classlib.PlatformDetector;
|
||||||
|
import org.teavm.interop.Async;
|
||||||
|
import org.teavm.interop.AsyncCallback;
|
||||||
|
import org.teavm.jso.JSBody;
|
||||||
|
import org.teavm.jso.browser.Window;
|
||||||
|
import org.teavm.jso.dom.html.HTMLDocument;
|
||||||
|
|
||||||
|
public final class GCSupport {
|
||||||
|
private GCSupport() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void tryToTriggerGC() {
|
||||||
|
tryToTriggerGC(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void tryToTriggerGC(Reference<?> ref) {
|
||||||
|
if (PlatformDetector.isC() || PlatformDetector.isWebAssembly()) {
|
||||||
|
System.gc();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var weakReferences = new ArrayList<WeakReference<Object>>();
|
||||||
|
for (var i = 0; i < 100; ++i) {
|
||||||
|
System.out.println("GC trigger attempt " + i);
|
||||||
|
weakReferences.add(new WeakReference<>(generateTree("R")));
|
||||||
|
waitInJS();
|
||||||
|
if (weakReferences.stream().anyMatch(s -> s.get() == null)) {
|
||||||
|
if (ref != null) {
|
||||||
|
if (ref.get() == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (i > 5) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void waitInJS() {
|
||||||
|
if (PlatformDetector.isJavaScript()) {
|
||||||
|
var doc = HTMLDocument.current();
|
||||||
|
var div = doc.createElement("div");
|
||||||
|
div.appendChild(doc.createTextNode("hello"));
|
||||||
|
doc.getBody().appendChild(div);
|
||||||
|
triggerGCInJS();
|
||||||
|
waitImpl();
|
||||||
|
triggerGCInJS();
|
||||||
|
waitImpl();
|
||||||
|
} else {
|
||||||
|
Runtime.getRuntime().gc();
|
||||||
|
Runtime.getRuntime().gc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Async
|
||||||
|
private static native void waitImpl();
|
||||||
|
private static void waitImpl(AsyncCallback<Void> callback) {
|
||||||
|
Window.setTimeout(() -> callback.complete(null), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@JSBody(script = "if (typeof window.gc === 'function') { window.gc(); }")
|
||||||
|
private static native void triggerGCInJS();
|
||||||
|
|
||||||
|
private static Tree generateTree(String path) {
|
||||||
|
var result = new Tree();
|
||||||
|
result.s = path;
|
||||||
|
if (path.length() < 18) {
|
||||||
|
result.a = generateTree(path + "l");
|
||||||
|
result.b = generateTree(path + "r");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Tree {
|
||||||
|
String s;
|
||||||
|
Tree a;
|
||||||
|
Tree b;
|
||||||
|
}
|
||||||
|
}
|
|
@ -420,6 +420,7 @@ public class BrowserRunner {
|
||||||
"--disable-gpu",
|
"--disable-gpu",
|
||||||
"--remote-debugging-port=9222",
|
"--remote-debugging-port=9222",
|
||||||
"--no-first-run",
|
"--no-first-run",
|
||||||
|
"--js-flags=--expose-gc",
|
||||||
"--user-data-dir=" + profile
|
"--user-data-dir=" + profile
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue
Block a user