From f88d58eb9492c93c2d0e69a319ea0d7b3d114fd3 Mon Sep 17 00:00:00 2001 From: Yu Kobayashi Date: Thu, 17 Jul 2014 14:32:23 +0900 Subject: [PATCH] GROOVY-6944: Add immutable collections. Immutable collections are convenient for backtracking algorithms. The implementation is based on Blackdrag's PCollections. http://pcollections.org/ As the license of PCollections is MIT license, it can be merged to Groovy, which is Apache license. I modified API, and wrote unit tests, and fixed bugs. What I changed. 1. All the public class names start with `Immutable`. 2. All the implementation classes are package scope and marked `Serializable`. 3. `Empty` class is renamed to `ImmutableCollections`. 4. `ImmutableCollections` can create not only empty collections but also convert from an `Iterable` to an immutable collection. HashTreePBag, HashTreePMap and HashTreePSet, which are static convenience classes, are removed. 5. `PVector` is renamed to `ImmutableList`. 6. `POrderedSet` is renamed to `ImmutableListSet`. The name `ListSet` comes from Scala. I didn't use the name `OrderedSet` because it does not have a comparator. 7. `minus()` of PCollections can remove an element and also can remove a specified position element, but this API creates bugs on using `ImmutableList`. Therefore I changed one to `minusAt()`. For consistency, I also renamed to `plusAt()`. And `with()` is renamed to `replaceAt()`. 8. Removed `PSequence` interface. `ImmutableStack` extends `ImmutableList` like `Stack` extends `List`. 9. A new instance can be created by `[] as ImmutableList`. 10. `Iterator` of `ImmutableList` and `ImmutableStack` are same as the original, but `ListIterator` is backed by `Object[]`. I think this is faster than the original. 11. `DefaultGroovyMethods.plus()` , `minus()`, `multiply()` can handle `ImmutableCollections`. 12. `minus(no argument)` of `ImmutableQueue` is renamed to `tail()`. 13. Renamed `Collection.asImmutable()` to `Collection.asUnmodifiable()`, and deprecated the original method. 14. Implemented `Object[] as Queue` and `Object[] as Stack`. 15. Changed `ImmutableQueue` to `ImmutableDeque`. 16. Changed scope of `ImmutableStack` to package scope. Use `ImmutableDeque` instead. 17. Changed scope of `ImmutableBag` to package scope. Use `ImmutableList` instead. 18. Added `minusAt()` and `replaceAt()` to `List` and `Object[]` --- build.gradle | 1 + lib/pcollections-2.2.0-SNAPSHOT.jar | Bin 0 -> 50179 bytes src/main/groovy/transform/Immutable.java | 4 +- .../util/immutable/ImmutableCollection.java | 88 ++++ .../util/immutable/ImmutableCollections.java | 129 ++++++ .../groovy/util/immutable/ImmutableDeque.java | 295 ++++++++++++ .../util/immutable/ImmutableDequeImpl.java | 253 ++++++++++ .../groovy/util/immutable/ImmutableList.java | 161 +++++++ .../util/immutable/ImmutableListImpl.java | 189 ++++++++ .../util/immutable/ImmutableListSet.java | 99 ++++ .../util/immutable/ImmutableListSetImpl.java | 141 ++++++ .../groovy/util/immutable/ImmutableMap.java | 110 +++++ .../util/immutable/ImmutableMapImpl.java | 133 ++++++ .../groovy/util/immutable/ImmutableSet.java | 72 +++ .../util/immutable/ImmutableSetImpl.java | 129 ++++++ .../groovy/runtime/DefaultGroovyMethods.java | 436 ++++++++++++++---- .../runtime/DefaultGroovyMethodsSupport.java | 86 ++++ .../transform/ImmutableASTTransformation.java | 12 +- src/test/groovy/ListTest.groovy | 36 ++ .../util/immutable/ImmutableDequeTest.groovy | 264 +++++++++++ .../immutable/ImmutableListSetTest.groovy | 153 ++++++ .../util/immutable/ImmutableListTest.groovy | 226 +++++++++ .../util/immutable/ImmutableMapTest.groovy | 163 +++++++ .../util/immutable/ImmutableSetTest.groovy | 120 +++++ .../groovy/util/immutable/PBagTest.groovy | 124 +++++ .../groovy/util/immutable/PStackTest.groovy | 300 ++++++++++++ 26 files changed, 3636 insertions(+), 88 deletions(-) create mode 100644 lib/pcollections-2.2.0-SNAPSHOT.jar create mode 100644 src/main/groovy/util/immutable/ImmutableCollection.java create mode 100644 src/main/groovy/util/immutable/ImmutableCollections.java create mode 100644 src/main/groovy/util/immutable/ImmutableDeque.java create mode 100644 src/main/groovy/util/immutable/ImmutableDequeImpl.java create mode 100644 src/main/groovy/util/immutable/ImmutableList.java create mode 100644 src/main/groovy/util/immutable/ImmutableListImpl.java create mode 100644 src/main/groovy/util/immutable/ImmutableListSet.java create mode 100644 src/main/groovy/util/immutable/ImmutableListSetImpl.java create mode 100644 src/main/groovy/util/immutable/ImmutableMap.java create mode 100644 src/main/groovy/util/immutable/ImmutableMapImpl.java create mode 100644 src/main/groovy/util/immutable/ImmutableSet.java create mode 100644 src/main/groovy/util/immutable/ImmutableSetImpl.java create mode 100644 src/test/groovy/util/immutable/ImmutableDequeTest.groovy create mode 100644 src/test/groovy/util/immutable/ImmutableListSetTest.groovy create mode 100644 src/test/groovy/util/immutable/ImmutableListTest.groovy create mode 100644 src/test/groovy/util/immutable/ImmutableMapTest.groovy create mode 100644 src/test/groovy/util/immutable/ImmutableSetTest.groovy create mode 100644 src/test/groovy/util/immutable/PBagTest.groovy create mode 100644 src/test/groovy/util/immutable/PStackTest.groovy diff --git a/build.gradle b/build.gradle index 0b0327eb71..60b882df75 100644 --- a/build.gradle +++ b/build.gradle @@ -138,6 +138,7 @@ dependencies { exclude(group: 'jmock', module: 'jmock') } compile files("lib/openbeans-1.0.jar") + compile files("lib/pcollections-2.2.0-SNAPSHOT.jar") compile "org.fusesource.jansi:jansi:$jansiVersion" compile("org.apache.ivy:ivy:$ivyVersion") { transitive = false diff --git a/lib/pcollections-2.2.0-SNAPSHOT.jar b/lib/pcollections-2.2.0-SNAPSHOT.jar new file mode 100644 index 0000000000000000000000000000000000000000..73e789a7a91386430c0b60b8e174655d24f7044a GIT binary patch literal 50179 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 z{{@code Date}s, {@code Cloneable}s and arrays are defensively copied on the way in (constructor) and out (getters). * Arrays and {@code Cloneable} objects use the {@code clone} method. For your own classes, * it is up to you to define this method and use deep cloning if appropriate. - *
  • {@code Collection}s and {@code Map}s are wrapped by immutable wrapper classes (but not deeply cloned!). + *
  • {@code Collection}s and {@code Map}s are wrapped by unmodifiable wrapper classes (but not deeply cloned!). * Attempts to update them will result in an {@code UnsupportedOperationException}. *
  • Fields that are enums or other {@code @Immutable} classes are allowed but for an * otherwise possible mutable property type, an error is thrown. @@ -101,7 +101,7 @@ * it is up to you to define this method and use deep cloning if appropriate. *
  • *
  • - * As outlined above, {@code Collection}s and {@code Map}s are wrapped by immutable wrapper classes (but not deeply cloned!). + * As outlined above, {@code Collection}s and {@code Map}s are wrapped by unmodifiable wrapper classes (but not deeply cloned!). *
  • *
  • * Currently {@code BigInteger} and {@code BigDecimal} are deemed immutable but see: diff --git a/src/main/groovy/util/immutable/ImmutableCollection.java b/src/main/groovy/util/immutable/ImmutableCollection.java new file mode 100644 index 0000000000..c5d5172539 --- /dev/null +++ b/src/main/groovy/util/immutable/ImmutableCollection.java @@ -0,0 +1,88 @@ +/* + * Copyright 2003-2014 the original author or authors. + * + * 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 groovy.util.immutable; + +import java.util.Collection; + +/** + * An immutable and persistent collection of elements of type E. + * + * @author harold + * @author Yu Kobayashi + * @since 2.4.0 + */ +public interface ImmutableCollection extends Collection { + /** + * @param element an element to append + * @return a collection which contains the element and all of the elements of this + */ + ImmutableCollection plus(E element); + + /** + * @param iterable elements to append + * @return a collection which contains all of the elements of iterable and this + */ + ImmutableCollection plus(Iterable iterable); + + /** + * @param element an element to remove + * @return this with a single instance of the element removed, if the element is in this collection + */ + ImmutableCollection minus(Object element); + + /** + * @param iterable elements to remove + * @return this with all elements of the iterable completely removed + */ + ImmutableCollection minus(Iterable iterable); + + /** + * Always throws {@link UnsupportedOperationException}. + */ + @Deprecated + boolean add(E o); + + /** + * Always throws {@link UnsupportedOperationException}. + */ + @Deprecated + boolean remove(Object o); + + /** + * Always throws {@link UnsupportedOperationException}. + */ + @Deprecated + boolean addAll(Collection c); + + /** + * Always throws {@link UnsupportedOperationException}. + */ + @Deprecated + boolean removeAll(Collection c); + + /** + * Always throws {@link UnsupportedOperationException}. + */ + @Deprecated + boolean retainAll(Collection c); + + /** + * Always throws {@link UnsupportedOperationException}. + */ + @Deprecated + void clear(); +} diff --git a/src/main/groovy/util/immutable/ImmutableCollections.java b/src/main/groovy/util/immutable/ImmutableCollections.java new file mode 100644 index 0000000000..769f1e9267 --- /dev/null +++ b/src/main/groovy/util/immutable/ImmutableCollections.java @@ -0,0 +1,129 @@ +/* + * Copyright 2003-2014 the original author or authors. + * + * 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 groovy.util.immutable; + +import java.util.Map; + +/** + * A static utility class for getting empty immutable and persistent collections or creating immutable and persistent collections from mutable collections backed by the 'default' implementations. + * + * @author mtklein + * @author Yu Kobayashi + * @since 2.4.0 + */ +public final class ImmutableCollections { + /** + * non-instantiable + */ + private ImmutableCollections() { + } + + /** + * Creates an empty immutable deque. + * + * @return an empty immutable deque + */ + public static ImmutableDeque deque() { + return ImmutableDequeImpl.empty(); + } + + /** + * Creates an immutable deque from an iterable. + * + * @param iterable creates from + * @return the immutable deque + */ + public static ImmutableDeque deque(Iterable iterable) { + return ImmutableDequeImpl.from(iterable); + } + + /** + * Creates an empty immutable list. + * + * @return an empty immutable list + */ + public static ImmutableList list() { + return ImmutableListImpl.empty(); + } + + /** + * Creates an immutable list from an iterable. + * + * @param iterable creates from + * @return the immutable list + */ + public static ImmutableList list(Iterable iterable) { + return ImmutableListImpl.from(iterable); + } + + /** + * Creates an empty immutable set. + * + * @return an empty immutable set + */ + public static ImmutableSet set() { + return ImmutableSetImpl.empty(); + } + + /** + * Creates an immutable set from an iterable. + * + * @param iterable creates from + * @return the immutable set + */ + public static ImmutableSet set(Iterable iterable) { + return ImmutableSetImpl.from(iterable); + } + + /** + * Creates an empty immutable list set. + * + * @return an empty immutable list set + */ + public static ImmutableListSet listSet() { + return ImmutableListSetImpl.empty(); + } + + /** + * Creates an immutable list set from an iterable. + * + * @param iterable creates from + * @return the immutable list set + */ + public static ImmutableListSet listSet(Iterable iterable) { + return ImmutableListSetImpl.from(iterable); + } + + /** + * Creates an empty immutable map. + * + * @return an empty immutable map + */ + public static ImmutableMap map() { + return ImmutableMapImpl.empty(); + } + + /** + * Creates an immutable map from a mutable map. + * + * @param map creates from + * @return the immutable map + */ + public static ImmutableMap map(Map map) { + return ImmutableMapImpl.from(map); + } +} diff --git a/src/main/groovy/util/immutable/ImmutableDeque.java b/src/main/groovy/util/immutable/ImmutableDeque.java new file mode 100644 index 0000000000..6391d28502 --- /dev/null +++ b/src/main/groovy/util/immutable/ImmutableDeque.java @@ -0,0 +1,295 @@ +/* + * Copyright 2003-2014 the original author or authors. + * + * 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 groovy.util.immutable; + +import java.util.Deque; + +/** + * An immutable and persistent deque. + * The elements can be null. + *

    + * You can create an instance by {@code [] as ImmutableDeque}. + *

    + * Example: + *

    + * def deque = [] as ImmutableDeque
    + * deque += 1
    + * assert 1 == deque.peek()
    + * deque -= [1]
    + * assert 0 == deque.size()
    + * 
    + * + * @author mtklein + * @author Yu Kobayashi + * @since 2.4.0 + */ +public interface ImmutableDeque extends ImmutableCollection, Deque { + /** + * Complexity: O(1) + * + * @return the first element of this deque + */ + E peek(); + + /** + * Complexity: O(1) + * + * @return the first element of this deque + */ + E peekFirst(); + + /** + * Complexity: O(1) + * + * @return the last element of this deque + */ + E peekLast(); + + /** + * Complexity: O(1) + * + * @return the first element of this deque + * @throws java.util.NoSuchElementException if this deque is empty + */ + E element(); + + /** + * Complexity: O(1) + * + * @return the first element of this deque + * @throws java.util.NoSuchElementException if this deque is empty + */ + E getFirst(); + + /** + * Complexity: O(1) + * + * @return the last element of this deque + * @throws java.util.NoSuchElementException if this deque is empty + */ + E getLast(); + + /** + * Complexity: O(1) + * + * @return the first element of this deque + * @throws java.util.NoSuchElementException if this deque is empty + */ + E first(); + + /** + * Complexity: O(1) + * + * @return the first element of this deque + * @throws java.util.NoSuchElementException if this deque is empty + */ + E head(); + + /** + * Complexity: O(1) + * + * @return the last element of this deque + * @throws java.util.NoSuchElementException if this deque is empty + */ + E last(); + + /** + * Complexity:
    + * + * + * + * + * + * + * + *
     Average-caseAmortizedWorst-case
    Used as DequeO(1)O(n)O(n)
    Used as QueueO(1)O(1)O(n)
    Used as StackO(1)O(1)O(1)
    + * + * @return a deque without its first element + * @throws java.util.NoSuchElementException if this deque is empty + */ + ImmutableDeque tail(); + + /** + * Complexity:
    + * + * + * + * + * + * + * + *
     Average-caseAmortizedWorst-case
    Used as DequeO(1)O(n)O(n)
    Used as QueueO(1)O(1)O(n)
    Used as StackO(1)O(1)O(1)
    + * + * @return a deque without its last element + * @throws java.util.NoSuchElementException if this deque is empty + */ + ImmutableDeque init(); + + /** + * Complexity: O(1) + * + * @param element an element to append + * @return a deque which contains the element and all of the elements of this + */ + ImmutableDeque plus(E element); + + /** + * Complexity: O(1) + * + * @param element an element to append + * @return a deque which contains the element and all of the elements of this + */ + ImmutableDeque plusFirst(E element); + + /** + * Complexity: O(1) + * + * @param element an element to append + * @return a deque which contains the element and all of the elements of this + */ + ImmutableDeque plusLast(E element); + + /** + * Complexity: O(iterable.size()) + * + * @param iterable elements to append + * @return a deque which contains all of the elements of iterable and this + */ + ImmutableDeque plus(Iterable iterable); + + /** + * Complexity: O(iterable.size()) + * + * @param iterable elements to append + * @return a deque which contains all of the elements of iterable and this + */ + ImmutableDeque plusFirst(Iterable iterable); + + /** + * Complexity: O(iterable.size()) + * + * @param iterable elements to append + * @return a deque which contains all of the elements of iterable and this + */ + ImmutableDeque plusLast(Iterable iterable); + + /** + * Complexity: O(n) + * + * @param element an element to remove + * @return this with a single instance of the element removed, if the element is in this deque + */ + ImmutableDeque minus(Object element); + + /** + * Complexity: O(n + iterable.size()) + * + * @param iterable elements to remove + * @return this with all elements of the iterable completely removed + */ + ImmutableDeque minus(Iterable iterable); + + /** + * Always throws {@link UnsupportedOperationException}. + */ + @Deprecated + boolean offer(E e); + + /** + * Always throws {@link UnsupportedOperationException}. + */ + @Deprecated + E poll(); + + /** + * Always throws {@link UnsupportedOperationException}. + */ + @Deprecated + E remove(); + + /** + * Always throws {@link UnsupportedOperationException}. + */ + @Deprecated + void addFirst(E e); + + /** + * Always throws {@link UnsupportedOperationException}. + */ + @Deprecated + void addLast(E e); + + /** + * Always throws {@link UnsupportedOperationException}. + */ + @Deprecated + boolean offerFirst(E e); + + /** + * Always throws {@link UnsupportedOperationException}. + */ + @Deprecated + boolean offerLast(E e); + + /** + * Always throws {@link UnsupportedOperationException}. + */ + @Deprecated + E removeFirst(); + + /** + * Always throws {@link UnsupportedOperationException}. + */ + @Deprecated + E removeLast(); + + /** + * Always throws {@link UnsupportedOperationException}. + */ + @Deprecated + E pollFirst(); + + /** + * Always throws {@link UnsupportedOperationException}. + */ + @Deprecated + E pollLast(); + + /** + * Always throws {@link UnsupportedOperationException}. + */ + @Deprecated + boolean removeFirstOccurrence(Object o); + + /** + * Always throws {@link UnsupportedOperationException}. + */ + @Deprecated + boolean removeLastOccurrence(Object o); + + /** + * Always throws {@link UnsupportedOperationException}. + */ + @Deprecated + void push(E e); + + /** + * Always throws {@link UnsupportedOperationException}. + */ + @Deprecated + E pop(); +} diff --git a/src/main/groovy/util/immutable/ImmutableDequeImpl.java b/src/main/groovy/util/immutable/ImmutableDequeImpl.java new file mode 100644 index 0000000000..cc8d970b4b --- /dev/null +++ b/src/main/groovy/util/immutable/ImmutableDequeImpl.java @@ -0,0 +1,253 @@ +/* + * Copyright 2003-2014 the original author or authors. + * + * 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 groovy.util.immutable; + +import org.codehaus.groovy.runtime.DefaultGroovyMethods; +import org.pcollections.AmortizedPDeque; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Iterator; + +/** + * @author Yu Kobayashi + * @since 2.4.0 + */ +@SuppressWarnings("deprecation") +class ImmutableDequeImpl implements ImmutableDeque, Serializable { + private static final ImmutableDequeImpl EMPTY = new ImmutableDequeImpl(AmortizedPDeque.empty()); + private static final long serialVersionUID = 8383495243255576114L; + + private final AmortizedPDeque deque; + + private ImmutableDequeImpl(AmortizedPDeque deque) { + this.deque = deque; + } + + @SuppressWarnings("unchecked") + static ImmutableDequeImpl empty() { + return (ImmutableDequeImpl) EMPTY; + } + + static ImmutableDequeImpl from(Iterable iterable) { + return (ImmutableDequeImpl) empty().plus(iterable); + } + + public E peek() { + return deque.peek(); + } + + public E peekFirst() { + return deque.peekFirst(); + } + + public E peekLast() { + return deque.peekLast(); + } + + public E element() { + return deque.element(); + } + + public E getFirst() { + return deque.getFirst(); + } + + public E getLast() { + return deque.getLast(); + } + + public E first() { + return deque.first(); + } + + public E head() { + return deque.head(); + } + + public E last() { + return deque.last(); + } + + public ImmutableDeque tail() { + return new ImmutableDequeImpl(deque.tail()); + } + + public ImmutableDeque init() { + return new ImmutableDequeImpl(deque.init()); + } + + public ImmutableDeque plus(E element) { + return new ImmutableDequeImpl(deque.plus(element)); + } + + public ImmutableDeque plusFirst(E element) { + return new ImmutableDequeImpl(deque.plusFirst(element)); + } + + public ImmutableDeque plusLast(E element) { + return new ImmutableDequeImpl(deque.plusLast(element)); + } + + public ImmutableDeque plus(Iterable iterable) { + return new ImmutableDequeImpl(deque.plusAll(DefaultGroovyMethods.asCollection(iterable))); + } + + public ImmutableDeque plusFirst(Iterable iterable) { + return new ImmutableDequeImpl(deque.plusFirstAll(DefaultGroovyMethods.asCollection(iterable))); + } + + public ImmutableDeque plusLast(Iterable iterable) { + return new ImmutableDequeImpl(deque.plusLastAll(DefaultGroovyMethods.asCollection(iterable))); + } + + public ImmutableDeque minus(Object element) { + return new ImmutableDequeImpl(deque.minus(element)); + } + + public ImmutableDeque minus(Iterable iterable) { + return new ImmutableDequeImpl(deque.minusAll(DefaultGroovyMethods.asCollection(iterable))); + } + + public int size() { + return deque.size(); + } + + public boolean isEmpty() { + return deque.isEmpty(); + } + + public boolean contains(Object o) { + return deque.contains(o); + } + + public Iterator iterator() { + return deque.iterator(); + } + + public Object[] toArray() { + return deque.toArray(); + } + + public T[] toArray(T[] a) { + return deque.toArray(a); + } + + public boolean add(E o) { + return deque.add(o); + } + + public boolean remove(Object o) { + return deque.remove(o); + } + + public boolean containsAll(Collection c) { + return deque.containsAll(c); + } + + public boolean addAll(Collection c) { + return deque.addAll(c); + } + + public boolean removeAll(Collection c) { + return deque.removeAll(c); + } + + public boolean retainAll(Collection c) { + return deque.retainAll(c); + } + + public void clear() { + deque.clear(); + } + + public boolean offer(E e) { + return deque.offer(e); + } + + public E poll() { + return deque.poll(); + } + + public E remove() { + return deque.remove(); + } + + public void addFirst(E e) { + deque.addFirst(e); + } + + public void addLast(E e) { + deque.addLast(e); + } + + public boolean offerFirst(E e) { + return deque.offerFirst(e); + } + + public boolean offerLast(E e) { + return deque.offerLast(e); + } + + public E removeFirst() { + return deque.removeFirst(); + } + + public E removeLast() { + return deque.removeLast(); + } + + public E pollFirst() { + return deque.pollFirst(); + } + + public E pollLast() { + return deque.pollLast(); + } + + public boolean removeFirstOccurrence(Object o) { + return deque.removeFirstOccurrence(o); + } + + public boolean removeLastOccurrence(Object o) { + return deque.removeLastOccurrence(o); + } + + public void push(E e) { + deque.push(e); + } + + public E pop() { + return deque.pop(); + } + + public Iterator descendingIterator() { + return deque.descendingIterator(); + } + + public int hashCode() { + return deque.hashCode(); + } + + public boolean equals(Object obj) { + return (obj instanceof ImmutableDequeImpl) && deque.equals(((ImmutableDequeImpl) obj).deque); + } + + public String toString() { + return DefaultGroovyMethods.toString(deque); + } +} diff --git a/src/main/groovy/util/immutable/ImmutableList.java b/src/main/groovy/util/immutable/ImmutableList.java new file mode 100644 index 0000000000..5d1cdb72d8 --- /dev/null +++ b/src/main/groovy/util/immutable/ImmutableList.java @@ -0,0 +1,161 @@ +/* + * Copyright 2003-2014 the original author or authors. + * + * 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 groovy.util.immutable; + +import java.util.Collection; +import java.util.List; + +/** + * An immutable and persistent list. + *

    + * You can create an instance by {@code [] as ImmutableList}. + *

    + * Example: + *

    + * def list = [] as ImmutableList
    + * list += 1
    + * assert 1 == list[0]
    + * list -= [1]
    + * assert 0 == list.size()
    + * 
    + * + * @author harold + * @author Yu Kobayashi + * @since 2.4.0 + */ +public interface ImmutableList extends ImmutableCollection, List { + /** + * Complexity: O(log n) + * + * @param index index of the element to return + * @return the element at the specified position in this list + * @throws IndexOutOfBoundsException if index < 0 || index >= size() + */ + E get(int index); + + /** + * Returns a vector consisting of the elements of this with e appended. + *

    + * Complexity: O(log n) + * + * @param element an element to append + * @return a list which contains the element and all of the elements of this + */ + ImmutableList plus(E element); + + /** + * Returns a vector consisting of the elements of this with list appended. + *

    + * Complexity: O((log n) * list.size()) + * + * @param iterable elements to append + * @return a list which contains all of the elements of iterable and this + */ + ImmutableList plus(Iterable iterable); + + /** + * Complexity: O(log n) + * + * @param index an index to insert + * @param element an element to insert + * @return a list consisting of the elements of this with the element inserted at the specified index. + * @throws IndexOutOfBoundsException if index < 0 || index > size() + */ + ImmutableList plusAt(int index, E element); + + /** + * Complexity: O((log n) * list.size()) + * + * @param index an index to insert + * @param iterable elements to insert + * @return a list consisting of the elements of this with the iterable inserted at the specified index. + * @throws IndexOutOfBoundsException if index < 0 || index > size() + */ + ImmutableList plusAt(int index, Iterable iterable); + + /** + * Complexity: O(log n) + * + * @param index an index to replace + * @param element an element to replace + * @return a list consisting of the elements of this with the element replacing at the specified index. + * @throws IndexOutOfBoundsException if index < 0 || index >= size() + */ + ImmutableList replaceAt(int index, E element); + + /** + * Returns a sequence consisting of the elements of this without the first occurrence of the specified element. + *

    + * Complexity: O(log n) + * + * @param element an element to remove + * @return this with a single instance of the element removed, if the element is in this list + */ + ImmutableList minus(Object element); + + /** + * Complexity: O(log n * list.size()) + * + * @param iterable elements to remove + * @return this with all elements of the iterable completely removed + */ + ImmutableList minus(Iterable iterable); + + /** + * Complexity: O(log n) + * + * @param index an index to remove + * @return a list consisting of the elements of this with the element at the specified index removed. + * @throws IndexOutOfBoundsException if index < 0 || index >= size() + */ + ImmutableList minusAt(int index); + + /** + * Complexity: O(log n) + * + * @param start a start index + * @param end a end index + * @return a view of the specified range within this list + */ + ImmutableList subList(int start, int end); + + /** + * Complexity: O(log n) + * + * @param start a start index + * @return subList(start, this.size()) + */ + ImmutableList subList(int start); + + /** + * Always throws {@link UnsupportedOperationException}. + */ + @Deprecated + boolean addAll(int index, Collection c); + + /** + * Always throws {@link UnsupportedOperationException}. + */ + @Deprecated + void add(int index, E e); + + /** + * Always throws {@link UnsupportedOperationException}. + */ + @Deprecated + E remove(int index); +} diff --git a/src/main/groovy/util/immutable/ImmutableListImpl.java b/src/main/groovy/util/immutable/ImmutableListImpl.java new file mode 100644 index 0000000000..63931d0e78 --- /dev/null +++ b/src/main/groovy/util/immutable/ImmutableListImpl.java @@ -0,0 +1,189 @@ +/* + * Copyright 2003-2014 the original author or authors. + * + * 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 groovy.util.immutable; + +import org.codehaus.groovy.runtime.DefaultGroovyMethods; +import org.pcollections.TreePVector; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Iterator; +import java.util.ListIterator; + +/** + * @author Yu Kobayashi + * @since 2.4.0 + */ +class ImmutableListImpl implements ImmutableList, Serializable { + private static final ImmutableListImpl EMPTY = new ImmutableListImpl(TreePVector.empty()); + private static final long serialVersionUID = -7182079639404183366L; + + private final TreePVector list; + + private ImmutableListImpl(TreePVector list) { + this.list = list; + } + + @SuppressWarnings("unchecked") + static ImmutableListImpl empty() { + return (ImmutableListImpl) EMPTY; + } + + static ImmutableListImpl from(Iterable iterable) { + return (ImmutableListImpl) empty().plus(iterable); + } + + public E get(int index) { + return list.get(index); + } + + public E set(int index, E element) { + return list.set(index, element); + } + + public ImmutableList plus(E element) { + return new ImmutableListImpl(list.plus(element)); + } + + public ImmutableList plus(Iterable iterable) { + return new ImmutableListImpl(list.plusAll(DefaultGroovyMethods.asCollection(iterable))); + } + + public ImmutableList plusAt(int index, E element) { + return new ImmutableListImpl(list.plus(index, element)); + } + + public ImmutableList plusAt(int index, Iterable iterable) { + return new ImmutableListImpl(list.plusAll(index, DefaultGroovyMethods.asCollection(iterable))); + } + + public ImmutableList replaceAt(int index, E element) { + return new ImmutableListImpl(list.with(index, element)); + } + + public ImmutableList minus(Object element) { + return new ImmutableListImpl(list.minus(element)); + } + + public ImmutableList minus(Iterable iterable) { + return new ImmutableListImpl(list.minusAll(DefaultGroovyMethods.asCollection(iterable))); + } + + public int size() { + return list.size(); + } + + public boolean isEmpty() { + return list.isEmpty(); + } + + public boolean contains(Object o) { + return list.contains(o); + } + + public Iterator iterator() { + return list.iterator(); + } + + public Object[] toArray() { + return list.toArray(); + } + + public T[] toArray(T[] a) { + return list.toArray(a); + } + + public boolean add(E o) { + return list.add(o); + } + + public boolean remove(Object o) { + return list.remove(o); + } + + public boolean containsAll(Collection c) { + return list.containsAll(c); + } + + public boolean addAll(Collection c) { + return list.addAll(c); + } + + public boolean removeAll(Collection c) { + return list.removeAll(c); + } + + public boolean retainAll(Collection c) { + return list.retainAll(c); + } + + public void clear() { + list.clear(); + } + + public ImmutableList minusAt(int index) { + return new ImmutableListImpl(list.minus(index)); + } + + public ImmutableList subList(int start, int end) { + return new ImmutableListImpl(list.subList(start, end)); + } + + public ImmutableList subList(int start) { + return new ImmutableListImpl(list.subList(start)); + } + + public boolean addAll(int index, Collection c) { + return list.addAll(index, c); + } + + public void add(int index, E e) { + list.add(index, e); + } + + public E remove(int index) { + return list.remove(index); + } + + public int indexOf(Object o) { + return list.indexOf(o); + } + + public int lastIndexOf(Object o) { + return list.lastIndexOf(o); + } + + public ListIterator listIterator() { + return list.listIterator(); + } + + public ListIterator listIterator(int index) { + return list.listIterator(index); + } + + public int hashCode() { + return list.hashCode(); + } + + public boolean equals(Object obj) { + return (obj instanceof ImmutableListImpl) && list.equals(((ImmutableListImpl) obj).list); + } + + public String toString() { + return DefaultGroovyMethods.toString(list); + } +} diff --git a/src/main/groovy/util/immutable/ImmutableListSet.java b/src/main/groovy/util/immutable/ImmutableListSet.java new file mode 100644 index 0000000000..1b735dedca --- /dev/null +++ b/src/main/groovy/util/immutable/ImmutableListSet.java @@ -0,0 +1,99 @@ +/* + * Copyright 2003-2014 the original author or authors. + * + * 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 groovy.util.immutable; + +/** + * An immutable and presistent list set like {@link ImmutableSet} but preserves insertion order. + * Persistent equivalent of {@link java.util.LinkedHashSet}. + *

    + * The elements must be non-null. + *

    + * You can create an instance by {@code [] as ImmutableListSet}. + *

    + * Example: + *

    + * def listSet = [] as ImmutableListSet
    + * listSet += 1
    + * assert 1 == listSet[0]
    + * listSet -= [1]
    + * assert 0 == listSet.size()
    + * 
    + * + * @author Tassilo Horn <horn@uni-koblenz.de> + * @author Yu Kobayashi + * @since 2.4.0 + */ +public interface ImmutableListSet extends ImmutableSet { + /** + * Complexity: O(log n) + * + * @param element an non-null element to append + * @return a list set which contains the element and all of the elements of this + */ + ImmutableListSet plus(E element); + + /** + * Complexity: O((log n) * iterable.size()) + * + * @param iterable contains no null elements to append + * @return a list set which contains all of the elements of list and this + */ + ImmutableListSet plus(Iterable iterable); + + /** + * Complexity: O(log n) + * + * @param element an element to remove + * @return this with a single instance of the element removed, if the element is in this list set + */ + ImmutableListSet minus(Object element); + + /** + * Complexity: O((log n) * iterable.size()) + * + * @param iterable elements to remove + * @return this with all elements of the iterable completely removed + */ + ImmutableListSet minus(Iterable iterable); + + /** + * Complexity: O(log n) + * + * @param index index of the element to return + * @return the element at the specified position in this list set + * @throws IndexOutOfBoundsException if index < 0 || index >= size() + */ + E get(int index); + + /** + * Complexity: O(log n) + * + * @param o element to search for + * @return the index of the first occurrence of the specified element in this list, or -1 if this list does not contain the element + */ + int indexOf(Object o); + + /** + * This always returns the same value of {@link #indexOf(Object)}. + *

    + * Complexity: O(log n) + * + * @param o element to search for + * @return the index of the last occurrence of the specified element in this list, or -1 if this list does not contain the element + */ + int lastIndexOf(Object o); +} diff --git a/src/main/groovy/util/immutable/ImmutableListSetImpl.java b/src/main/groovy/util/immutable/ImmutableListSetImpl.java new file mode 100644 index 0000000000..866151a4a6 --- /dev/null +++ b/src/main/groovy/util/immutable/ImmutableListSetImpl.java @@ -0,0 +1,141 @@ +/* + * Copyright 2003-2014 the original author or authors. + * + * 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 groovy.util.immutable; + +import org.codehaus.groovy.runtime.DefaultGroovyMethods; +import org.pcollections.OrderedPSet; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Iterator; + +/** + * @author Yu Kobayashi + * @since 2.4.0 + */ +@SuppressWarnings("deprecation") +class ImmutableListSetImpl implements ImmutableListSet, Serializable { + private static final ImmutableListSetImpl EMPTY = new ImmutableListSetImpl(OrderedPSet.empty()); + private static final long serialVersionUID = -3773996693856253314L; + + private final OrderedPSet set; + + private ImmutableListSetImpl(OrderedPSet set) { + this.set = set; + } + + @SuppressWarnings("unchecked") + static ImmutableListSetImpl empty() { + return (ImmutableListSetImpl) EMPTY; + } + + static ImmutableListSetImpl from(Iterable iterable) { + return (ImmutableListSetImpl) empty().plus(iterable); + } + + public ImmutableListSet plus(E element) { + return new ImmutableListSetImpl(set.plus(element)); + } + + public ImmutableListSet plus(Iterable iterable) { + return new ImmutableListSetImpl(set.plusAll(DefaultGroovyMethods.asCollection(iterable))); + } + + public ImmutableListSet minus(Object element) { + return new ImmutableListSetImpl(set.minus(element)); + } + + public ImmutableListSet minus(Iterable iterable) { + return new ImmutableListSetImpl(set.minusAll(DefaultGroovyMethods.asCollection(iterable))); + } + + public int size() { + return set.size(); + } + + public boolean isEmpty() { + return set.isEmpty(); + } + + public boolean contains(Object o) { + return set.contains(o); + } + + public Iterator iterator() { + return set.iterator(); + } + + public Object[] toArray() { + return set.toArray(); + } + + public T[] toArray(T[] a) { + return set.toArray(a); + } + + public boolean add(E o) { + return set.add(o); + } + + public boolean remove(Object o) { + return set.remove(o); + } + + public boolean containsAll(Collection c) { + return set.containsAll(c); + } + + public boolean addAll(Collection c) { + return set.addAll(c); + } + + public boolean removeAll(Collection c) { + return set.removeAll(c); + } + + public boolean retainAll(Collection c) { + return set.retainAll(c); + } + + public void clear() { + set.clear(); + } + + public E get(int index) { + return set.get(index); + } + + public int indexOf(Object o) { + return set.indexOf(o); + } + + public int lastIndexOf(Object o) { + return set.lastIndexOf(o); + } + + public int hashCode() { + return set.hashCode(); + } + + public boolean equals(Object obj) { + return (obj instanceof ImmutableListSetImpl) && set.equals(((ImmutableListSetImpl) obj).set); + } + + public String toString() { + return DefaultGroovyMethods.toString(set); + } +} diff --git a/src/main/groovy/util/immutable/ImmutableMap.java b/src/main/groovy/util/immutable/ImmutableMap.java new file mode 100644 index 0000000000..94f4522afd --- /dev/null +++ b/src/main/groovy/util/immutable/ImmutableMap.java @@ -0,0 +1,110 @@ +/* + * Copyright 2003-2014 the original author or authors. + * + * 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 groovy.util.immutable; + +import java.util.Map; + +/** + * An immutable and persistent map from non-null keys of type K to nullable values of type V. + *

    + * You can create an instance by {@code [:] as ImmutableMap}. + *

    + * Example: + *

    + * def map = [:] as ImmutableMap
    + * map += [a: 1]
    + * assert 1 == map["a"]
    + * map -= ["a"]
    + * assert 0 == map.size()
    + * 
    + * + * @author harold + * @author Yu Kobayashi + * @since 2.4.0 + */ +public interface ImmutableMap extends Map { + /** + * Complexity: O(log n) + * + * @param key the key whose associated value is to be returned + * @return the value to which the specified key is mapped, or {@code null} if this map contains no mapping for the key + */ + V get(Object key); + + /** + * Complexity: O(log n) + * + * @param key a non-null key + * @param value a value + * @return a map with the mappings of this but with key mapped to value + */ + ImmutableMap plus(K key, V value); + + /** + * Complexity: O((log n) * map.size()) + * + * @param map a map to append + * @return this combined with map, with map's mappings used for any keys in both map and this + */ + ImmutableMap plus(Map map); + + /** + * Complexity: O(log n) + * + * @param key a non-null key + * @return a map with the mappings of this but with no value for key + */ + ImmutableMap minus(Object key); + + /** + * Complexity: O((log n) * keys.size()) + * + * @param keys non-null keys + * @return a map with the mappings of this but with no value for any element of keys + */ + ImmutableMap minus(Iterable keys); + + /** + * Always throws {@link UnsupportedOperationException}. + */ + @Deprecated + V putAt(K k, V v); + + /** + * Always throws {@link UnsupportedOperationException}. + */ + @Deprecated + V put(K k, V v); + + /** + * Always throws {@link UnsupportedOperationException}. + */ + @Deprecated + V remove(Object k); + + /** + * Always throws {@link UnsupportedOperationException}. + */ + @Deprecated + void putAll(Map m); + + /** + * Always throws {@link UnsupportedOperationException}. + */ + @Deprecated + void clear(); +} diff --git a/src/main/groovy/util/immutable/ImmutableMapImpl.java b/src/main/groovy/util/immutable/ImmutableMapImpl.java new file mode 100644 index 0000000000..23d1bc77ae --- /dev/null +++ b/src/main/groovy/util/immutable/ImmutableMapImpl.java @@ -0,0 +1,133 @@ +/* + * Copyright 2003-2014 the original author or authors. + * + * 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 groovy.util.immutable; + +import org.codehaus.groovy.runtime.DefaultGroovyMethods; +import org.pcollections.HashPMap; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +/** + * @author Yu Kobayashi + * @since 2.4.0 + */ +@SuppressWarnings("deprecation") +class ImmutableMapImpl implements ImmutableMap, Serializable { + private static final ImmutableMapImpl EMPTY = new ImmutableMapImpl(HashPMap.empty()); + private static final long serialVersionUID = -3266597752208198094L; + + private final HashPMap map; + + private ImmutableMapImpl(HashPMap map) { + this.map = map; + } + + @SuppressWarnings("unchecked") + static ImmutableMapImpl empty() { + return (ImmutableMapImpl) EMPTY; + } + + static ImmutableMapImpl from(Map map) { + return (ImmutableMapImpl) empty().plus(map); + } + + public int size() { + return map.size(); + } + + public boolean isEmpty() { + return map.isEmpty(); + } + + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + public V get(Object key) { + return map.get(key); + } + + public ImmutableMap plus(K key, V value) { + return new ImmutableMapImpl(map.plus(key, value)); + } + + public ImmutableMap plus(Map m) { + return new ImmutableMapImpl(map.plusAll(m)); + } + + public ImmutableMap minus(Object key) { + return new ImmutableMapImpl(map.minus(key)); + } + + public ImmutableMap minus(Iterable keys) { + return new ImmutableMapImpl(map.minusAll(DefaultGroovyMethods.asCollection(keys))); + } + + public V putAt(K k, V v) { + return map.putAt(k, v); + } + + public V put(K k, V v) { + return map.put(k, v); + } + + public V remove(Object k) { + return map.remove(k); + } + + public void putAll(Map m) { + map.putAll(m); + } + + public void clear() { + map.clear(); + } + + public Set keySet() { + return map.keySet(); + } + + public Collection values() { + return map.values(); + } + + public Set> entrySet() { + return map.entrySet(); + } + + @Override + public int hashCode() { + return map.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return (obj instanceof ImmutableMapImpl) && map.equals(((ImmutableMapImpl) obj).map); + } + + @Override + public String toString() { + return DefaultGroovyMethods.toString(map); + } +} diff --git a/src/main/groovy/util/immutable/ImmutableSet.java b/src/main/groovy/util/immutable/ImmutableSet.java new file mode 100644 index 0000000000..9ef79aad26 --- /dev/null +++ b/src/main/groovy/util/immutable/ImmutableSet.java @@ -0,0 +1,72 @@ +/* + * Copyright 2003-2014 the original author or authors. + * + * 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 groovy.util.immutable; + +import java.util.Set; + +/** + * An immutable and persistent set, containing no duplicate elements. + * The elements must be non-null. + *

    + * You can create an instance by {@code [] as ImmutableSet}. + *

    + * Example: + *

    + * def set = [] as ImmutableSet
    + * set += 1
    + * assert set.contains(1)
    + * set -= [1]
    + * assert 0 == set.size()
    + * 
    + * + * @author harold + * @author Yu Kobayashi + * @since 2.4.0 + */ +public interface ImmutableSet extends ImmutableCollection, Set { + /** + * Complexity: O(log n) + * + * @param element an non-null element to append + * @return a set which contains the element and all of the elements of this + */ + ImmutableSet plus(E element); + + /** + * Complexity: O((log n) * iterable.size()) + * + * @param iterable contains non-null elements to append + * @return a set which contains all of the elements of iterable and this + */ + ImmutableSet plus(Iterable iterable); + + /** + * Complexity: O(log n) + * + * @param element an element to remove + * @return this with a single instance of the element removed, if the element is in this set + */ + ImmutableSet minus(Object element); + + /** + * Complexity: O((log n) * iterable.size()) + * + * @param iterable elements to remove + * @return this with all elements of the iterable completely removed + */ + ImmutableSet minus(Iterable iterable); +} diff --git a/src/main/groovy/util/immutable/ImmutableSetImpl.java b/src/main/groovy/util/immutable/ImmutableSetImpl.java new file mode 100644 index 0000000000..98a3352298 --- /dev/null +++ b/src/main/groovy/util/immutable/ImmutableSetImpl.java @@ -0,0 +1,129 @@ +/* + * Copyright 2003-2014 the original author or authors. + * + * 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 groovy.util.immutable; + +import org.codehaus.groovy.runtime.DefaultGroovyMethods; +import org.pcollections.MapPSet; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Iterator; + +/** + * @author Yu Kobayashi + * @since 2.4.0 + */ +@SuppressWarnings("deprecation") +class ImmutableSetImpl implements ImmutableSet, Serializable { + private static final ImmutableSetImpl EMPTY = new ImmutableSetImpl(MapPSet.empty()); + private static final long serialVersionUID = -2986101792651388100L; + + private final MapPSet set; + + private ImmutableSetImpl(MapPSet set) { + this.set = set; + } + + @SuppressWarnings("unchecked") + static ImmutableSetImpl empty() { + return (ImmutableSetImpl) EMPTY; + } + + static ImmutableSetImpl from(Iterable iterable) { + return (ImmutableSetImpl) empty().plus(iterable); + } + + public ImmutableSet plus(E element) { + return new ImmutableSetImpl(set.plus(element)); + } + + public ImmutableSet plus(Iterable iterable) { + return new ImmutableSetImpl(set.plusAll(DefaultGroovyMethods.asCollection(iterable))); + } + + public ImmutableSet minus(Object element) { + return new ImmutableSetImpl(set.minus(element)); + } + + public ImmutableSet minus(Iterable iterable) { + return new ImmutableSetImpl(set.minusAll(DefaultGroovyMethods.asCollection(iterable))); + } + + public int size() { + return set.size(); + } + + public boolean isEmpty() { + return set.isEmpty(); + } + + public boolean contains(Object o) { + return set.contains(o); + } + + public Iterator iterator() { + return set.iterator(); + } + + public Object[] toArray() { + return set.toArray(); + } + + public T[] toArray(T[] a) { + return set.toArray(a); + } + + public boolean add(E o) { + return set.add(o); + } + + public boolean remove(Object o) { + return set.remove(o); + } + + public boolean containsAll(Collection c) { + return set.containsAll(c); + } + + public boolean addAll(Collection c) { + return set.addAll(c); + } + + public boolean removeAll(Collection c) { + return set.removeAll(c); + } + + public boolean retainAll(Collection c) { + return set.retainAll(c); + } + + public void clear() { + set.clear(); + } + + public int hashCode() { + return set.hashCode(); + } + + public boolean equals(Object obj) { + return (obj instanceof ImmutableSetImpl) && set.equals(((ImmutableSetImpl) obj).set); + } + + public String toString() { + return DefaultGroovyMethods.toString(set); + } +} diff --git a/src/main/org/codehaus/groovy/runtime/DefaultGroovyMethods.java b/src/main/org/codehaus/groovy/runtime/DefaultGroovyMethods.java index 3289bb7f34..905967034f 100644 --- a/src/main/org/codehaus/groovy/runtime/DefaultGroovyMethods.java +++ b/src/main/org/codehaus/groovy/runtime/DefaultGroovyMethods.java @@ -29,6 +29,7 @@ import groovy.util.OrderBy; import groovy.util.PermutationGenerator; import groovy.util.ProxyGenerator; +import groovy.util.immutable.*; import org.codehaus.groovy.classgen.Verifier; import org.codehaus.groovy.reflection.ClassInfo; import org.codehaus.groovy.reflection.MixinInMetaClass; @@ -1060,7 +1061,7 @@ public static Collection unique(Collection self, boolean mutate) { self.clear(); self.addAll(answer); } - return mutate ? self : answer ; + return mutate ? self : wrapSimilar(self, answer); } /** @@ -1288,7 +1289,7 @@ public static Collection unique(Collection self, boolean mutate, Compa self.clear(); self.addAll(answer); } - return mutate ? self : answer; + return mutate ? self : wrapSimilar(self, answer); } /** @@ -1435,7 +1436,7 @@ public static Iterator toUnique(Iterator self) { public static Collection toUnique(Iterable self, Comparator comparator) { Collection result = createSimilarCollection((Collection) self); addAll(result, toUnique(self.iterator(), comparator)); - return result; + return wrapSimilar(self, result); } /** @@ -1992,7 +1993,7 @@ public static Collection grep(Object self, Object filter) { answer.add(object); } } - return answer; + return wrapSimilar(self, answer); } /** @@ -2022,7 +2023,7 @@ public static Collection grep(Collection self, Object filter) { answer.add(element); } } - return answer; + return wrapSimilar(self, answer); } /** @@ -2553,10 +2554,10 @@ public static List> collate(Iterable self, int size, int step, bo for (int offs = pos; offs < pos + size && offs < selfList.size(); offs++) { element.add(selfList.get(offs)); } - answer.add( element ) ; + answer.add( wrapSimilar(self, element) ) ; } } - return answer ; + return wrapSimilar(self, answer); } /** @@ -2614,7 +2615,7 @@ public static Collection collect(Object self, Collection collector, Cl for (Iterator iter = InvokerHelper.asIterator(self); iter.hasNext(); ) { collector.add(transform.call(iter.next())); } - return collector; + return wrapSimilar(self, collector); } /** @@ -2663,7 +2664,7 @@ public static Collection collect(Collection self, Collection coll break; } } - return collector; + return wrapSimilar(self, collector); } /** @@ -2760,7 +2761,7 @@ public static Collection collectNested(Iterable self, Collection collector, Clos break; } } - return collector; + return wrapSimilar(self, collector); } /** @@ -2995,7 +2996,7 @@ public static Map collectEntries(Map self, Map co * @since 1.7.9 */ public static Map collectEntries(Map self, @ClosureParams(MapEntryOrKeyValue.class) Closure transform) { - return collectEntries(self, createSimilarMap(self), transform); + return wrapSimilar(self, collectEntries(self, createSimilarMap(self), transform)); } /** @@ -3478,7 +3479,7 @@ public static Collection findResults(Iterable self, @ClosureParams(F result.add(transformed); } } - return result; + return wrapSimilar(self, result); } /** @@ -3507,7 +3508,7 @@ public static Collection findResults(Map self, @ClosureParams(M result.add(transformed); } } - return result; + return wrapSimilar(self, result); } /** @@ -3590,7 +3591,7 @@ public static T findResult(Map self, @ClosureParams(MapEntryOrKeyV public static Collection findAll(Collection self, @ClosureParams(FirstParam.FirstGenericType.class) Closure closure) { Collection answer = createSimilarCollection(self); Iterator iter = self.iterator(); - return findAll(closure, answer, iter); + return wrapSimilar(self, findAll(closure, answer, iter)); } /** @@ -3657,7 +3658,7 @@ public static Collection findAll(T[] self) { public static Collection findAll(Object self, Closure closure) { List answer = new ArrayList(); Iterator iter = InvokerHelper.asIterator(self); - return findAll(closure, answer, iter); + return wrapSimilar(self, findAll(closure, answer, iter)); } /** @@ -3881,7 +3882,7 @@ public static boolean addAll(List self, int index, T[] items) { public static Collection split(Object self, Closure closure) { List accept = new ArrayList(); List reject = new ArrayList(); - return split(closure, accept, reject, InvokerHelper.asIterator(self)); + return split(self, closure, accept, reject, InvokerHelper.asIterator(self)); } /** @@ -3901,10 +3902,11 @@ public static Collection> split(Collection self, @ClosurePa Collection accept = createSimilarCollection(self); Collection reject = createSimilarCollection(self); Iterator iter = self.iterator(); - return split(closure, accept, reject, iter); + return split(self, closure, accept, reject, iter); } - private static Collection> split(Closure closure, Collection accept, Collection reject, Iterator iter) { + private static Collection> split(Object self, Closure closure, + Collection accept, Collection reject, Iterator iter) { List> answer = new ArrayList>(); BooleanClosureWrapper bcw = new BooleanClosureWrapper(closure); while (iter.hasNext()) { @@ -3915,9 +3917,9 @@ private static Collection> split(Closure closure, Collection Map findAll(Map self, @ClosureParams(MapEntryOr answer.put(entry.getKey(), entry.getValue()); } } - return answer; + return wrapSimilar(self, answer); } /** @@ -4464,7 +4466,7 @@ public static Map> groupBy(Map self, @ClosureParams putAll(target, entries); answer.put(key, target); } - return answer; + return wrapSimilar(self, answer); } /** @@ -5710,7 +5712,7 @@ public static List getAt(List self, Range range) { List answer = createSimilarList(self, subList.size()); answer.addAll(subList); - return answer; + return wrapSimilar(self, answer); } @@ -5795,7 +5797,7 @@ public static List getAt(ListWithDefault self, EmptyRange range) { * @since 1.0 */ public static List getAt(List self, EmptyRange range) { - return createSimilarList(self, 0); + return wrapSimilar(self, createSimilarList(self, 0)); } /** @@ -5819,7 +5821,7 @@ public static List getAt(List self, Collection indices) { answer.add(getAt(self, idx)); } } - return answer; + return wrapSimilar(self, answer); } /** @@ -5997,6 +5999,26 @@ public static T getAt(List self, int idx) { } } + /** + * Support the subscript operator for an ImmutableListSet. + *
    def list = [2, "a", 5.3] as ImmutableListSet
    +     * assert list[1] == "a"
    + * + * @param self an ImmutableListSet + * @param idx an index + * @return the value at the given index + * @since 1.0 + */ + public static T getAt(ImmutableListSet self, int idx) { + int size = self.size(); + int i = normaliseIndex(idx, size); + if (i < size) { + return self.get(i); + } else { + return null; + } + } + /** * Support the subscript operator for an Iterator. The iterator * will be partially exhausted up until the idx entry after returning @@ -6310,6 +6332,10 @@ public static V getAt(Map self, K key) { * @since 1.5.0 */ public static Map plus(Map left, Map right) { + if (left instanceof ImmutableMap) { + return ((ImmutableMap) left).plus(right); + } + Map map = cloneSimilarMap(left); map.putAll(right); return map; @@ -6342,7 +6368,7 @@ public static V putAt(Map self, K key, V value) { */ public static List getAt(Collection coll, String property) { List answer = new ArrayList(coll.size()); - return getAtIterable(coll, property, answer); + return wrapSimilar(coll, getAtIterable(coll, property, answer)); } private static List getAtIterable(Iterable coll, String property, List answer) { @@ -6362,17 +6388,40 @@ private static List getAtIterable(Iterable coll, String property, List a } /** - * A convenience method for creating an immutable map. + * A convenience method for creating an unmodifiable map. * * @param self a Map - * @return an immutable Map + * @return an unmodifiable Map * @see java.util.Collections#unmodifiableMap(java.util.Map) * @since 1.0 */ - public static Map asImmutable(Map self) { + public static Map asUnmodifiable(Map self) { return Collections.unmodifiableMap(self); } + /** + * A convenience method for creating an immutable map. + * + * @param self a Map + * @return an immutable Map + * @since 2.4.0 + */ + public static Map asImmutable(Map self) { + return ImmutableCollections.map(self); + } + + /** + * A convenience method for creating an unmodifiable sorted map. + * + * @param self a SortedMap + * @return an unmodifiable SortedMap + * @see java.util.Collections#unmodifiableSortedMap(java.util.SortedMap) + * @since 2.4.0 + */ + public static SortedMap asUnmodifiable(SortedMap self) { + return Collections.unmodifiableSortedMap(self); + } + /** * A convenience method for creating an immutable sorted map. * @@ -6386,27 +6435,67 @@ public static SortedMap asImmutable(SortedMap self) { } /** - * A convenience method for creating an immutable list + * A convenience method for creating an unmodifiable list * * @param self a List - * @return an immutable List + * @return an unmodifiable List * @see java.util.Collections#unmodifiableList(java.util.List) - * @since 1.0 + * @since 2.4.0 */ - public static List asImmutable(List self) { + public static List asUnmodifiable(List self) { return Collections.unmodifiableList(self); } /** * A convenience method for creating an immutable list. * + * Since 2.4.0 this method was using {@link java.util.Collections#unmodifiableList(java.util.List)}, + * but from 2.4.0 this method uses {@link ImmutableCollections#list(Iterable)}. + * + * @param self a List + * @return an immutable List + * @since 1.0 + */ + public static List asImmutable(List self) { + return ImmutableCollections.list(self); + } + + /** + * A convenience method for creating an unmodifiable set. + * * @param self a Set - * @return an immutable Set + * @return an unmodifiable Set * @see java.util.Collections#unmodifiableSet(java.util.Set) + * @since 2.4.0 + */ + public static Set asUnmodifiable(Set self) { + return Collections.unmodifiableSet(self); + } + + /** + * A convenience method for creating an immutable set. + * + * Since 2.4.0 this method was using {@link java.util.Collections#unmodifiableSet(java.util.Set)}, + * but from 2.4.0 this method uses {@link ImmutableCollections#set(Iterable)}. + * + * @param self a Set + * @return an immutable Set * @since 1.0 */ public static Set asImmutable(Set self) { - return Collections.unmodifiableSet(self); + return ImmutableCollections.set(self); + } + + /** + * A convenience method for creating an unmodifiable sorted set. + * + * @param self a SortedSet + * @return an unmodifiable SortedSet + * @see java.util.Collections#unmodifiableSortedSet(java.util.SortedSet) + * @since 2.4.0 + */ + public static SortedSet asUnmodifiable(SortedSet self) { + return Collections.unmodifiableSortedSet(self); } /** @@ -6422,24 +6511,44 @@ public static SortedSet asImmutable(SortedSet self) { } /** - * A convenience method for creating an immutable Collection. + * A convenience method for creating an unmodifiable Collection. *
    def mutable = [1,2,3]
    -     * def immutable = mutable.asImmutable()
    +     * def unmodifiable = mutable.asUnmodifiable()
          * mutable << 4
          * try {
    -     *   immutable << 4
    +     *   unmodifiable << 4
          *   assert false
          * } catch (UnsupportedOperationException) {
          *   assert true
          * }
    * * @param self a Collection - * @return an immutable Collection + * @return an unmodifiable Collection * @see java.util.Collections#unmodifiableCollection(java.util.Collection) + * @since 2.4.0 + */ + public static Collection asUnmodifiable(Collection self) { + return Collections.unmodifiableCollection(self); + } + + /** + * A convenience method for creating an immutable Collection. + * + * @param self a Collection + * @return an immutable Collection * @since 1.5.0 */ + @SuppressWarnings("unchecked") public static Collection asImmutable(Collection self) { - return Collections.unmodifiableCollection(self); + if (self instanceof Queue) { + return ImmutableCollections.deque(self); + } else if (self instanceof Set) { + return ImmutableCollections.set(self); + } else if (self instanceof List) { + return ImmutableCollections.list(self); + } else { + return Collections.unmodifiableCollection(self); + } } /** @@ -7375,7 +7484,6 @@ public static Map toSorted(Map self) { private static class NumberAwareValueComparator implements Comparator> { private Comparator delegate = new NumberAwareComparator(); - @Override public int compare(Map.Entry e1, Map.Entry e2) { return delegate.compare(e1.getValue(), e2.getValue()); } @@ -7501,6 +7609,14 @@ public static Map putAll(Map self, Collection * @since 1.6.1 */ public static Map plus(Map self, Collection> entries) { + if (self instanceof ImmutableMap) { + ImmutableMap immutableMap = (ImmutableMap) self; + for (Map.Entry entry : entries) { + immutableMap = immutableMap.plus(entry.getKey(), entry.getValue()); + } + return immutableMap; + } + Map map = cloneSimilarMap(self); putAll(map, entries); return map; @@ -7735,7 +7851,7 @@ public static Collection tail(Iterable self) { } Collection result = createSimilarCollection(self); addAll(result, tail(self.iterator())); - return result; + return wrapSimilar(self, result); } /** @@ -7804,7 +7920,7 @@ public static Collection init(Iterable self) { result = new ArrayList(); } addAll(result, init(self.iterator())); - return result; + return wrapSimilar(self, result); } /** @@ -7950,7 +8066,7 @@ public static T[] take(T[] self, int num) { public static Collection take(Iterable self, int num) { Collection result = self instanceof Collection ? createSimilarCollection((Collection) self, num < 0 ? 0 : num) : new ArrayList(); addAll(result, take(self.iterator(), num)); - return result; + return wrapSimilar(self, result); } /** @@ -8003,7 +8119,7 @@ public static boolean addAll(Collection self, Iterable items) { */ public static Map take(Map self, int num) { if (self.isEmpty() || num <= 0) { - return createSimilarMap(self); + return wrapSimilar(self, createSimilarMap(self)); } Map ret = createSimilarMap(self); for (K key : self.keySet()) { @@ -8012,7 +8128,7 @@ public static Map take(Map self, int num) { break; } } - return ret; + return wrapSimilar(self, ret); } /** @@ -8124,17 +8240,17 @@ public static T[] takeRight(T[] self, int num) { */ public static Collection takeRight(Iterable self, int num) { if (!self.iterator().hasNext() || num <= 0) { - return self instanceof Collection ? createSimilarCollection((Collection) self, 0) : new ArrayList(); + return wrapSimilar(self, self instanceof Collection ? createSimilarCollection((Collection) self, 0) : new ArrayList()); } Collection selfCol = self instanceof Collection ? (Collection) self : toList(self); if (selfCol.size() <= num) { Collection ret = createSimilarCollection(selfCol, selfCol.size()); ret.addAll(selfCol); - return ret; + return wrapSimilar(self, ret); } Collection ret = createSimilarCollection(selfCol, num); ret.addAll(asList((Iterable) selfCol).subList(selfCol.size() - num, selfCol.size())); - return ret; + return wrapSimilar(self, ret); } /** @@ -8174,7 +8290,7 @@ public static List drop(List self, int num) { public static Collection drop(Iterable self, int num) { Collection result = createSimilarCollection(self); addAll(result, drop(self.iterator(), num)); - return result; + return wrapSimilar(self, result); } /** @@ -8229,7 +8345,7 @@ public static T[] drop(T[] self, int num) { */ public static Map drop(Map self, int num) { if (self.size() <= num) { - return createSimilarMap(self); + return wrapSimilar(self, createSimilarMap(self)); } if (num == 0) { return cloneSimilarMap(self); @@ -8240,7 +8356,7 @@ public static Map drop(Map self, int num) { ret.put(key, self.get(key)); } } - return ret; + return wrapSimilar(self, ret); } /** @@ -8297,7 +8413,7 @@ public static Iterator drop(Iterator self, int num) { public static Collection dropRight(Iterable self, int num) { Collection selfCol = self instanceof Collection ? (Collection) self : toList(self); if (selfCol.size() <= num) { - return createSimilarCollection(selfCol, 0); + return wrapSimilar(self, createSimilarCollection(selfCol, 0)); } if (num <= 0) { Collection ret = createSimilarCollection(selfCol, selfCol.size()); @@ -8306,7 +8422,7 @@ public static Collection dropRight(Iterable self, int num) { } Collection ret = createSimilarCollection(selfCol, selfCol.size() - num); ret.addAll(asList((Iterable)selfCol).subList(0, selfCol.size() - num)); - return ret; + return wrapSimilar(self, ret); } /** @@ -8419,7 +8535,7 @@ public static List takeWhile(List self, @ClosureParams(FirstParam.Firs public static Collection takeWhile(Iterable self, @ClosureParams(FirstParam.FirstGenericType.class) Closure condition) { Collection result = createSimilarCollection(self); addAll(result, takeWhile(self.iterator(), condition)); - return result; + return wrapSimilar(self, result); } /** @@ -8443,7 +8559,7 @@ public static Collection takeWhile(Iterable self, @ClosureParams(First */ public static Map takeWhile(Map self, @ClosureParams(MapEntryOrKeyValue.class) Closure condition) { if (self.isEmpty()) { - return createSimilarMap(self); + return wrapSimilar(self, createSimilarMap(self)); } Map ret = createSimilarMap(self); BooleanClosureWrapper bcw = new BooleanClosureWrapper(condition); @@ -8451,7 +8567,7 @@ public static Map takeWhile(Map self, @ClosureParams(MapEntry if (!bcw.callForMap(entry)) break; ret.put(entry.getKey(), entry.getValue()); } - return ret; + return wrapSimilar(self, ret); } /** @@ -8603,7 +8719,7 @@ public static Collection dropWhile(Iterable self, @ClosureParams(First Collection selfCol = self instanceof Collection ? (Collection) self : toList(self); Collection result = createSimilarCollection(selfCol); addAll(result, dropWhile(self.iterator(), condition)); - return result; + return wrapSimilar(self, result); } /** @@ -8628,7 +8744,7 @@ public static Collection dropWhile(Iterable self, @ClosureParams(First */ public static Map dropWhile(Map self, @ClosureParams(MapEntryOrKeyValue.class) Closure condition) { if (self.isEmpty()) { - return createSimilarMap(self); + return wrapSimilar(self, createSimilarMap(self)); } Map ret = createSimilarMap(self); boolean dropping = true; @@ -8637,7 +8753,7 @@ public static Map dropWhile(Map self, @ClosureParams(MapEntry if (dropping && !bcw.callForMap(entry)) dropping = false; if (!dropping) ret.put(entry.getKey(), entry.getValue()); } - return ret; + return wrapSimilar(self, ret); } /** @@ -9056,6 +9172,22 @@ public static T asType(Collection col, Class clazz) { stack.addAll(col); return (T) stack; } + if (clazz == ImmutableList.class) { + if (col instanceof ImmutableList) return (T) col; + return (T) ImmutableCollections.list(col); + } + if (clazz == ImmutableSet.class) { + if (col instanceof ImmutableSet) return (T) col; + return (T) ImmutableCollections.set(col); + } + if (clazz == ImmutableListSet.class) { + if (col instanceof ImmutableListSet) return (T) col; + return (T) ImmutableCollections.listSet(col); + } + if (clazz == ImmutableDeque.class) { + if (col instanceof ImmutableDeque) return (T) col; + return (T) ImmutableCollections.deque(col); + } if (clazz!=String[].class && ReflectionCache.isArray(clazz)) { try { @@ -9106,6 +9238,26 @@ public static T asType(Object[] ary, Class clazz) { if (clazz == SortedSet.class) { return (T) new TreeSet(Arrays.asList(ary)); } + if (clazz == Queue.class) { + return (T) new LinkedList(Arrays.asList(ary)); + } + if (clazz == Stack.class) { + final Stack stack = new Stack(); + stack.addAll(Arrays.asList(ary)); + return (T) stack; + } + if (clazz == ImmutableList.class) { + return (T) ImmutableCollections.list(Arrays.asList(ary)); + } + if (clazz == ImmutableSet.class) { + return (T) ImmutableCollections.set(Arrays.asList(ary)); + } + if (clazz == ImmutableListSet.class) { + return (T) ImmutableCollections.listSet(Arrays.asList(ary)); + } + if (clazz == ImmutableDeque.class) { + return (T) ImmutableCollections.deque(Arrays.asList(ary)); + } return asType((Object) ary, clazz); } @@ -9159,6 +9311,11 @@ public static T asType(Closure cl, Class clazz) { */ @SuppressWarnings("unchecked") public static T asType(Map map, Class clazz) { + if (clazz == ImmutableMap.class) { + if (map instanceof ImmutableMap) return (T) map; + return (T) ImmutableCollections.map(map); + } + if (!(clazz.isInstance(map)) && clazz.isInterface() && !Traits.isTrait(clazz)) { return (T) Proxy.newProxyInstance( clazz.getClassLoader(), @@ -9222,7 +9379,7 @@ public static List reverse(List self, boolean mutate) { while (iter.hasPrevious()) { answer.add(iter.previous()); } - return answer; + return wrapSimilar(self, answer); } /** @@ -9360,9 +9517,13 @@ public static T[] plus(T[] left, Iterable right) { * @since 1.5.0 */ public static Collection plus(Collection left, Collection right) { + if (left instanceof ImmutableCollection) { + return ((ImmutableCollection) left).plus(right); + } + final Collection answer = cloneSimilarCollection(left, left.size() + right.size()); answer.addAll(right); - return answer; + return wrapSimilar(left, answer); } /** @@ -9426,6 +9587,10 @@ public static Collection plus(Collection left, Iterable right) { * @since 1.8.1 */ public static List plus(List self, int index, T[] items) { + if (self instanceof ImmutableList) { + return ((ImmutableList) self).plusAt(index, Arrays.asList(items)); + } + return plus(self, index, Arrays.asList(items)); } @@ -9455,9 +9620,13 @@ public static List plus(List self, int index, T[] items) { * @since 1.8.1 */ public static List plus(List self, int index, List additions) { + if (self instanceof ImmutableList) { + return ((ImmutableList) self).plusAt(index, additions); + } + final List answer = new ArrayList(self); answer.addAll(index, additions); - return answer; + return wrapSimilar(self, answer); } /** @@ -9488,9 +9657,13 @@ public static List plus(List self, int index, Iterable additions) { * @since 1.5.0 */ public static Collection plus(Collection left, T right) { + if (left instanceof ImmutableCollection) { + return ((ImmutableCollection) left).plus(right); + } + final Collection answer = cloneSimilarCollection(left, left.size() + 1); answer.add(right); - return answer; + return wrapSimilar(left, answer); } /** @@ -9540,7 +9713,7 @@ public static Collection multiply(Iterable self, Number factor) { for (int i = 0; i < size; i++) { answer.addAll(selfCol); } - return answer; + return wrapSimilar(self, answer); } /** @@ -9555,7 +9728,7 @@ public static Collection multiply(Iterable self, Number factor) { */ public static Collection intersect(Collection left, Collection right) { if (left.isEmpty() || right.isEmpty()) - return createSimilarCollection(left, 0); + return wrapSimilar(left, createSimilarCollection(left, 0)); if (left.size() < right.size()) { Collection swaptemp = left; @@ -9575,7 +9748,7 @@ public static Collection intersect(Collection left, Collection righ if (pickFrom.contains(t)) result.add(t); } - return result; + return wrapSimilar(left, result); } /** @@ -9614,7 +9787,7 @@ public static Map intersect(Map left, Map right) { } } } - return ansMap; + return wrapSimilar(left, ansMap); } /** @@ -9888,6 +10061,10 @@ public static boolean equals(Map self, Map other) { * @since 1.5.0 */ public static Set minus(Set self, Collection removeMe) { + if (self instanceof ImmutableSet) { + return ((ImmutableSet) self).minus(removeMe); + } + Comparator comparator = (self instanceof SortedSet) ? ((SortedSet) self).comparator() : null; final Set ansSet = createSimilarSet(self); ansSet.addAll(self); @@ -9901,7 +10078,7 @@ public static Set minus(Set self, Collection removeMe) { } } } - return ansSet; + return wrapSimilar(self, ansSet); } /** @@ -9926,13 +10103,17 @@ public static Set minus(Set self, Iterable removeMe) { * @since 1.5.0 */ public static Set minus(Set self, Object removeMe) { + if (self instanceof ImmutableSet) { + return ((ImmutableSet) self).minus(removeMe); + } + Comparator comparator = (self instanceof SortedSet) ? ((SortedSet) self).comparator() : null; final Set ansSet = createSimilarSet(self); for (T t : self) { boolean areEqual = (comparator != null)? (comparator.compare(t, removeMe) == 0) : coercedEquals(t, removeMe); if (!areEqual) ansSet.add(t); } - return ansSet; + return wrapSimilar(self, ansSet); } /** @@ -9988,9 +10169,13 @@ public static List minus(List self, Collection removeMe) { * @since 2.4.0 */ public static Collection minus(Collection self, Collection removeMe) { + if (self instanceof ImmutableCollection) { + return ((ImmutableCollection) self).minus(removeMe); + } + Collection ansCollection = createSimilarCollection(self); if (self.size() == 0) - return ansCollection; + return wrapSimilar(self, ansCollection); T head = self.iterator().next(); boolean nlgnSort = sameType(new Collection[]{self, removeMe}); @@ -10049,7 +10234,7 @@ public static Collection minus(Collection self, Collection removeMe //can't use treeset since the base classes are different ansCollection.addAll(tmpAnswer); } - return ansCollection; + return wrapSimilar(self, ansCollection); } /** @@ -10099,11 +10284,15 @@ public static List minus(List self, Object removeMe) { * @since 2.4.0 */ public static Collection minus(Iterable self, Object removeMe) { + if (self instanceof ImmutableCollection) { + return ((ImmutableCollection) self).minus(removeMe); + } + Collection ansList = createSimilarCollection(self); for (T t : self) { if (!coercedEquals(t, removeMe)) ansList.add(t); } - return ansList; + return wrapSimilar(self, ansList); } /** @@ -10120,6 +10309,89 @@ public static T[] minus(T[] self, Object removeMe) { return (T[]) minus((Iterable) toList(self), removeMe).toArray(); } + /** + * Create a new List composed of the elements of the first list minus the specified index element to remove. + *
    assert ["a", 5, true].minusAt(1) == ["a", true]
    + * + * @param self an list + * @param index a specified index to remove + * @return the resulting List with the specified index element removed + * @since 2.4.0 + */ + public static List minusAt(List self, int index) { + if (self instanceof ImmutableList) + return ((ImmutableList) self).minusAt(index); + + if (index < 0 || index >= self.size()) + throw new IndexOutOfBoundsException(); + + List ansList = createSimilarList(self, self.size() - 1); + int i = 0; + for (T t : self) { + if (i++ != index) + ansList.add(t); + } + return wrapSimilar(self, ansList); + } + + /** + * Create a new object array composed of the elements of the first array minus the specified index element to remove. + * + * @param self an object array + * @param index a specified index to remove + * @return a new array with the specified index element removed + * @since 2.4.0 + */ + public static T[] minusAt(T[] self, int index) { + if (index < 0 || index >= self.length) + throw new ArrayIndexOutOfBoundsException(); + + T[] ansArray = createSimilarArray(self, self.length - 1); + System.arraycopy(self, 0, ansArray, 0, index); + System.arraycopy(self, index + 1, ansArray, index, ansArray.length - index); + return ansArray; + } + + /** + * Create a new List composed of the elements of the first list minus the specified index element to replace. + *
    assert ["a", 5, true].replaceAt(1, 3) == ["a", 3, true]
    + * + * @param self an list + * @param index a specified index to remove + * @return the resulting List with the specified index element removed + * @since 2.4.0 + */ + public static List replaceAt(List self, int index, T element) { + if (self instanceof ImmutableList) + return ((ImmutableList) self).replaceAt(index, element); + + if (index < 0 || index >= self.size()) + throw new IndexOutOfBoundsException(); + + List ansList = createSimilarList(self, self.size()); + ansList.addAll(self); + ansList.set(index, element); + return wrapSimilar(self, ansList); + } + + /** + * Create a new object array composed of the elements of the first array minus the specified index element to replace. + * + * @param self an object array + * @param index a specified index to replace + * @return a new array with the specified index element replaced + * @since 2.4.0 + */ + public static T[] replaceAt(T[] self, int index, T element) { + if (index < 0 || index >= self.length) + throw new ArrayIndexOutOfBoundsException(); + + T[] ansArray = createSimilarArray(self, self.length); + System.arraycopy(self, 0, ansArray, 0, self.length); + ansArray[index] = element; + return ansArray; + } + /** * Create a Map composed of the entries of the first map minus the * entries of the given map. @@ -10130,6 +10402,10 @@ public static T[] minus(T[] self, Object removeMe) { * @since 1.7.4 */ public static Map minus(Map self, Map removeMe) { + if (self instanceof ImmutableMap) { + return ((ImmutableMap) self).minus(removeMe); + } + final Map ansMap = createSimilarMap(self); ansMap.putAll(self); if (removeMe != null && removeMe.size() > 0) { @@ -10141,7 +10417,7 @@ public static Map minus(Map self, Map removeMe) { } } } - return ansMap; + return wrapSimilar(self, ansMap); } /** @@ -10151,7 +10427,7 @@ public static Map minus(Map self, Map removeMe) { */ @Deprecated public static Collection flatten(Collection self) { - return flatten(self, createSimilarCollection(self)); + return flatten((Iterable) self); } /** @@ -10164,7 +10440,7 @@ public static Collection flatten(Collection self) { * @since 1.6.0 */ public static Collection flatten(Iterable self) { - return flatten(self, createSimilarCollection(self)); + return wrapSimilar(self, flatten(self, createSimilarCollection(self))); } /** @@ -10296,7 +10572,7 @@ private static Collection flatten(Iterable elements, Collection addTo) { */ @Deprecated public static Collection flatten(Collection self, Closure flattenUsing) { - return flatten(self, createSimilarCollection(self), flattenUsing); + return flatten((Iterable) self, flattenUsing); } /** @@ -10312,7 +10588,7 @@ public static Collection flatten(Collection self, Closure * @since 1.6.0 */ public static Collection flatten(Iterable self, Closure flattenUsing) { - return flatten(self, createSimilarCollection(self), flattenUsing); + return wrapSimilar(self, flatten(self, createSimilarCollection(self), flattenUsing)); } private static Collection flatten(Iterable elements, Collection addTo, Closure flattenUsing) { @@ -13547,7 +13823,7 @@ public static List findIndexValues(Object self, Number startIndex, Closu result.add(count); } } - return result; + return wrapSimilar(self, result); } /** diff --git a/src/main/org/codehaus/groovy/runtime/DefaultGroovyMethodsSupport.java b/src/main/org/codehaus/groovy/runtime/DefaultGroovyMethodsSupport.java index 437f5f5afa..1f08f3cf5e 100644 --- a/src/main/org/codehaus/groovy/runtime/DefaultGroovyMethodsSupport.java +++ b/src/main/org/codehaus/groovy/runtime/DefaultGroovyMethodsSupport.java @@ -18,6 +18,7 @@ import groovy.lang.EmptyRange; import groovy.lang.IntRange; import groovy.lang.Range; +import groovy.util.immutable.*; import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation; import java.io.Closeable; @@ -258,4 +259,89 @@ protected static boolean sameType(Collection[] cols) { } return true; } + + /** + * If the first object is an unmodifiable object, then wrap the second list to an unmodifiable collection. + * If the first object is an immutable collection, then wrap the second list to an immutable collection. + */ + protected static Collection wrapSimilar(Object object, Collection collection) { + if (object instanceof ImmutableCollection) { + if (object instanceof ImmutableDeque) { + return ImmutableCollections.deque(collection); + } else if (object instanceof ImmutableListSet) { + return ImmutableCollections.listSet(collection); + } else if (object instanceof ImmutableSet) { + return ImmutableCollections.set(collection); + } else { + return ImmutableCollections.list(collection); + } + } else if (isUnmodifiable(object)) { + if (object instanceof List) { + return Collections.unmodifiableList((List) collection); + } else if (object instanceof SortedSet) { + return Collections.unmodifiableSortedSet((SortedSet) collection); + } else if (object instanceof Set) { + return Collections.unmodifiableSet((Set) collection); + } else { + return Collections.unmodifiableCollection(collection); + } + } else { + return collection; + } + } + + /** + * If the first object is an unmodifiable object, then wrap the second list to an unmodifiable list. + * If the first object is an immutable collection, then wrap the second list to an immutable list. + */ + protected static List wrapSimilar(Object object, List list) { + if (object instanceof ImmutableCollection) { + return ImmutableCollections.list(list); + } else if (isUnmodifiable(object)) { + return Collections.unmodifiableList(list); + } else { + return list; + } + } + + /** + * If the first object is an unmodifiable object, then convert the second set to an unmodifiable set. + * If the first object is an immutable collection, then convert the second set to an immutable set. + */ + protected static Set wrapSimilar(Object object, Set set) { + if (object instanceof ImmutableCollection) { + return ImmutableCollections.set(set); + } else if (isUnmodifiable(object)) { + return Collections.unmodifiableSet(set); + } else { + return set; + } + } + + /** + * If the first object is an unmodifiable object, then convert the second map to an unmodifiable map. + * If the first object is an immutable collection, then convert the second map to an immutable map. + */ + protected static Map wrapSimilar(Object object, Map map) { + if (object instanceof ImmutableCollection) { + return ImmutableCollections.map(map); + } else if (isUnmodifiable(object)) { + if (map instanceof SortedMap) { + return Collections.unmodifiableSortedMap((SortedMap) map); + } else { + return Collections.unmodifiableMap(map); + } + } else { + return map; + } + } + + /** + * Checks whether the specified object is an unmodifiable view or not. + * + * @param object an object to check + */ + protected static boolean isUnmodifiable(Object object) { + return object.getClass().getName().startsWith("java.util.Collections$Unmodifiable"); + } } diff --git a/src/main/org/codehaus/groovy/transform/ImmutableASTTransformation.java b/src/main/org/codehaus/groovy/transform/ImmutableASTTransformation.java index 6af5c7bcc6..8fcd7e94f7 100644 --- a/src/main/org/codehaus/groovy/transform/ImmutableASTTransformation.java +++ b/src/main/org/codehaus/groovy/transform/ImmutableASTTransformation.java @@ -289,7 +289,7 @@ private Expression cloneCollectionExpr(Expression fieldExpr, ClassNode type) { createIfInstanceOfAsImmutableS(fieldExpr, SET_CLASSNODE, createIfInstanceOfAsImmutableS(fieldExpr, MAP_CLASSNODE, createIfInstanceOfAsImmutableS(fieldExpr, ClassHelper.LIST_TYPE, - createAsImmutableX(fieldExpr, COLLECTION_TYPE)) + createAsUnmodifiableX(fieldExpr, COLLECTION_TYPE)) ) ) ) @@ -297,11 +297,11 @@ private Expression cloneCollectionExpr(Expression fieldExpr, ClassNode type) { } private Expression createIfInstanceOfAsImmutableS(Expression expr, ClassNode type, Expression elseStatement) { - return ternaryX(isInstanceOfX(expr, type), createAsImmutableX(expr, type), elseStatement); + return ternaryX(isInstanceOfX(expr, type), createAsUnmodifiableX(expr, type), elseStatement); } - private Expression createAsImmutableX(final Expression expr, final ClassNode type) { - return callX(DGM_TYPE, "asImmutable", castX(type, expr)); + private Expression createAsUnmodifiableX(final Expression expr, final ClassNode type) { + return callX(DGM_TYPE, "asUnmodifiable", castX(type, expr)); } private Expression cloneArrayOrCloneableExpr(Expression fieldExpr, ClassNode type) { @@ -691,7 +691,7 @@ private void createCopyWith(final ClassNode cNode, final List pLis @SuppressWarnings("Unchecked") public static Object checkImmutable(String className, String fieldName, Object field) { if (field == null || field instanceof Enum || inImmutableList(field.getClass().getName())) return field; - if (field instanceof Collection) return DefaultGroovyMethods.asImmutable((Collection) field); + if (field instanceof Collection) return DefaultGroovyMethods.asUnmodifiable((Collection) field); if (field.getClass().getAnnotation(MY_CLASS) != null) return field; final String typeName = field.getClass().getName(); throw new RuntimeException(createErrorMessage(className, fieldName, typeName, "constructing")); @@ -714,7 +714,7 @@ public static Object checkImmutable(Class clazz, String fieldName, Object fie declaredField = clazz.getDeclaredField(fieldName); Class fieldType = declaredField.getType(); if (Collection.class.isAssignableFrom(fieldType)) { - return DefaultGroovyMethods.asImmutable((Collection) field); + return DefaultGroovyMethods.asUnmodifiable((Collection) field); } // potentially allow Collection coercion for a constructor if (fieldType.getAnnotation(MY_CLASS) != null) return field; diff --git a/src/test/groovy/ListTest.groovy b/src/test/groovy/ListTest.groovy index 1caf18e369..cff535f0aa 100644 --- a/src/test/groovy/ListTest.groovy +++ b/src/test/groovy/ListTest.groovy @@ -151,6 +151,42 @@ class ListTest extends GroovyTestCase { assert l1 - l2 == ["wrer", 3, 3, "wrewer", 4, 5] } + void testMinusAt() { + shouldFail(IndexOutOfBoundsException) { + [1, 2].minusAt(-1) + } + shouldFail(IndexOutOfBoundsException) { + [1, 2].minusAt(2) + } + shouldFail(ArrayIndexOutOfBoundsException) { + ([1, 2] as Integer[]).minusAt(-1) + } + shouldFail(ArrayIndexOutOfBoundsException) { + ([1, 2] as Integer[]).minusAt(2) + } + + assert [1, 3] == [1, 2, 3].minusAt(1) + assert Arrays.equals([1, 3] as Integer[], ([1, 2, 3] as Integer[]).minusAt(1)) + } + + void testReplaceAt() { + shouldFail(IndexOutOfBoundsException) { + [1, 2].replaceAt(-1, 0) + } + shouldFail(IndexOutOfBoundsException) { + [1, 2].replaceAt(2, 0) + } + shouldFail(ArrayIndexOutOfBoundsException) { + ([1, 2] as Integer[]).replaceAt(-1, 0) + } + shouldFail(ArrayIndexOutOfBoundsException) { + ([1, 2] as Integer[]).replaceAt(2, 0) + } + + assert [1, 5, 3] == [1, 2, 3].replaceAt(1, 5) + assert Arrays.equals([1, 5, 3] as Integer[], ([1, 2, 3] as Integer[]).replaceAt(1, 5)) + } + void testMinusEmptyCollection() { // GROOVY-790 def list = [1, 1] diff --git a/src/test/groovy/util/immutable/ImmutableDequeTest.groovy b/src/test/groovy/util/immutable/ImmutableDequeTest.groovy new file mode 100644 index 0000000000..95515739dd --- /dev/null +++ b/src/test/groovy/util/immutable/ImmutableDequeTest.groovy @@ -0,0 +1,264 @@ +/* + * Copyright 2003-2014 the original author or authors. + * + * 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 groovy.util.immutable + +/** + * @author Yu Kobayashi + */ +class ImmutableDequeTest extends GroovyTestCase { + void testSupportedOperation() { + def deque = [] as ImmutableDeque + check([], deque) + + deque -= 1 + check([], deque) + + deque += (Integer) null + check([null], deque) + + deque -= (Integer) null + check([], deque) + + deque = deque + 1 + 2 + check([1, 2], deque) + + deque = deque - 2 + check([1], deque) + + deque = deque + 1 + 2 + check([1, 1, 2], deque) + + deque = deque - 2 - 1 - 1 + check([], deque) + + deque += [1] + deque += [2, 3] + check([1, 2, 3], deque) + + deque -= [2, 1] + check([3], deque) + + deque = deque.plusFirst(4) + check([4, 3], deque) + + deque = deque.plusLast(2) + check([4, 3, 2], deque) + + deque = deque.plusFirst([5, 6]) + check([6, 5, 4, 3, 2], deque) + + deque = deque.plusLast([1, 0]) + check([6, 5, 4, 3, 2, 1, 0], deque) + } + + private void check(List answer, ImmutableDeque deque) { + assert answer == deque as List + + // toString, toArray + assert answer.toString() == deque.toString() + assert Arrays.equals(answer as Integer[], deque.toArray()) + + // equals, hashCode + assert ImmutableCollections.deque(answer) == deque + assert ImmutableCollections.deque(answer).hashCode() == deque.hashCode() + + // size, isEmpty + assert answer.size() == deque.size() + assert answer.isEmpty() == deque.isEmpty() + + // getAt + for (int i = 0; i <= answer.size(); i++) { + assert answer[i] == deque[i] + } + if (answer.size() > 0) { + assert answer[-1] == deque[-1] + } + + // contains + for (int i = 0; i <= 4; i++) { + assert answer.contains(i) == deque.contains(i) + } + + // containsAll + assert deque.containsAll([]) + assert deque.containsAll(answer) + if (answer.size() >= 3) { + assert deque.containsAll([answer[0], answer.last()]) + } + + // peek, element, peekFirst, peekLast, getFirst, getLast, first, head, last + if (answer.size() == 0) { + assert null == deque.peek() + assert null == deque.peekFirst() + assert null == deque.peekLast() + shouldFail(NoSuchElementException) { + deque.element() + } + shouldFail(NoSuchElementException) { + deque.getFirst() + } + shouldFail(NoSuchElementException){ + deque.getLast() + } + shouldFail(NoSuchElementException){ + deque.first() + } + shouldFail(NoSuchElementException){ + deque.last() + } + } else { + assert answer[0] == deque.peek() + assert answer[0] == deque.peekFirst() + assert answer[0] == deque.element() + assert answer[0] == deque.getFirst() + assert answer[0] == deque.first() + assert answer[0] == deque.head() + assert answer[-1] == deque.peekLast() + assert answer[-1] == deque.getLast() + assert answer[-1] == deque.last() + } + + // tail + if (answer.size() == 0) { + shouldFail(NoSuchElementException) { + answer.tail() + } + shouldFail(NoSuchElementException) { + deque.tail() + } + } else { + assert answer.tail() == deque.tail() as List + } + + // init + if (answer.size() == 0) { + shouldFail(NoSuchElementException) { + answer.init() + } + shouldFail(NoSuchElementException) { + deque.init() + } + } else { + assert answer.init() == deque.init() as List + } + + // Iterator + Iterator iterAnswer = answer.iterator() + Iterator iter = deque.iterator() + while (true) { + if (iterAnswer.hasNext()) { + assert iter.hasNext() + assert iterAnswer.next() == iter.next() + } else { + shouldFail(NoSuchElementException) { + iter.next() + } + break + } + } + + // descendingIterator + if (!answer.contains(null)) { + Iterator descAnswer = new ArrayDeque(answer).descendingIterator() + Iterator desc = deque.descendingIterator() + while (true) { + if (descAnswer.hasNext()) { + assert desc.hasNext() + assert descAnswer.next() == desc.next() + } else { + shouldFail(NoSuchElementException) { + desc.next() + } + break + } + } + } + } + + @SuppressWarnings("GrDeprecatedAPIUsage") + void testUnsupportedOperation() { + shouldFail(UnsupportedOperationException) { + ImmutableCollections.deque().add(1) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.deque().remove(1) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.deque().addAll([1, 2]) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.deque().removeAll([1, 2]) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.deque().retainAll([1, 2]) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.deque().clear() + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.deque().offer(1) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.deque([0]).poll() + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.deque([0]).remove() + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.deque([0]).addFirst(1) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.deque([0]).addLast(1) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.deque([0]).offerFirst(1) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.deque([0]).offerLast(1) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.deque([0]).removeFirst() + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.deque([0]).removeLast() + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.deque([0]).pollFirst() + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.deque([0]).pollLast() + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.deque([0]).removeFirstOccurrence(1) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.deque([0]).removeLastOccurrence(1) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.deque([0]).push(1) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.deque([0]).pop() + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.deque([0]).iterator().remove() + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.deque([0]).descendingIterator().remove() + } + } +} diff --git a/src/test/groovy/util/immutable/ImmutableListSetTest.groovy b/src/test/groovy/util/immutable/ImmutableListSetTest.groovy new file mode 100644 index 0000000000..1a5fd744d0 --- /dev/null +++ b/src/test/groovy/util/immutable/ImmutableListSetTest.groovy @@ -0,0 +1,153 @@ +/* + * Copyright 2003-2014 the original author or authors. + * + * 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 groovy.util.immutable + +/** + * @author Yu Kobayashi + */ +class ImmutableListSetTest extends GroovyTestCase { + void testSupportedOperation() { + def listSet = [] as ImmutableListSet + check([], listSet) + + shouldFail(NullPointerException) { + listSet + (Integer) null + } + + listSet -= 1 + check([], listSet) + + listSet = listSet + 1 + 2 + check([1, 2], listSet) + + listSet = listSet + 1 + 2 + check([1, 2], listSet) + + listSet = listSet - 1 - 2 + check([], listSet) + + listSet += [1] + listSet += [2, 3] + check([1, 2, 3], listSet) + + listSet -= [2, 1] + check([3], listSet) + } + + private void check(List answer, ImmutableListSet listSet) { + assert answer == listSet as List + + // toString, toArray + assert answer.toString() == listSet.toString() + assert Arrays.equals(answer as Integer[], listSet.toArray()) + + // equals, hashCode + assert ImmutableCollections.listSet(answer) == listSet + assert ImmutableCollections.listSet(answer).hashCode() == listSet.hashCode() + + // size, isEmpty + assert answer.size() == listSet.size() + assert answer.isEmpty() == listSet.isEmpty() + + // getAt + for (int i = 0; i <= answer.size(); i++) { + assert answer[i] == listSet[i] + } + if (answer.size() > 0) { + assert answer[-1] == listSet[-1] + } + shouldFail(IndexOutOfBoundsException) { + listSet.get(-1) + } + shouldFail(IndexOutOfBoundsException) { + listSet.get(answer.size()) + } + + // contains + for (int i = 0; i <= 4; i++) { + assert answer.contains(i) == listSet.contains(i) + assert answer.indexOf(i) == listSet.indexOf(i) + assert answer.lastIndexOf(i) == listSet.lastIndexOf(i) + } + + // containsAll + assert listSet.containsAll([]) + assert listSet.containsAll(answer) + if (answer.size() >= 3) { + assert listSet.containsAll([answer[0], answer.last()]) + } + + // Iterator + Iterator iterAnswer = answer.iterator() + Iterator iter = listSet.iterator() + while (true) { + if (iterAnswer.hasNext()) { + assert iter.hasNext() + assert iterAnswer.next() == iter.next() + } else { + shouldFail(NoSuchElementException) { + iter.next() + } + break + } + } + } + + @SuppressWarnings("GrDeprecatedAPIUsage") + void testUnsupportedOperation() { + shouldFail(UnsupportedOperationException) { + ImmutableCollections.listSet().add(1) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.listSet().remove(1) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.listSet().addAll([1, 2]) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.listSet().removeAll([1, 2]) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.listSet().retainAll([1, 2]) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.listSet().clear() + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.listSet([0]).iterator().remove() + } + } + + public void testBehavesLikeImmutableSet() { + def set = ImmutableCollections.set() + def listSet = ImmutableCollections.listSet() + + Random r = new Random(); + for (int i = 0; i < 100000; i++) { + int v = r.nextInt(1000) + if (r.nextFloat() < 0.8) { + set += v + listSet += v + } else { + set -= v + listSet -= v + } + } + + assert set == listSet + } +} diff --git a/src/test/groovy/util/immutable/ImmutableListTest.groovy b/src/test/groovy/util/immutable/ImmutableListTest.groovy new file mode 100644 index 0000000000..2ef824e0c7 --- /dev/null +++ b/src/test/groovy/util/immutable/ImmutableListTest.groovy @@ -0,0 +1,226 @@ +/* + * Copyright 2003-2014 the original author or authors. + * + * 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 groovy.util.immutable + +/** + * @author Yu Kobayashi + */ +class ImmutableListTest extends GroovyTestCase { + void testSupportedOperation() { + def list = [] as ImmutableList + check([], list) + + list -= 1 + check([], list) + + list += (Integer) null + check([null], list) + + list -= (Integer) null + check([], list) + + list += 1 + check([1], list) + + list += 1 + check([1, 1], list) + + list -= 1 + check([1], list) + + list += [2, 3] + check([1, 2, 3], list) + + list -= [2, 1] + check([3], list) + + list = list.plusAt(0, [1, 2]) + check([1, 2, 3], list) + + list = list.minusAt(0) + check([2, 3], list) + + list = list.replaceAt(1, 4) + check([2, 4], list) + } + + private void check(List answer, ImmutableList list) { + assert answer == list as List + + // toString, toArray + assert answer.toString() == list.toString() + assert Arrays.equals(answer as Integer[], list.toArray()) + + // equals, hashCode + assert ImmutableCollections.list(answer) == list + assert ImmutableCollections.list(answer).hashCode() == list.hashCode() + + // size, isEmpty + assert answer.size() == list.size() + assert answer.isEmpty() == list.isEmpty() + + // getAt + for (int i = 0; i <= answer.size(); i++) { + assert answer[i] == list[i] + } + if (answer.size() > 0) { + assert answer[-1] == list[-1] + } + shouldFail(IndexOutOfBoundsException) { + list.get(-1) + } + shouldFail(IndexOutOfBoundsException) { + list.get(answer.size()) + } + + // contains, indexOf, lastIndexOf + for (int i = 0; i <= 4; i++) { + assert answer.contains(i) == list.contains(i) + assert answer.indexOf(i) == list.indexOf(i) + assert answer.lastIndexOf(i) == list.lastIndexOf(i) + } + + // containsAll + assert list.containsAll([]) + assert list.containsAll(answer) + if (answer.size() >= 3) { + assert list.containsAll([answer[0], answer.last()]) + } + + // subList + if (answer.size() >= 2) { + assert answer.subList(1, answer.size()) == list.subList(1) + } + if (answer.size() >= 3) { + assert answer.subList(1, 2) == list.subList(1, 2) + } + + // Iterator + Iterator iterAnswer = answer.iterator() + Iterator iter = list.iterator() + while (true) { + if (iterAnswer.hasNext()) { + assert iter.hasNext() + assert iterAnswer.next() == iter.next() + } else { + shouldFail(NoSuchElementException) { + iter.next() + } + break + } + } + + // ListIterator + checkListIterator(answer.listIterator(), list.listIterator()) + for (int i = 0; i < answer.size(); i++) { + checkListIterator(answer.listIterator(i), list.listIterator(i)) + } + shouldFail(IndexOutOfBoundsException) { + list.listIterator(-1) + } + shouldFail(IndexOutOfBoundsException) { + list.listIterator(answer.size() + 1) + } + + // multiply + assert (list * 0) instanceof ImmutableList + assert (answer * 0) == (list * 0) as List + assert (list * 3) instanceof ImmutableList + assert (answer * 3) == (list * 3) as List + } + + def void checkListIterator(ListIterator listIterAnswer, ListIterator listIter) { + while (true) { + if (listIterAnswer.hasNext()) { + assert listIter.hasNext() + assert listIterAnswer.nextIndex() == listIter.nextIndex() + assert listIterAnswer.previousIndex() == listIter.previousIndex() + assert listIterAnswer.next() == listIter.next() + } else { + shouldFail(NoSuchElementException) { + listIter.next() + } + break + } + } + while (true) { + if (listIterAnswer.hasPrevious()) { + assert listIter.hasPrevious() + assert listIterAnswer.nextIndex() == listIter.nextIndex() + assert listIterAnswer.previousIndex() == listIter.previousIndex() + assert listIterAnswer.previous() == listIter.previous() + } else { + shouldFail(NoSuchElementException) { + listIter.previous() + } + break + } + } + } + + @SuppressWarnings("GrDeprecatedAPIUsage") + void testUnsupportedOperation() { + shouldFail(UnsupportedOperationException) { + ImmutableCollections.list().add(1) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.list().add(1, 1) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.list().remove(1) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.list().remove((Object) 1) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.list().addAll([1, 2]) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.list().addAll(1, [1, 2]) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.list().removeAll([1, 2]) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.list().retainAll([1, 2]) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.list().clear() + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.list([0]).iterator().remove() + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.list([0]).listIterator().remove() + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.list([0]).listIterator().set(null) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.list([0]).listIterator().add(null) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.list([0]).listIterator(0).remove() + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.list([0]).listIterator(0).set(null) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.list([0]).listIterator(0).add(null) + } + } +} diff --git a/src/test/groovy/util/immutable/ImmutableMapTest.groovy b/src/test/groovy/util/immutable/ImmutableMapTest.groovy new file mode 100644 index 0000000000..ba542bdd0d --- /dev/null +++ b/src/test/groovy/util/immutable/ImmutableMapTest.groovy @@ -0,0 +1,163 @@ +/* + * Copyright 2003-2014 the original author or authors. + * + * 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 groovy.util.immutable + +import java.util.Map.Entry + +/** + * @author Yu Kobayashi + */ +class ImmutableMapTest extends GroovyTestCase { + void testSupportedOperation() { + def map = [:] as ImmutableMap + check([:], map) + + shouldFail(NullPointerException) { + map.plus((String) null, 1) + } + + map -= 1 + check([:], map) + + map += [a: 1] + check([a: 1], map) + + map += [a: 1] + check([a: 1], map) + + map -= 'a' + check([:], map) + + map += [a: 1] + map += [b: 2, c: 3] + check([a: 1, b: 2, c: 3], map) + + map -= ["b", "a"] + check([c: 3], map) + } + + private static void check(Map answer, ImmutableMap map) { + assert answer == map as Map + + // toString, toArray + assert answer.toString() == map.toString() + + // equals, hashCode + assert ImmutableCollections.map(answer) == map + assert ImmutableCollections.map(answer).hashCode() == map.hashCode() + + // size, isEmpty + assert answer.size() == map.size() + assert answer.isEmpty() == map.isEmpty() + + // contains + for (int i = 0; i <= 4; i++) { + def key = (((char) 'a') + i) as String + assert answer.containsKey(key) == map.containsKey(key) + assert answer.containsValue(i) == map.containsValue(i) + assert answer[key] == map[key] + } + + // keySet, entrySet, values + assert answer.keySet() == map.keySet() + assert answer.values() as List == map.values() as List + assert answer.entrySet() == map.entrySet() + } + + @SuppressWarnings("GrDeprecatedAPIUsage") + void testUnsupportedOperation() { + shouldFail(UnsupportedOperationException) { + ImmutableCollections.map()["a"] = 1 + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.map().put("a", 1) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.map().remove(1) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.map().putAll([a: 1, b: 2]) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.map().clear() + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.map([a: 0]).iterator().remove() + } + } + + /** + * Compares the behavior of HashMap to the behavior of ImmutableMap. + */ + void testRandomlyAgainstJavaMap() { + def pmap = ImmutableCollections.map() // [:] as ImmutableMap + def map = new HashMap() + def r = new Random() + for (int i = 0; i < 1000; i++) { + if (pmap.size() == 0 || r.nextBoolean()) { // add + int k = r.nextInt() + int v = r.nextInt() + + assert map.containsKey(k) == pmap.containsKey(k) + assert map[k] == pmap[k] + + map[k] = v + pmap = pmap.plus(k, v); + } else { // remove a random key + int j = r.nextInt(pmap.size()) + for (Entry e : pmap.entrySet()) { + int k = e.key + + assert map.containsKey(k) + assert pmap.containsKey(k) + assert map[k] == pmap[k] + assert map.entrySet().contains(e) + assert pmap.entrySet().contains(e) + assert pmap == pmap.plus(k, e.value) + + if (j-- == 0) { + map.remove(k) + pmap -= k + assert !pmap.entrySet().contains(e) + } + } + } + + // also try to remove a _totally_ random key: + int k = r.nextInt() + assert map.containsKey(k) == pmap.containsKey(k) + assert map[k] == pmap[k] + map.remove(k) + pmap -= k + + // and try out a non-Integer: + def s = Integer.toString(k) + assert !pmap.containsKey(s) + assert null == pmap[s] + assert !pmap.entrySet().contains(s) + pmap -= s + + assert map.size() == pmap.size() + assert map == pmap + assert map.entrySet() == pmap.entrySet() + + assert pmap == ImmutableCollections.map(pmap) + assert ImmutableCollections.map() == (pmap - pmap.keySet()) + assert pmap == (pmap + pmap) + } + } +} diff --git a/src/test/groovy/util/immutable/ImmutableSetTest.groovy b/src/test/groovy/util/immutable/ImmutableSetTest.groovy new file mode 100644 index 0000000000..c4e70184e2 --- /dev/null +++ b/src/test/groovy/util/immutable/ImmutableSetTest.groovy @@ -0,0 +1,120 @@ +/* + * Copyright 2003-2014 the original author or authors. + * + * 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 groovy.util.immutable + +/** + * @author Yu Kobayashi + */ +class ImmutableSetTest extends GroovyTestCase { + void testSupportedOperation() { + def set = [] as ImmutableSet + check([], set) + + shouldFail(NullPointerException) { + set + (Integer) null + } + + set -= 1 + check([], set) + + set = set + 1 + 2 + check([1, 2], set) + + set = set + 1 + 2 + check([1, 2], set) + + set = set - 2 + check([1], set) + + set += [2, 3] + check([1, 2, 3], set) + + set -= [2, 1] + check([3], set) + } + + private void check(List ans, ImmutableSet set) { + Set answer = ans as Set + + // toString, toArray + if (answer.size() <= 1) { + assert answer.toString() == set.toString() + assert Arrays.equals(answer as Integer[], set.toArray()) + } + + // equals, hashCode + assert ImmutableCollections.set(answer) == set + assert ImmutableCollections.set(answer).hashCode() == set.hashCode() + + // size, isEmpty + assert answer.size() == set.size() + assert answer.isEmpty() == set.isEmpty() + + // contains + for (int i = 0; i <= 4; i++) { + assert answer.contains(i) == set.contains(i) + } + + // containsAll + assert set.containsAll([]) + assert set.containsAll(answer) + if (answer.size() >= 3) { + assert set.containsAll([answer[0], answer.last()]) + } + + // Iterator + Iterator iterAnswer = answer.iterator() + Iterator iter = set.iterator() + while (true) { + if (iterAnswer.hasNext()) { + assert iter.hasNext() + assert answer.contains(iter.next()) + iterAnswer.next() + } else { + shouldFail(NoSuchElementException) { + iter.next() + } + break + } + } + } + + @SuppressWarnings("GrDeprecatedAPIUsage") + void testUnsupportedOperation() { + shouldFail(UnsupportedOperationException) { + ImmutableCollections.set().add(1) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.set().remove(1) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.set().addAll([1, 2]) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.set().removeAll([1, 2]) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.set().retainAll([1, 2]) + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.set().clear() + } + shouldFail(UnsupportedOperationException) { + ImmutableCollections.set([0]).iterator().remove() + } + } +} diff --git a/src/test/groovy/util/immutable/PBagTest.groovy b/src/test/groovy/util/immutable/PBagTest.groovy new file mode 100644 index 0000000000..8f9c5cd211 --- /dev/null +++ b/src/test/groovy/util/immutable/PBagTest.groovy @@ -0,0 +1,124 @@ +/* + * Copyright 2003-2014 the original author or authors. + * + * 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 groovy.util.immutable + +import org.pcollections.HashTreePBag +import org.pcollections.MapPBag +import org.pcollections.PBag + +/** + * @author Yu Kobayashi + */ +class PBagTest extends GroovyTestCase { + void testSupportedOperation() { + MapPBag bag = MapPBag. empty() + check([], bag) + + shouldFail(NullPointerException) { + bag + (Integer) null + } + + bag -= 1 + check([], bag) + + bag = bag + 1 + 2 + check([1, 2], bag) + + bag = bag + 1 + 2 + check([1, 2, 1, 2], bag) + + bag = bag - 1 - 2 - 2 + check([1], bag) + + bag = bag.plusAll([2, 3]) + check([1, 2, 3], bag) + + bag = bag.minusAll([2, 1]) + check([3], bag) + } + + private void check(List answer, PBag bag) { + // toString, toArray + if (answer.size() == (answer as Set).size()) { + assert answer.toString() == bag.toString() + assert Arrays.equals(answer as Integer[], bag.toArray()) + } + + // equals, hashCode + assert HashTreePBag.from(answer) == bag + assert HashTreePBag.from(answer).hashCode() == bag.hashCode() + + // size, isEmpty + assert answer.size() == bag.size() + assert answer.isEmpty() == bag.isEmpty() + + // contains + for (int i = 0; i <= 4; i++) { + assert answer.contains(i) == bag.contains(i) + } + + // containsAll + assert bag.containsAll([]) + assert bag.containsAll(answer) + if (answer.size() >= 3) { + assert bag.containsAll([answer[0], answer.last()]) + } + + // Iterator + Iterator iterAnswer = answer.iterator() + Iterator iter = bag.iterator() + while (true) { + if (iterAnswer.hasNext()) { + assert iter.hasNext() + assert answer.contains(iter.next()) + iterAnswer.next() + } else { + shouldFail(NoSuchElementException) { + iter.next() + } + break + } + } + } + + @SuppressWarnings("GrDeprecatedAPIUsage") + void testUnsupportedOperation() { + shouldFail(UnsupportedOperationException) { + MapPBag.empty().add(1) + } + shouldFail(UnsupportedOperationException) { + MapPBag.empty().remove(1) + } + shouldFail(UnsupportedOperationException) { + MapPBag.empty().addAll([1, 2]) + } + shouldFail(UnsupportedOperationException) { + MapPBag.empty().removeAll([1, 2]) + } + shouldFail(UnsupportedOperationException) { + MapPBag.empty().retainAll([1, 2]) + } + shouldFail(UnsupportedOperationException) { + MapPBag.empty().clear() + } + shouldFail(UnsupportedOperationException) { + MapPBag.from([0]).iterator().remove() + } + } +} diff --git a/src/test/groovy/util/immutable/PStackTest.groovy b/src/test/groovy/util/immutable/PStackTest.groovy new file mode 100644 index 0000000000..07ec192dc0 --- /dev/null +++ b/src/test/groovy/util/immutable/PStackTest.groovy @@ -0,0 +1,300 @@ +/* + * Copyright 2003-2014 the original author or authors. + * + * 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 groovy.util.immutable + +import org.pcollections.ConsPStack +import org.pcollections.PStack + +/** + * @author Yu Kobayashi + */ +class PStackTest extends GroovyTestCase { + void testSupportedOperation() { + PStack stack = ConsPStack.empty() + check([], stack) + + stack = stack.minus((Object) 1) + check([], stack) + + stack += (Object) null + check([null], stack) + + stack = stack.minus((Object) null) + check([], stack) + + stack += new Integer(1) + check([1], stack) + + stack += new Integer(1) + check([1, 1], stack) + + stack = stack.minus((Object) 1) + check([1], stack) + + stack = stack.plusAll([2, 3]) + check([3, 2, 1], stack) + + stack = stack.minusAll([2, 1]) + check([3], stack) + + stack = stack.plusAll(0, [1, 2]) + check([2, 1, 3], stack) + + stack = stack.minus(0) + check([1, 3], stack) + + stack = stack.with(1, 4) + check([1, 4], stack) + } + + private void check(List answer, PStack list) { + assert answer == list as List + + // toString, toArray + assert answer.toString() == list.toString() + assert Arrays.equals(answer as Integer[], list.toArray()) + + // equals, hashCode + assert ConsPStack.from(answer) == list + assert ConsPStack.from(answer).hashCode() == list.hashCode() + + // size, isEmpty + assert answer.size() == list.size() + assert answer.isEmpty() == list.isEmpty() + + // getAt + for (int i = 0; i <= answer.size(); i++) { + assert answer[i] == list[i] + } + if (answer.size() > 0) { + assert answer[-1] == list[-1] + } + shouldFail(IndexOutOfBoundsException) { + list.get(-1) + } + shouldFail(IndexOutOfBoundsException) { + list.get(answer.size()) + } + + // contains, indexOf, lastIndexOf + for (int i = 0; i <= 4; i++) { + assert answer.contains(i) == list.contains(i) + assert answer.indexOf(i) == list.indexOf(i) + assert answer.lastIndexOf(i) == list.lastIndexOf(i) + } + + // containsAll + assert list.containsAll([]) + assert list.containsAll(answer) + if (answer.size() >= 3) { + assert list.containsAll([answer[0], answer.last()]) + } + + // subList + if (answer.size() >= 2) { + assert answer.subList(1, answer.size()) == list.subList(1) + } + if (answer.size() >= 3) { + assert answer.subList(1, 2) == list.subList(1, 2) + } + + // Iterator + Iterator iterAnswer = answer.iterator() + Iterator iter = list.iterator() + while (true) { + if (iterAnswer.hasNext()) { + assert iter.hasNext() + assert iterAnswer.next() == iter.next() + } else { + shouldFail(NoSuchElementException) { + iter.next() + } + break + } + } + + // ListIterator + checkListIterator(answer.listIterator(), list.listIterator()) + for (int i = 0; i < answer.size(); i++) { + checkListIterator(answer.listIterator(i), list.listIterator(i)) + } + shouldFail(IndexOutOfBoundsException) { + list.listIterator(-1) + } + shouldFail(IndexOutOfBoundsException) { + list.listIterator(answer.size() + 1) + } + } + + def void checkListIterator(ListIterator listIterAnswer, ListIterator listIter) { + while (true) { + if (listIterAnswer.hasNext()) { + assert listIter.hasNext() + assert listIterAnswer.nextIndex() == listIter.nextIndex() + assert listIterAnswer.previousIndex() == listIter.previousIndex() + assert listIterAnswer.next() == listIter.next() + } else { + shouldFail(NoSuchElementException) { + listIter.next() + } + break + } + } + while (true) { + if (listIterAnswer.hasPrevious()) { + assert listIter.hasPrevious() + assert listIterAnswer.nextIndex() == listIter.nextIndex() + assert listIterAnswer.previousIndex() == listIter.previousIndex() + assert listIterAnswer.previous() == listIter.previous() + } else { + shouldFail(NoSuchElementException) { + listIter.previous() + } + break + } + } + } + + @SuppressWarnings("GrDeprecatedAPIUsage") + void testUnsupportedOperation() { + shouldFail(UnsupportedOperationException) { + ConsPStack.empty().add(1) + } + shouldFail(UnsupportedOperationException) { + ConsPStack.empty().add(1, 1) + } + shouldFail(UnsupportedOperationException) { + ConsPStack.empty().remove(1) + } + shouldFail(UnsupportedOperationException) { + ConsPStack.empty().remove((Object) 1) + } + shouldFail(UnsupportedOperationException) { + ConsPStack.empty().addAll([1, 2]) + } + shouldFail(UnsupportedOperationException) { + ConsPStack.empty().addAll(1, [1, 2]) + } + shouldFail(UnsupportedOperationException) { + ConsPStack.empty().removeAll([1, 2]) + } + shouldFail(UnsupportedOperationException) { + ConsPStack.empty().retainAll([1, 2]) + } + shouldFail(UnsupportedOperationException) { + ConsPStack.empty().clear() + } + shouldFail(UnsupportedOperationException) { + ConsPStack.from([0]).iterator().remove() + } + shouldFail(UnsupportedOperationException) { + ConsPStack.from([0]).listIterator().remove() + } + shouldFail(UnsupportedOperationException) { + ConsPStack.from([0]).listIterator().set(null) + } + shouldFail(UnsupportedOperationException) { + ConsPStack.from([0]).listIterator().add(null) + } + shouldFail(UnsupportedOperationException) { + ConsPStack.from([0]).listIterator(0).remove() + } + shouldFail(UnsupportedOperationException) { + ConsPStack.from([0]).listIterator(0).set(null) + } + shouldFail(UnsupportedOperationException) { + ConsPStack.from([0]).listIterator(0).add(null) + } + } + + /** + * Compares the behavior of LinkedList to the behavior of ImmutableStack. + */ + public void testRandomlyAgainstJavaList() { + def pstack = ConsPStack.empty() + def list = new LinkedList() + def r = new Random() + for (int i = 0; i < 1000; i++) { + if (pstack.size() == 0 || r.nextBoolean()) { // add + if (r.nextBoolean()) { // append + int v = r.nextInt() + + assert list.contains(v) == pstack.contains(v) + + list.add(0, v) + pstack += new Integer(v) + } else { // insert + int k = r.nextInt(pstack.size() + 1) + int v = r.nextInt() + + assert list.contains(v) == pstack.contains(v) + if (k < pstack.size()) + assert list[k] == pstack[k] + + list.add(k, v); + pstack = pstack.plus(k, v) + } + } else if (r.nextBoolean()) { // replace + int k = r.nextInt(pstack.size()) + int v = r.nextInt() + list[k] = v + pstack = pstack.with(k, v) + } else { // remove a random element + int j = r.nextInt(pstack.size()) + int k = 0 + for (int e : pstack) { + assert list.contains(e) + assert pstack.contains(e) + assert e == pstack[k] + assert list[k] == pstack[k] + assert pstack == pstack.minus(k).plus(k, pstack[k]) + assert pstack == pstack.plus(k, 10).minus(k) + + if (k == j) { + list.remove(k) + pstack = pstack.minus(k) + k-- // indices are now smaller + j = -1 // don't remove again + } + k++ + } + } + + // also try to remove a _totally_ random value: + int v = r.nextInt() + assert list.contains(v) == pstack.contains(v) + list.remove((Object) v) + pstack = pstack.minus((Object) v) + + // and try out a non-Integer: + def s = v as String + assert !pstack.contains(v) + pstack = pstack.minus(s) + + assert list.size() == pstack.size() + assert list == pstack + + assert pstack == ConsPStack.from(pstack) + assert ConsPStack.empty() == pstack.minusAll(pstack) + assert pstack == ConsPStack.empty().plusAll(pstack.reverse()) + + assert pstack == ConsPStack.singleton(10).plusAll(1, pstack.reverse()).minus(0) + } + } +}