From 58bc611e3a429ba7c9589b66cc9b6a5787a40c02 Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Mon, 9 Jun 2014 21:24:27 +0400 Subject: [PATCH] Copy pcollections's HashPMap to kotlin/reflect/jvm/internal It will be used for caching KClass instances for foreign (Java) classes --- .../jet/parsing/JetCodeConformanceTest.java | 2 + .../jvm/internal/pcollections/ConsPStack.java | 210 ++++++++++++ .../jvm/internal/pcollections/HashPMap.java | 175 ++++++++++ .../internal/pcollections/HashTreePMap.java | 51 +++ .../jvm/internal/pcollections/IntTree.java | 309 ++++++++++++++++++ .../internal/pcollections/IntTreePMap.java | 163 +++++++++ .../internal/pcollections/PCollection.java | 47 +++ .../jvm/internal/pcollections/PMap.java | 45 +++ .../jvm/internal/pcollections/PSequence.java | 69 ++++ .../jvm/internal/pcollections/PStack.java | 53 +++ .../pcollections/SimpleImmutableEntry.java | 148 +++++++++ license/third_party/pcollections_LICENSE.txt | 19 ++ 12 files changed, 1291 insertions(+) create mode 100644 core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/ConsPStack.java create mode 100644 core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/HashPMap.java create mode 100644 core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/HashTreePMap.java create mode 100644 core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/IntTree.java create mode 100644 core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/IntTreePMap.java create mode 100644 core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/PCollection.java create mode 100644 core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/PMap.java create mode 100644 core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/PSequence.java create mode 100644 core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/PStack.java create mode 100644 core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/SimpleImmutableEntry.java create mode 100644 license/third_party/pcollections_LICENSE.txt diff --git a/compiler/tests/org/jetbrains/jet/parsing/JetCodeConformanceTest.java b/compiler/tests/org/jetbrains/jet/parsing/JetCodeConformanceTest.java index 7c092def797..534f6719e38 100644 --- a/compiler/tests/org/jetbrains/jet/parsing/JetCodeConformanceTest.java +++ b/compiler/tests/org/jetbrains/jet/parsing/JetCodeConformanceTest.java @@ -36,6 +36,8 @@ public class JetCodeConformanceTest extends TestCase { private static final Pattern SOURCES_FILE_PATTERN = Pattern.compile("(.+\\.java|.+\\.kt|.+\\.js)"); private static final List EXCLUDED_FILES_AND_DIRS = Arrays.asList( new File("android.tests.dependencies"), + new File("core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections"), + new File("libraries/tools/runtime/target/copied-sources"), new File("dependencies"), new File("examples"), new File("js/js.translator/qunit/qunit.js"), diff --git a/core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/ConsPStack.java b/core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/ConsPStack.java new file mode 100644 index 00000000000..b529b6580c8 --- /dev/null +++ b/core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/ConsPStack.java @@ -0,0 +1,210 @@ +package kotlin.reflect.jvm.internal.pcollections; + +import java.util.AbstractSequentialList; +import java.util.Collection; +import java.util.Iterator; +import java.util.ListIterator; + + + +/** + * + * A simple persistent stack of non-null values. + *

