diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TAbstractMap.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TAbstractMap.java index 8e422840c..c56985038 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TAbstractMap.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TAbstractMap.java @@ -128,8 +128,8 @@ public abstract class TAbstractMap extends TObject implements TMap { } } - private KeySet cachedKeySet; - private Values cachedValues; + TSet cachedKeySet; + TCollection cachedValues; protected TAbstractMap() { } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TCollections.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TCollections.java index ef02b031c..308115072 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TCollections.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TCollections.java @@ -93,6 +93,46 @@ public class TCollections extends TObject { }; } + public static TSet singleton(final T o) { + return new TAbstractSet() { + @Override public int size() { + return 1; + } + @Override public TIterator iterator() { + return new TIterator() { + private boolean read; + @Override public boolean hasNext() { + return !read; + } + @Override public T next() { + if (read) { + throw new TNoSuchElementException(); + } + read = true; + return o; + } + @Override public void remove() { + throw new TUnsupportedOperationException(); + } + }; + } + @Override public boolean contains(Object o2) { + return TObjects.equals(o, o2); + } + }; + } + + public static TList unmodifiableList(final TList list) { + return new TAbstractList() { + @Override public T get(int index) { + return list.get(index); + } + @Override public int size() { + return list.size(); + } + }; + } + public static TList nCopies(final int n, final T o) { return new TAbstractList() { @Override public T get(int index) { diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/THashMap.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/THashMap.java new file mode 100644 index 000000000..0346163ea --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/THashMap.java @@ -0,0 +1,602 @@ +/* + * 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.util.*; +import org.teavm.classlib.java.io.TSerializable; +import org.teavm.classlib.java.lang.TCloneNotSupportedException; +import org.teavm.classlib.java.lang.TIllegalArgumentException; +import org.teavm.classlib.java.lang.TIllegalStateException; +import org.teavm.classlib.java.lang.TObject; + +public class THashMap extends TAbstractMap implements TSerializable { + transient int elementCount; + transient Entry[] elementData; + transient int modCount = 0; + private static final int DEFAULT_SIZE = 16; + final float loadFactor; + int threshold; + + static class Entry extends TMapEntry { + final int origKeyHash; + + Entry next; + + Entry(K theKey, int hash) { + super(theKey, null); + this.origKeyHash = hash; + } + + Entry(K theKey, V theValue) { + super(theKey, theValue); + origKeyHash = (theKey == null ? 0 : computeHashCode(theKey)); + } + + @Override + @SuppressWarnings("unchecked") + public Object clone() { + Entry entry = (Entry) super.clone(); + if (next != null) { + entry.next = (Entry) next.clone(); + } + return entry; + } + } + + private static class AbstractMapIterator { + private int position = 0; + int expectedModCount; + Entry futureEntry; + Entry currentEntry; + Entry prevEntry; + + final THashMap associatedMap; + + AbstractMapIterator(THashMap hm) { + associatedMap = hm; + expectedModCount = hm.modCount; + futureEntry = null; + } + + public boolean hasNext() { + if (futureEntry != null) { + return true; + } + while (position < associatedMap.elementData.length) { + if (associatedMap.elementData[position] == null) { + position++; + } else { + return true; + } + } + return false; + } + + final void checkConcurrentMod() throws ConcurrentModificationException { + if (expectedModCount != associatedMap.modCount) { + throw new TConcurrentModificationException(); + } + } + + final void makeNext() { + checkConcurrentMod(); + if (!hasNext()) { + throw new TNoSuchElementException(); + } + if (futureEntry == null) { + currentEntry = associatedMap.elementData[position++]; + futureEntry = currentEntry.next; + prevEntry = null; + } else { + if(currentEntry!=null){ + prevEntry = currentEntry; + } + currentEntry = futureEntry; + futureEntry = futureEntry.next; + } + } + + public final void remove() { + checkConcurrentMod(); + if (currentEntry == null) { + throw new TIllegalStateException(); + } + if(prevEntry == null){ + int index = currentEntry.origKeyHash & (associatedMap.elementData.length - 1); + associatedMap.elementData[index] = associatedMap.elementData[index].next; + } else { + prevEntry.next = currentEntry.next; + } + currentEntry = null; + expectedModCount++; + associatedMap.modCount++; + associatedMap.elementCount--; + + } + } + + + private static class EntryIterator extends AbstractMapIterator + implements TIterator> { + EntryIterator (THashMap map) { + super(map); + } + + @Override + public TMap.Entry next() { + makeNext(); + return currentEntry; + } + } + + private static class KeyIterator extends AbstractMapIterator implements TIterator { + KeyIterator (THashMap map) { + super(map); + } + + @Override + public K next() { + makeNext(); + return currentEntry.key; + } + } + + private static class ValueIterator extends AbstractMapIterator implements TIterator { + ValueIterator (THashMap map) { + super(map); + } + + @Override + public V next() { + makeNext(); + return currentEntry.value; + } + } + + static class HashMapEntrySet extends TAbstractSet> { + private final THashMap associatedMap; + + public HashMapEntrySet(THashMap hm) { + associatedMap = hm; + } + + THashMap hashMap() { + return associatedMap; + } + + @Override + public int size() { + return associatedMap.elementCount; + } + + @Override + public void clear() { + associatedMap.clear(); + } + + @Override + public boolean remove(Object object) { + if (object instanceof TMap.Entry) { + TMap.Entry oEntry = (TMap.Entry) object; + TMap.Entry entry = associatedMap.getEntry(oEntry.getKey()); + if(valuesEq(entry, oEntry)) { + associatedMap.removeEntry(entry); + return true; + } + } + return false; + } + + @Override + public boolean contains(Object object) { + if (object instanceof TMap.Entry) { + TMap.Entry oEntry = (TMap.Entry) object; + TMap.Entry entry = associatedMap.getEntry(oEntry.getKey()); + return valuesEq(entry, oEntry); + } + return false; + } + + private static boolean valuesEq(TMap.Entry entry, TMap.Entry oEntry) { + return (entry != null) && + ((entry.getValue() == null) ? + (oEntry.getValue() == null) : + (areEqualValues(entry.getValue(), oEntry.getValue()))); + } + + @Override + public TIterator> iterator() { + return new EntryIterator<>(associatedMap); + } + } + + @SuppressWarnings("unchecked") + Entry[] newElementArray(int s) { + return new Entry[s]; + } + + public THashMap() { + this(DEFAULT_SIZE); + } + + public THashMap(int capacity) { + this(capacity, 0.75f); // default load factor of 0.75 + } + + private static final int calculateCapacity(int x) { + if(x >= 1 << 30){ + return 1 << 30; + } + if(x == 0){ + return 16; + } + x = x -1; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + return x + 1; + } + + /** + * Constructs a new {@code HashMap} instance with the specified capacity and + * load factor. + * + * @param capacity + * the initial capacity of this hash map. + * @param loadFactor + * the initial load factor. + * @throws IllegalArgumentException + * when the capacity is less than zero or the load factor is + * less or equal to zero. + */ + public THashMap(int capacity, float loadFactor) { + if (capacity >= 0 && loadFactor > 0) { + capacity = calculateCapacity(capacity); + elementCount = 0; + elementData = newElementArray(capacity); + this.loadFactor = loadFactor; + computeThreshold(); + } else { + throw new TIllegalArgumentException(); + } + } + + public THashMap(TMap map) { + this(calculateCapacity(map.size())); + putAllImpl(map); + } + + @Override + public void clear() { + if (elementCount > 0) { + elementCount = 0; + Arrays.fill(elementData, null); + modCount++; + } + } + + @Override + @SuppressWarnings("unchecked") + public TObject clone() { + try { + THashMap map = (THashMap) super.clone(); + map.elementCount = 0; + map.elementData = newElementArray(elementData.length); + map.putAll(this); + + return map; + } catch (TCloneNotSupportedException e) { + return null; + } + } + + private void computeThreshold() { + threshold = (int) (elementData.length * loadFactor); + } + + @Override + public boolean containsKey(Object key) { + Entry m = getEntry(key); + return m != null; + } + + @Override + public boolean containsValue(Object value) { + if (value != null) { + for (int i = 0; i < elementData.length; i++) { + Entry entry = elementData[i]; + while (entry != null) { + if (areEqualValues(value, entry.value)) { + return true; + } + entry = entry.next; + } + } + } else { + for (int i = 0; i < elementData.length; i++) { + Entry entry = elementData[i]; + while (entry != null) { + if (entry.value == null) { + return true; + } + entry = entry.next; + } + } + } + return false; + } + + @Override + public TSet> entrySet() { + return new HashMapEntrySet<>(this); + } + + @Override + public V get(Object key) { + Entry m = getEntry(key); + if (m != null) { + return m.value; + } + return null; + } + + final Entry getEntry(Object key) { + Entry m; + if (key == null) { + m = findNullKeyEntry(); + } else { + int hash = computeHashCode(key); + int index = hash & (elementData.length - 1); + m = findNonNullKeyEntry(key, index, hash); + } + return m; + } + + final Entry findNonNullKeyEntry(Object key, int index, int keyHash) { + Entry m = elementData[index]; + while (m != null + && (m.origKeyHash != keyHash || !areEqualKeys(key, m.key))) { + m = m.next; + } + return m; + } + + final Entry findNullKeyEntry() { + Entry m = elementData[0]; + while (m != null && m.key != null) + m = m.next; + return m; + } + + @Override + public boolean isEmpty() { + return elementCount == 0; + } + + @Override + public TSet keySet() { + if (cachedKeySet == null) { + cachedKeySet = new TAbstractSet() { + @Override public boolean contains(Object object) { + return containsKey(object); + } + @Override public int size() { + return THashMap.this.size(); + } + @Override public void clear() { + THashMap.this.clear(); + } + @Override public boolean remove(Object key) { + Entry entry = THashMap.this.removeEntry(key); + return entry != null; + } + @Override public TIterator iterator() { + return new KeyIterator<>(THashMap.this); + } + }; + } + return cachedKeySet; + } + + @Override + public V put(K key, V value) { + return putImpl(key, value); + } + + V putImpl(K key, V value) { + Entry entry; + if(key == null) { + entry = findNullKeyEntry(); + if (entry == null) { + modCount++; + entry = createHashedEntry(null, 0, 0); + if (++elementCount > threshold) { + rehash(); + } + } + } else { + int hash = computeHashCode(key); + int index = hash & (elementData.length - 1); + entry = findNonNullKeyEntry(key, index, hash); + if (entry == null) { + modCount++; + entry = createHashedEntry(key, index, hash); + if (++elementCount > threshold) { + rehash(); + } + } + } + + V result = entry.value; + entry.value = value; + return result; + } + + Entry createEntry(K key, int index, V value) { + Entry entry = new Entry<>(key, value); + entry.next = elementData[index]; + elementData[index] = entry; + return entry; + } + + Entry createHashedEntry(K key, int index, int hash) { + Entry entry = new Entry<>(key,hash); + entry.next = elementData[index]; + elementData[index] = entry; + return entry; + } + + @Override + public void putAll(TMap map) { + if (!map.isEmpty()) { + putAllImpl(map); + } + } + + private void putAllImpl(TMap map) { + int capacity = elementCount + map.size(); + if (capacity > threshold) { + rehash(capacity); + } + for (TIterator> iter = map.entrySet().iterator(); + iter.hasNext();) { + TMap.Entry entry = iter.next(); + putImpl(entry.getKey(), entry.getValue()); + } + } + + void rehash(int capacity) { + int length = calculateCapacity((capacity == 0 ? 1 : capacity << 1)); + + Entry[] newData = newElementArray(length); + for (int i = 0; i < elementData.length; i++) { + Entry entry = elementData[i]; + elementData[i] = null; + while (entry != null) { + int index = entry.origKeyHash & (length - 1); + Entry next = entry.next; + entry.next = newData[index]; + newData[index] = entry; + entry = next; + } + } + elementData = newData; + computeThreshold(); + } + + void rehash() { + rehash(elementData.length); + } + + @Override + public V remove(Object key) { + Entry entry = removeEntry(key); + if (entry != null) { + return entry.value; + } + return null; + } + + final void removeEntry(Entry entry) { + int index = entry.origKeyHash & (elementData.length - 1); + Entry m = elementData[index]; + if (m == entry) { + elementData[index] = entry.next; + } else { + while (m.next != entry) { + m = m.next; + } + m.next = entry.next; + + } + modCount++; + elementCount--; + } + + final Entry removeEntry(Object key) { + int index = 0; + Entry entry; + Entry last = null; + if (key != null) { + int hash = computeHashCode(key); + index = hash & (elementData.length - 1); + entry = elementData[index]; + while (entry != null && !(entry.origKeyHash == hash && areEqualKeys(key, entry.key))) { + last = entry; + entry = entry.next; + } + } else { + entry = elementData[0]; + while (entry != null && entry.key != null) { + last = entry; + entry = entry.next; + } + } + if (entry == null) { + return null; + } + if (last == null) { + elementData[index] = entry.next; + } else { + last.next = entry.next; + } + modCount++; + elementCount--; + return entry; + } + + @Override + public int size() { + return elementCount; + } + + @Override + public TCollection values() { + if (cachedValues == null) { + cachedValues = new TAbstractCollection() { + @Override public boolean contains(Object object) { + return containsValue(object); + } + @Override public int size() { + return THashMap.this.size(); + } + @Override public void clear() { + THashMap.this.clear(); + } + @Override public TIterator iterator() { + return new ValueIterator<>(THashMap.this); + } + }; + } + return cachedValues; + } + + static int computeHashCode(Object key) { + return key.hashCode(); + } + + static boolean areEqualKeys(Object key1, Object key2) { + return (key1 == key2) || key1.equals(key2); + } + + static boolean areEqualValues(Object value1, Object value2) { + return (value1 == value2) || value1.equals(value2); + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TMapEntry.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TMapEntry.java new file mode 100644 index 000000000..2e2c03dd4 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TMapEntry.java @@ -0,0 +1,105 @@ +/* + * Copyright 2014 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; + +/** + * MapEntry is an internal class which provides an implementation of Map.Entry. + */ +class TMapEntry implements TMap.Entry, Cloneable { + + K key; + V value; + + interface Type { + RT get(TMapEntry entry); + } + + TMapEntry(K theKey) { + key = theKey; + } + + TMapEntry(K theKey, V theValue) { + key = theKey; + value = theValue; + } + + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + return null; + } + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object instanceof TMap.Entry) { + TMap.Entry entry = (TMap.Entry) object; + return (key == null ? entry.getKey() == null : key.equals(entry.getKey())) + && (value == null ? entry.getValue() == null : value + .equals(entry.getValue())); + } + return false; + } + + @Override + public K getKey() { + return key; + } + + @Override + public V getValue() { + return value; + } + + @Override + public int hashCode() { + return (key == null ? 0 : key.hashCode()) + ^ (value == null ? 0 : value.hashCode()); + } + + @Override + public V setValue(V object) { + V result = value; + value = object; + return result; + } + + @Override + public String toString() { + return key + "=" + value; + } +}