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 0000000000..73e789a7a9 Binary files /dev/null and b/lib/pcollections-2.2.0-SNAPSHOT.jar differ diff --git a/src/main/groovy/transform/Immutable.java b/src/main/groovy/transform/Immutable.java index 5ab27dc8eb..18119c3752 100644 --- a/src/main/groovy/transform/Immutable.java +++ b/src/main/groovy/transform/Immutable.java @@ -72,7 +72,7 @@ *
  • {@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) + } + } +}