+ * This implementation is thread-safe (assuming Java's AbstractSequentialList is thread-safe), + * although its iterators may not be. + * + * @author harold + * + * @param + */ +public final class ConsPStack extends AbstractSequentialList implements PStack { +//// STATIC FACTORY METHODS //// + private static final ConsPStack EMPTY = new ConsPStack(); + + /** + * @param + * @return an empty stack + */ + @SuppressWarnings("unchecked") + public static ConsPStack empty() { + return (ConsPStack)EMPTY; } + + /** + * @param + * @param e + * @return empty().plus(e) + */ + public static ConsPStack singleton(final E e) { + return ConsPStack.empty().plus(e); } + + /** + * @param + * @param list + * @return a stack consisting of the elements of list in the order of list.iterator() + */ + @SuppressWarnings("unchecked") + public static ConsPStack from(final Collection list) { + if(list instanceof ConsPStack) + return (ConsPStack)list; //(actually we only know it's ConsPStack) + // but that's good enough for an immutable + // (i.e. we can't mess someone else up by adding the wrong type to it) + return from(list.iterator()); + } + + private static ConsPStack from(final Iterator i) { + if(!i.hasNext()) return empty(); + E e = i.next(); + return from(i).plus(e); + } + + +//// PRIVATE CONSTRUCTORS //// + private final E first; private final ConsPStack rest; + private final int size; + // not externally instantiable (or subclassable): + private ConsPStack() { // EMPTY constructor + if(EMPTY!=null) + throw new RuntimeException("empty constructor should only be used once"); + size = 0; first=null; rest=null; + } + private ConsPStack(final E first, final ConsPStack rest) { + this.first = first; this.rest = rest; + + size = 1 + rest.size; + } + + +//// REQUIRED METHODS FROM AbstractSequentialList //// + @Override + public int size() { + return size; } + + @Override + public ListIterator listIterator(final int index) { + if(index<0 || index>size) throw new IndexOutOfBoundsException(); + + return new ListIterator() { + int i = index; + ConsPStack next = subList(index); + + public boolean hasNext() { + return next.size>0; } + public boolean hasPrevious() { + return i>0; } + public int nextIndex() { + return index; } + public int previousIndex() { + return index-1; } + public E next() { + E e = next.first; + next = next.rest; + return e; + } + public E previous() { + System.err.println("ConsPStack.listIterator().previous() is inefficient, don't use it!"); + next = subList(index-1); // go from beginning... + return next.first; + } + + public void add(final E o) { + throw new UnsupportedOperationException(); } + public void remove() { + throw new UnsupportedOperationException(); } + public void set(final E o) { + throw new UnsupportedOperationException(); } + }; + } + + +//// OVERRIDDEN METHODS FROM AbstractSequentialList //// + @Override + public ConsPStack subList(final int start, final int end) { + if(start<0 || end>size || start>end) + throw new IndexOutOfBoundsException(); + if(end==size) // want a substack + return subList(start); // this is faster + if(start==end) // want nothing + return empty(); + if(start==0) // want the current element + return new ConsPStack(first, rest.subList(0, end-1)); + // otherwise, don't want the current element: + return rest.subList(start-1, end-1); + } + + +//// IMPLEMENTED METHODS OF PStack //// + public ConsPStack plus(final E e) { + return new ConsPStack(e, this); + } + + public ConsPStack plusAll(final Collection list) { + ConsPStack result = this; + for(E e : list) + result = result.plus(e); + return result; + } + + public ConsPStack plus(final int i, final E e) { + if(i<0 || i>size) + throw new IndexOutOfBoundsException(); + if(i==0) // insert at beginning + return plus(e); + return new ConsPStack(first, rest.plus(i-1, e)); + } + + public ConsPStack plusAll(final int i, final Collection list) { + // TODO inefficient if list.isEmpty() + if(i<0 || i>size) + throw new IndexOutOfBoundsException(); + if(i==0) + return plusAll(list); + return new ConsPStack(first, rest.plusAll(i-1, list)); + } + + public ConsPStack minus(final Object e) { + if(size==0) + return this; + if(first.equals(e)) // found it + return rest; // don't recurse (only remove one) + // otherwise keep looking: + ConsPStack newRest = rest.minus(e); + if(newRest==rest) return this; + return new ConsPStack(first, newRest); + } + + public ConsPStack minus(final int i) { + return minus(get(i)); + } + + public ConsPStack minusAll(final Collection list) { + if(size==0) + return this; + if(list.contains(first)) // get rid of current element + return rest.minusAll(list); // recursively delete all + // either way keep looking: + ConsPStack newRest = rest.minusAll(list); + if(newRest==rest) return this; + return new ConsPStack(first, newRest); + } + + public ConsPStack with(final int i, final E e) { + if(i<0 || i>=size) + throw new IndexOutOfBoundsException(); + if(i==0) { + if(first.equals(e)) return this; + return new ConsPStack(e, rest); + } + ConsPStack newRest = rest.with(i-1, e); + if(newRest==rest) return this; + return new ConsPStack(first, newRest); + } + + public ConsPStack subList(final int start) { + if(start<0 || start>size) + throw new IndexOutOfBoundsException(); + if(start==0) + return this; + return rest.subList(start-1); + } +} diff --git a/core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/HashPMap.java b/core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/HashPMap.java new file mode 100644 index 00000000000..f2c777c364b --- /dev/null +++ b/core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/HashPMap.java @@ -0,0 +1,175 @@ +package kotlin.reflect.jvm.internal.pcollections; + +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + + + + +/** + * + * A persistent map from non-null keys to non-null values. + *

+ * This map uses a given integer map to map hashcodes to lists of elements + * with the same hashcode. Thus if all elements have the same hashcode, performance + * is reduced to that of an association list. + *

