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() {
|
||||
}
|
||||
|
||||
@PlatformMarker
|
||||
public static boolean isTeaVM() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@PlatformMarker(Platforms.WEBASSEMBLY)
|
||||
public static boolean isWebAssembly() {
|
||||
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) {
|
||||
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;
|
||||
if (queue !== null && supported) {
|
||||
if (queue !== null && supported && target !== null) {
|
||||
let registry = queue[teavm_javaField("java.lang.ref.ReferenceQueue", "registry")];
|
||||
if (registry !== null) {
|
||||
registry.register(target, this);
|
||||
|
|
|
@ -58,9 +58,13 @@ public class WeakReferenceDependencyListener extends AbstractDependencyListener
|
|||
|
||||
private void queueMethodReached(DependencyAgent agent, MethodDependency method) {
|
||||
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());
|
||||
reportMethod.use();
|
||||
break;
|
||||
}
|
||||
case "<init>":
|
||||
agent.linkField(new FieldReference(ReferenceQueue.class.getName(), "inner"));
|
||||
agent.linkField(new FieldReference(ReferenceQueue.class.getName(), "registry"));
|
||||
|
|
|
@ -15,66 +15,62 @@
|
|||
*/
|
||||
package org.teavm.classlib.java.lang.ref;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import org.junit.Ignore;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.junit.Test;
|
||||
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.SkipJVM;
|
||||
import org.teavm.junit.SkipPlatform;
|
||||
import org.teavm.junit.TeaVMTestRunner;
|
||||
import org.teavm.junit.TestPlatform;
|
||||
|
||||
@RunWith(TeaVMTestRunner.class)
|
||||
@SkipJVM
|
||||
@EachTestCompiledSeparately
|
||||
public class WeakReferenceTest {
|
||||
private Node lastNode;
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void deref() throws InterruptedException {
|
||||
@SkipPlatform({ TestPlatform.JAVASCRIPT, TestPlatform.WEBASSEMBLY, TestPlatform.WASI })
|
||||
public void deref() {
|
||||
var ref = createAndTestRef(null);
|
||||
|
||||
for (var i = 0; i < 100; ++i) {
|
||||
lastNode = createNodes(20);
|
||||
Thread.sleep(1);
|
||||
if (ref.get() == null) {
|
||||
break;
|
||||
}
|
||||
assertNotNull(lastNode);
|
||||
}
|
||||
GCSupport.tryToTriggerGC(ref);
|
||||
assertNull(ref.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void refQueue() throws InterruptedException {
|
||||
@SkipPlatform({ TestPlatform.JAVASCRIPT, TestPlatform.WEBASSEMBLY, TestPlatform.WASI })
|
||||
public void refQueue() {
|
||||
var queue = new ReferenceQueue<>();
|
||||
var ref = createAndTestRef(queue);
|
||||
var hasValue = false;
|
||||
for (var i = 0; i < 100; ++i) {
|
||||
lastNode = createNodes(20);
|
||||
Thread.sleep(1);
|
||||
var polledRef = queue.poll();
|
||||
if (polledRef != null) {
|
||||
hasValue = true;
|
||||
assertNull(ref.get());
|
||||
GCSupport.tryToTriggerGC(ref);
|
||||
int attemptCount = 0;
|
||||
Object value;
|
||||
do {
|
||||
value = queue.poll();
|
||||
if (value != null) {
|
||||
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
|
||||
@Ignore
|
||||
@SkipPlatform({ TestPlatform.C, TestPlatform.WEBASSEMBLY, TestPlatform.WASI })
|
||||
public void queueRemove() throws InterruptedException {
|
||||
var queue = new ReferenceQueue<>();
|
||||
var ref = createAndTestRef(queue);
|
||||
|
@ -88,17 +84,10 @@ public class WeakReferenceTest {
|
|||
});
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
Object value = null;
|
||||
for (var i = 0; i < 100; ++i) {
|
||||
lastNode = createNodes(20);
|
||||
Thread.sleep(1);
|
||||
value = threadQueue.poll();
|
||||
if (value != null) {
|
||||
break;
|
||||
}
|
||||
assertNotNull(lastNode);
|
||||
}
|
||||
assertSame(ref, value);
|
||||
|
||||
GCSupport.tryToTriggerGC(ref);
|
||||
var result = threadQueue.poll(2, TimeUnit.SECONDS);
|
||||
assertSame(ref, result);
|
||||
}
|
||||
|
||||
private WeakReference<Object> createAndTestRef(ReferenceQueue<Object> queue) {
|
||||
|
@ -117,22 +106,4 @@ public class WeakReferenceTest {
|
|||
ref.clear();
|
||||
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",
|
||||
"--remote-debugging-port=9222",
|
||||
"--no-first-run",
|
||||
"--js-flags=--expose-gc",
|
||||
"--user-data-dir=" + profile
|
||||
));
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue
Block a user