+ * This implementation is thread-safe (assuming Java's AbstractMap and AbstractSet are thread-safe), + * although its iterators may not be. + * + * @author harold + * + * @param + * @param + */ +public final class HashPMap extends AbstractMap implements PMap { +//// STATIC FACTORY METHODS //// + /** + * @param + * @param + * @param intMap + * @return a map backed by an empty version of intMap, + * i.e. backed by intMap.minusAll(intMap.keySet()) + */ + public static HashPMap empty(final PMap>> intMap) { + return new HashPMap(intMap.minusAll(intMap.keySet()), 0); } + + +//// PRIVATE CONSTRUCTORS //// + private final PMap>> intMap; + private final int size; + // not externally instantiable (or subclassable): + private HashPMap(final PMap>> intMap, final int size) { + this.intMap = intMap; this.size = size; } + + +//// REQUIRED METHODS FROM AbstractMap //// + // this cache variable is thread-safe since assignment in Java is atomic: + private Set> entrySet = null; + @Override + public Set> entrySet() { + if(entrySet==null) + entrySet = new AbstractSet>() { + // REQUIRED METHODS OF AbstractSet // + @Override + public int size() { + return size; } + @Override + public Iterator> iterator() { + return new SequenceIterator>(intMap.values().iterator()); } + // OVERRIDDEN METHODS OF AbstractSet // + @Override + public boolean contains(final Object e) { + if(!(e instanceof Entry)) + return false; + V value = get(((Entry)e).getKey()); + return value!=null && value.equals(((Entry)e).getValue()); + } + }; + return entrySet; + } + + +//// OVERRIDDEN METHODS FROM AbstractMap //// + @Override + public int size() { + return size; } + + @Override + public boolean containsKey(final Object key) { + return keyIndexIn(getEntries(key.hashCode()), key) != -1; } + + @Override + public V get(final Object key) { + PSequence> entries = getEntries(key.hashCode()); + for(Entry entry : entries) + if(entry.getKey().equals(key)) + return entry.getValue(); + return null; + } + + +//// IMPLEMENTED METHODS OF PMap//// + public HashPMap plusAll(final Map map) { + HashPMap result = this; + for(Entry entry : map.entrySet()) + result = result.plus(entry.getKey(), entry.getValue()); + return result; + } + + public HashPMap minusAll(final Collection keys) { + HashPMap result = this; + for(Object key : keys) + result = result.minus(key); + return result; + } + + public HashPMap plus(final K key, final V value) { + PSequence> entries = getEntries(key.hashCode()); + int size0 = entries.size(), + i = keyIndexIn(entries, key); + if(i!=-1) entries = entries.minus(i); + entries = entries.plus(new SimpleImmutableEntry(key, value)); + return new HashPMap(intMap.plus(key.hashCode(), entries), + size-size0+entries.size()); + } + + public HashPMap minus(final Object key) { + PSequence> entries = getEntries(key.hashCode()); + int i = keyIndexIn(entries, key); + if(i==-1) // key not in this + return this; + entries = entries.minus(i); + if(entries.size()==0) // get rid of the entire hash entry + return new HashPMap(intMap.minus(key.hashCode()), + size-1); + // otherwise replace hash entry with new smaller one: + return new HashPMap(intMap.plus(key.hashCode(), entries), + size-1); + } + + +//// PRIVATE UTILITIES //// + private PSequence> getEntries(final int hash) { + PSequence> entries = intMap.get(hash); + if(entries==null) return ConsPStack.empty(); + return entries; + } + + +//// PRIVATE STATIC UTILITIES //// + private static int keyIndexIn(final PSequence> entries, final Object key) { + int i=0; + for(Entry entry : entries) { + if(entry.getKey().equals(key)) + return i; + i++; + } + return -1; + } + + static class SequenceIterator implements Iterator { + private final Iterator> i; + private PSequence seq = ConsPStack.empty(); + SequenceIterator(Iterator> i) { + this.i = i; } + + public boolean hasNext() { + return seq.size()>0 || i.hasNext(); } + + public E next() { + if(seq.size()==0) + seq = i.next(); + final E result = seq.get(0); + seq = seq.subList(1, seq.size()); + return result; + } + + public void remove() { + throw new UnsupportedOperationException(); } + } +} diff --git a/core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/HashTreePMap.java b/core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/HashTreePMap.java new file mode 100644 index 00000000000..46b3ab9c195 --- /dev/null +++ b/core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/HashTreePMap.java @@ -0,0 +1,51 @@ +package kotlin.reflect.jvm.internal.pcollections; + +import java.util.Map; +import java.util.Map.Entry; + + + + +/** + * + * A static convenience class for creating efficient persistent maps. + *

+ * This class simply creates HashPMaps backed by IntTreePMaps. + * + * @author harold + */ +public final class HashTreePMap { + // not instantiable (or subclassable): + private HashTreePMap() {} + + private static final HashPMap EMPTY + = HashPMap.empty(IntTreePMap.>>empty()); + + /** + * @param + * @param + * @return an empty map + */ + @SuppressWarnings("unchecked") + public static HashPMap empty() { + return (HashPMap)EMPTY; } + + /** + * @param + * @param + * @param key + * @param value + * @return empty().plus(key, value) + */ + public static HashPMap singleton(final K key, final V value) { + return HashTreePMap.empty().plus(key, value); } + + /** + * @param + * @param + * @param map + * @return empty().plusAll(map) + */ + public static HashPMap from(final Map map) { + return HashTreePMap.empty().plusAll(map); } +} diff --git a/core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/IntTree.java b/core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/IntTree.java new file mode 100644 index 00000000000..d5a1ea7b2bb --- /dev/null +++ b/core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/IntTree.java @@ -0,0 +1,309 @@ +package kotlin.reflect.jvm.internal.pcollections; + +import java.util.Iterator; +import java.util.Map.Entry; + + + + +/** + * + * A non-public utility class for persistent balanced tree maps with integer keys. + *

+ * To allow for efficiently increasing all keys above a certain value or decreasing + * all keys below a certain value, the keys values are stored relative to their parent. + * This makes this map a good backing for fast insertion and removal of indices in a + * vector. + *

+ * This implementation is thread-safe except for its iterators. + *

+ * Other than that, this tree is based on the Glasgow Haskell Compiler's Data.Map implementation, + * which in turn is based on "size balanced binary trees" as described by: + *

+ * Stephen Adams, "Efficient sets: a balancing act", + * Journal of Functional Programming 3(4):553-562, October 1993, + * http://www.swiss.ai.mit.edu/~adams/BB/. + *

+ * J. Nievergelt and E.M. Reingold, "Binary search trees of bounded balance", + * SIAM journal of computing 2(1), March 1973. + * + * @author harold + * + * @param + */ +class IntTree { + // marker value: + static final IntTree EMPTYNODE = new IntTree(); + + private final long key; // we use longs so relative keys can express all ints + // (e.g. if this has key -10 and right has 'absolute' key MAXINT, + // then its relative key is MAXINT+10 which overflows) + // there might be some way to deal with this based on left-verse-right logic, + // but that sounds like a mess. + private final V value; // null value means this is empty node + private final IntTree left, right; + private final int size; + private IntTree() { + if(EMPTYNODE!=null) + throw new RuntimeException("empty constructor should only be used once"); + size = 0; + + key=0; value=null; left=null; right=null; + } + private IntTree(final long key, final V value, final IntTree left, final IntTree right) { + this.key = key; this.value = value; + this.left = left; this.right = right; + size = 1 + left.size + right.size; + } + + private IntTree withKey(final long newKey) { + if(size==0 || newKey==key) return this; + return new IntTree(newKey, value, left, right); } + + Iterator> iterator() { + return new EntryIterator(this); } + + int size() { + return size; } + + boolean containsKey(final long key) { + if(size==0) + return false; + if(key < this.key) + return left.containsKey(key-this.key); + if(key > this.key) + return right.containsKey(key-this.key); + // otherwise key==this.key: + return true; + } + + V get(final long key) { + if(size==0) + return null; + if(key < this.key) + return left.get(key-this.key); + if(key > this.key) + return right.get(key-this.key); + // otherwise key==this.key: + return value; + } + + IntTree plus(final long key, final V value) { + if(size==0) + return new IntTree(key, value, this, this); + if(key < this.key) + return rebalanced(left.plus(key-this.key, value), right); + if(key > this.key) + return rebalanced(left, right.plus(key-this.key, value)); + // otherwise key==this.key, so we simply replace this, with no effect on balance: + if(value==this.value) + return this; + return new IntTree(key, value, left, right); + } + + IntTree minus(final long key) { + if(size==0) + return this; + if(key < this.key) + return rebalanced(left.minus(key-this.key), right); + if(key > this.key) + return rebalanced(left, right.minus(key-this.key)); + + // otherwise key==this.key, so we are killing this node: + + if(left.size==0) // we can just become right node + // make key 'absolute': + return right.withKey(right.key+this.key); + if(right.size==0) // we can just become left node + return left.withKey(left.key+this.key); + + // otherwise replace this with the next key (i.e. the smallest key to the right): + + // TODO have minNode() instead of minKey to avoid having to call get() + // TODO get node from larger subtree, i.e. if left.size>right.size use left.maxNode() + // TODO have faster minusMin() instead of just using minus() + + long newKey = right.minKey() + this.key; + //(right.minKey() is relative to this; adding this.key makes it 'absolute' + // where 'absolute' really means relative to the parent of this) + + V newValue = right.get(newKey-this.key); + // now that we've got the new stuff, take it out of the right subtree: + IntTree newRight = right.minus(newKey-this.key); + + // lastly, make the subtree keys relative to newKey (currently they are relative to this.key): + newRight = newRight.withKey( (newRight.key+this.key) - newKey ); + // left is definitely not empty: + IntTree newLeft = left.withKey( (left.key+this.key) - newKey ); + + return rebalanced(newKey, newValue, newLeft, newRight); + } + + /** + * Changes every key k>=key to k+delta. + * + * This method will create an _invalid_ tree if delta<0 + * and the distance between the smallest k>=key in this + * and the largest j changeKeysAbove(final long key, final int delta) { + if(size==0 || delta==0) + return this; + + if(this.key>=key) + // adding delta to this.key changes the keys of _all_ children of this, + // so we now need to un-change the children of this smaller than key, + // all of which are to the left. note that we still use the 'old' relative key...: + return new IntTree(this.key+delta, value, left.changeKeysBelow(key-this.key, -delta), right); + + // otherwise, doesn't apply yet, look to the right: + IntTree newRight = right.changeKeysAbove(key-this.key, delta); + if(newRight==right) return this; + return new IntTree(this.key, value, left, newRight); + } + + /** + * Changes every key k0 + * and the distance between the largest k=key in this is delta or less. + * + * In other words, this method must not result in any overlap or change + * in the order of the keys in this, since the tree _structure_ is + * not being changed at all. + */ + IntTree changeKeysBelow(final long key, final int delta) { + if(size==0 || delta==0) + return this; + + if(this.key(this.key+delta, value, left, right.changeKeysAbove(key-this.key, -delta)); + + // otherwise, doesn't apply yet, look to the left: + IntTree newLeft = left.changeKeysBelow(key-this.key, delta); + if(newLeft==left) return this; + return new IntTree(this.key, value, newLeft, right); + } + + // min key in this: + private long minKey() { + if(left.size==0) + return key; + // make key 'absolute' (i.e. relative to the parent of this): + return left.minKey() + this.key; + } + + private IntTree rebalanced(final IntTree newLeft, final IntTree newRight) { + if(newLeft==left && newRight==right) + return this; // already balanced + return rebalanced(key, value, newLeft, newRight); + } + + private static final int OMEGA = 5; + private static final int ALPHA = 2; + // rebalance a tree that is off-balance by at most 1: + private static IntTree rebalanced(final long key, final V value, + final IntTree left, final IntTree right) { + if(left.size + right.size > 1) { + if(left.size >= OMEGA*right.size) { // rotate to the right + IntTree ll = left.left, lr = left.right; + if(lr.size < ALPHA*ll.size) // single rotation + return new IntTree(left.key+key, left.value, + ll, + new IntTree(-left.key, value, + lr.withKey(lr.key+left.key), + right)); + else { // double rotation: + IntTree lrl = lr.left, lrr = lr.right; + return new IntTree(lr.key+left.key+key, lr.value, + new IntTree(-lr.key, left.value, + ll, + lrl.withKey(lrl.key+lr.key)), + new IntTree(-left.key-lr.key, value, + lrr.withKey(lrr.key+lr.key+left.key), + right)); + } + } + else if(right.size >= OMEGA*left.size) { // rotate to the left + IntTree rl = right.left, rr = right.right; + if(rl.size < ALPHA*rr.size) // single rotation + return new IntTree(right.key+key, right.value, + new IntTree(-right.key, value, + left, + rl.withKey(rl.key+right.key)), + rr); + else { // double rotation: + IntTree rll = rl.left, rlr = rl.right; + return new IntTree(rl.key+right.key+key, rl.value, + new IntTree(-right.key-rl.key, value, + left, + rll.withKey(rll.key+rl.key+right.key)), + new IntTree(-rl.key, right.value, + rlr.withKey(rlr.key+rl.key), + rr)); + } + } + } + // otherwise already balanced enough: + return new IntTree(key, value, left, right); + } + + +////entrySet().iterator() IMPLEMENTATION //// + // TODO make this a ListIterator? + private static final class EntryIterator implements Iterator> { + private PStack> stack = ConsPStack.empty(); //path of nonempty nodes + private int key = 0; // note we use _int_ here since this is a truly absolute key + + EntryIterator(final IntTree root) { + gotoMinOf(root); } + + public boolean hasNext() { + return stack.size()>0; } + + public Entry next() { + IntTree node = stack.get(0); + final Entry result = new SimpleImmutableEntry(key, node.value); + + // find next node. + // we've already done everything smaller, + // so try least larger node: + + if(node.right.size>0) // we can descend to the right + gotoMinOf(node.right); + + else // can't descend to the right -- try ascending to the right + while (true) { // find current node's least larger ancestor, if any + key -= node.key; // revert to parent's key + stack = stack.subList(1); // climb up to parent + // if parent was larger than child or there was no parent, we're done: + if(node.key<0 || stack.size()==0) + break; + // otherwise parent was smaller -- try its parent: + node = stack.get(0); + } + + return result; + } + + public void remove() { + throw new UnsupportedOperationException(); } + + // extend the stack to its least non-empty node: + private void gotoMinOf(IntTree node) { + while(node.size>0) { + stack = stack.plus(node); + key += node.key; + node = node.left; + } + } + } +} diff --git a/core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/IntTreePMap.java b/core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/IntTreePMap.java new file mode 100644 index 00000000000..4c144faf378 --- /dev/null +++ b/core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/IntTreePMap.java @@ -0,0 +1,163 @@ +package kotlin.reflect.jvm.internal.pcollections; + +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + + + + +/** + * + * An efficient persistent map from integer keys to non-null values. + *

+ * Iteration occurs in the integer order of the keys. + *

+ * This implementation is thread-safe (assuming Java's AbstractMap and AbstractSet are thread-safe), + * although its iterators may not be. + *

+ * The balanced tree is based on the Glasgow Haskell Compiler's Data.Map implementation, + * which in turn is based on "size balanced binary trees" as described by: + *

+ * Stephen Adams, "Efficient sets: a balancing act", + * Journal of Functional Programming 3(4):553-562, October 1993, + * http://www.swiss.ai.mit.edu/~adams/BB/. + *

+ * J. Nievergelt and E.M. Reingold, "Binary search trees of bounded balance", + * SIAM journal of computing 2(1), March 1973. + * + * @author harold + * + * @param + */ +public final class IntTreePMap extends AbstractMap implements PMap { +//// STATIC FACTORY METHODS //// + private static final IntTreePMap EMPTY = new IntTreePMap(IntTree.EMPTYNODE); + + /** + * @param + * @return an empty map + */ + @SuppressWarnings("unchecked") + public static IntTreePMap empty() { + return (IntTreePMap)EMPTY; } + + /** + * @param + * @param key + * @param value + * @return empty().plus(key, value) + */ + public static IntTreePMap singleton(final Integer key, final V value) { + return IntTreePMap.empty().plus(key, value); } + + /** + * @param + * @param map + * @return empty().plusAll(map) + */ + @SuppressWarnings("unchecked") + public static IntTreePMap from(final Map map) { + if(map instanceof IntTreePMap) + return (IntTreePMap)map; //(actually we only know it's IntTreePMap) + // but that's good enough for an immutable + // (i.e. we can't mess someone else up by adding the wrong type to it) + return IntTreePMap.empty().plusAll(map); } + + +//// PRIVATE CONSTRUCTORS //// + private final IntTree root; + // not externally instantiable (or subclassable): + private IntTreePMap(final IntTree root) { + this.root = root; } + private IntTreePMap withRoot(final IntTree root) { + if(root==this.root) return this; + return new IntTreePMap(root); } + + +//// UNINHERITED METHODS OF IntTreePMap //// + IntTreePMap withKeysChangedAbove(final int key, final int delta) { + // TODO check preconditions of changeKeysAbove() + // TODO make public? + return withRoot( root.changeKeysAbove(key, delta) ); + } + + IntTreePMap withKeysChangedBelow(final int key, final int delta) { + // TODO check preconditions of changeKeysAbove() + // TODO make public? + return withRoot( root.changeKeysBelow(key, delta) ); + } + +//// REQUIRED METHODS FROM AbstractMap //// + // this cache variable is thread-safe, since assignment in Java is atomic: + private Set> entrySet = null; + @Override + public Set> entrySet() { + if(entrySet==null) + entrySet = new AbstractSet>() { + // REQUIRED METHODS OF AbstractSet // + @Override + public int size() { // same as Map + return IntTreePMap.this.size(); } + @Override + public Iterator> iterator() { + return root.iterator(); } + // OVERRIDDEN METHODS OF AbstractSet // + @Override + public boolean contains(final Object e) { + if(!(e instanceof Entry)) + return false; + V value = get(((Entry)e).getKey()); + return value!=null && value.equals(((Entry)e).getValue()); + } + }; + return entrySet; + } + + +//// OVERRIDDEN METHODS FROM AbstractMap //// + @Override + public int size() { + return root.size(); } + + @Override + public boolean containsKey(final Object key) { + if(!(key instanceof Integer)) + return false; + return root.containsKey((Integer)key); + } + + @Override + public V get(final Object key) { + if(!(key instanceof Integer)) + return null; + return root.get((Integer)key); + } + + +//// IMPLEMENTED METHODS OF PMap//// + public IntTreePMap plus(final Integer key, final V value) { + return withRoot( root.plus(key, value) ); } + + public IntTreePMap minus(final Object key) { + if(!(key instanceof Integer)) return this; + return withRoot( root.minus((Integer)key) ); } + + public IntTreePMap plusAll(final Map map) { + IntTree root = this.root; + for(Entry entry : map.entrySet()) + root = root.plus(entry.getKey(), entry.getValue()); + return withRoot(root); + } + + public IntTreePMap minusAll(final Collection keys) { + IntTree root = this.root; + for(Object key : keys) + if(key instanceof Integer) + root = root.minus((Integer)key); + return withRoot(root); + } +} diff --git a/core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/PCollection.java b/core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/PCollection.java new file mode 100644 index 00000000000..333acc4ca15 --- /dev/null +++ b/core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/PCollection.java @@ -0,0 +1,47 @@ +package kotlin.reflect.jvm.internal.pcollections; + +import java.util.Collection; + +/** + * + * An immutable, persistent collection of non-null elements of type E. + * + * @author harold + * + * @param + */ +public interface PCollection extends Collection { + + /** + * @param e non-null + * @return a collection which contains e and all of the elements of this + */ + public PCollection plus(E e); + + /** + * @param list contains no null elements + * @return a collection which contains all of the elements of list and this + */ + public PCollection plusAll(Collection list); + + /** + * @param e + * @return this with a single instance of e removed, if e is in this + */ + public PCollection minus(Object e); + + /** + * @param list + * @return this with all elements of list completely removed + */ + public PCollection minusAll(Collection list); + + // TODO public PCollection retainingAll(Collection list); + + @Deprecated boolean add(E o); + @Deprecated boolean remove(Object o); + @Deprecated boolean addAll(Collection c); + @Deprecated boolean removeAll(Collection c); + @Deprecated boolean retainAll(Collection c); + @Deprecated void clear(); +} diff --git a/core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/PMap.java b/core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/PMap.java new file mode 100644 index 00000000000..bdfd4f54896 --- /dev/null +++ b/core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/PMap.java @@ -0,0 +1,45 @@ +package kotlin.reflect.jvm.internal.pcollections; + +import java.util.Collection; +import java.util.Map; + +/** + * + * An immutable, persistent map from non-null keys of type K to non-null values of type V. + * + * @author harold + * + * @param + * @param + */ +public interface PMap extends Map { + /** + * @param key non-null + * @param value non-null + * @return a map with the mappings of this but with key mapped to value + */ + public PMap plus(K key, V value); + + /** + * @param map + * @return this combined with map, with map's mappings used for any keys in both map and this + */ + public PMap plusAll(Map map); + + /** + * @param key + * @return a map with the mappings of this but with no value for key + */ + public PMap minus(Object key); + + /** + * @param keys + * @return a map with the mappings of this but with no value for any element of keys + */ + public PMap minusAll(Collection keys); + + @Deprecated V put(K k, V v); + @Deprecated V remove(Object k); + @Deprecated void putAll(Map m); + @Deprecated void clear(); +} diff --git a/core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/PSequence.java b/core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/PSequence.java new file mode 100644 index 00000000000..3be82dcec0f --- /dev/null +++ b/core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/PSequence.java @@ -0,0 +1,69 @@ +package kotlin.reflect.jvm.internal.pcollections; + +import java.util.Collection; +import java.util.List; + +/** + * + * An immutable, persistent indexed collection. + * + * @author harold + * + * @param + */ +public interface PSequence extends PCollection, List { + + //@Override + public PSequence plus(E e); + + //@Override + public PSequence plusAll(Collection list); + + /** + * @param i + * @param e + * @return a sequence consisting of the elements of this with e replacing the element at index i. + * @throws IndexOutOfBOundsException if i<0 || i>=this.size() + */ + public PSequence with(int i, E e); + + /** + * @param i + * @param e non-null + * @return a sequence consisting of the elements of this with e inserted at index i. + * @throws IndexOutOfBOundsException if i<0 || i>this.size() + */ + public PSequence plus(int i, E e); + + /** + * @param i + * @param list + * @return a sequence consisting of the elements of this with list inserted at index i. + * @throws IndexOutOfBOundsException if i<0 || i>this.size() + */ + public PSequence plusAll(int i, Collection list); + + /** + * Returns a sequence consisting of the elements of this without the first occurrence of e. + */ + //@Override + public PSequence minus(Object e); + + //@Override + public PSequence minusAll(Collection list); + + /** + * @param i + * @return a sequence consisting of the elements of this with the element at index i removed. + * @throws IndexOutOfBOundsException if i<0 || i>=this.size() + */ + public PSequence minus(int i); + + //@Override + public PSequence subList(int start, int end); + + @Deprecated boolean addAll(int index, Collection c); + @Deprecated E set(int index, E element); + @Deprecated void add(int index, E element); + @Deprecated E remove(int index); +} diff --git a/core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/PStack.java b/core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/PStack.java new file mode 100644 index 00000000000..ca68a69360e --- /dev/null +++ b/core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/PStack.java @@ -0,0 +1,53 @@ +package kotlin.reflect.jvm.internal.pcollections; + +import java.util.Collection; + +/** + * + * An immutable, persistent stack. + * + * @author harold + * + * @param + */ +public interface PStack extends PSequence { + + /** + * Returns a stack consisting of the elements of this with e prepended. + */ + //@Override + public PStack plus(E e); + + /** + * Returns a stack consisting of the elements of this with list prepended in reverse. + */ + //@Override + public PStack plusAll(Collection list); + + //@Override + public PStack with(int i, E e); + + //@Override + public PStack plus(int i, E e); + + //@Override + public PStack plusAll(int i, Collection list); + + //@Override + public PStack minus(Object e); + + //@Override + public PStack minusAll(Collection list); + + //@Override + public PStack minus(int i); + + //@Override + public PStack subList(int start, int end); + + /** + * @param start + * @return subList(start,this.size()) + */ + public PStack subList(int start); +} diff --git a/core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/SimpleImmutableEntry.java b/core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/SimpleImmutableEntry.java new file mode 100644 index 00000000000..71452649e25 --- /dev/null +++ b/core/runtime.jvm/src/kotlin/reflect/jvm/internal/pcollections/SimpleImmutableEntry.java @@ -0,0 +1,148 @@ +package kotlin.reflect.jvm.internal.pcollections; + +import java.util.Map; +import java.util.Map.Entry; + + +/** + * (Taken from Java 6 code, so pcollections can be Java 5-compatible.) + *

+ * An Entry maintaining an immutable key and value. This class + * does not support method setValue. This class may be + * convenient in methods that return thread-safe snapshots of + * key-value mappings. + * + * @since 1.6 + */ +/*public*/ final class SimpleImmutableEntry + implements Map.Entry, java.io.Serializable +{ + private static final long serialVersionUID = 7138329143949025153L; + + private final K key; + private final V value; + + /** + * Creates an entry representing a mapping from the specified + * key to the specified value. + * + * @param key the key represented by this entry + * @param value the value represented by this entry + */ + public SimpleImmutableEntry(K key, V value) { + this.key = key; + this.value = value; + } + + /** + * Creates an entry representing the same mapping as the + * specified entry. + * + * @param entry the entry to copy + */ + public SimpleImmutableEntry(Entry entry) { + this.key = entry.getKey(); + this.value = entry.getValue(); + } + + /** + * Returns the key corresponding to this entry. + * + * @return the key corresponding to this entry + */ + public K getKey() { + return key; + } + + /** + * Returns the value corresponding to this entry. + * + * @return the value corresponding to this entry + */ + public V getValue() { + return value; + } + + /** + * Replaces the value corresponding to this entry with the specified + * value (optional operation). This implementation simply throws + * UnsupportedOperationException, as this class implements + * an immutable map entry. + * + * @param value new value to be stored in this entry + * @return (Does not return) + * @throws UnsupportedOperationException always + */ + public V setValue(V value) { + throw new UnsupportedOperationException(); + } + + /** + * Compares the specified object with this entry for equality. + * Returns {@code true} if the given object is also a map entry and + * the two entries represent the same mapping. More formally, two + * entries {@code e1} and {@code e2} represent the same mapping + * if

+	 *   (e1.getKey()==null ?
+	 *    e2.getKey()==null :
+	 *    e1.getKey().equals(e2.getKey()))
+	 *   &&
+	 *   (e1.getValue()==null ?
+	 *    e2.getValue()==null :
+	 *    e1.getValue().equals(e2.getValue()))
+ * This ensures that the {@code equals} method works properly across + * different implementations of the {@code Map.Entry} interface. + * + * @param o object to be compared for equality with this map entry + * @return {@code true} if the specified object is equal to this map + * entry + * @see #hashCode + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry e = (Map.Entry)o; + return eq(key, e.getKey()) && eq(value, e.getValue()); + } + + /** + * Returns the hash code value for this map entry. The hash code + * of a map entry {@code e} is defined to be:
+	 *   (e.getKey()==null   ? 0 : e.getKey().hashCode()) ^
+	 *   (e.getValue()==null ? 0 : e.getValue().hashCode())
+ * This ensures that {@code e1.equals(e2)} implies that + * {@code e1.hashCode()==e2.hashCode()} for any two Entries + * {@code e1} and {@code e2}, as required by the general + * contract of {@link Object#hashCode}. + * + * @return the hash code value for this map entry + * @see #equals + */ + @Override + public int hashCode() { + return (key == null ? 0 : key.hashCode()) ^ + (value == null ? 0 : value.hashCode()); + } + + /** + * Returns a String representation of this map entry. This + * implementation returns the string representation of this + * entry's key followed by the equals character ("=") + * followed by the string representation of this entry's value. + * + * @return a String representation of this map entry + */ + @Override + public String toString() { + return key + "=" + value; + } + + /** + * Utility method for SimpleEntry and SimpleImmutableEntry. + * Test for equality, checking for nulls. + */ + private static boolean eq(Object o1, Object o2) { + return o1 == null ? o2 == null : o1.equals(o2); + } +} diff --git a/license/third_party/pcollections_LICENSE.txt b/license/third_party/pcollections_LICENSE.txt new file mode 100644 index 00000000000..345a0dad954 --- /dev/null +++ b/license/third_party/pcollections_LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2008 Harold Cooper + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE.