/*
* Copyright (c) 2012, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.util;
import org.junit.Ignore;
import org.junit.Test;
import java.util.*;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
/**
* CollectionUtilTest
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: CollectionUtilTest.java,v 1.0 24.01.12 17:39 haraldk Exp$
*/
public class CollectionUtilTest {
private static final Object[] stringObjects = new Object[] {"foo", "bar", "baz"};
private static final Object[] integerObjects = new Object[] {1, 2, 3};
@Test
public void testMergeArraysObject() {
Object[] merged = (Object[]) CollectionUtil.mergeArrays(stringObjects, integerObjects);
assertArrayEquals(new Object[] {"foo", "bar", "baz", 1, 2, 3}, merged);
}
@Test
public void testMergeArraysObjectOffset() {
Object[] merged = (Object[]) CollectionUtil.mergeArrays(stringObjects, 1, 2, integerObjects, 2, 1);
assertArrayEquals(new Object[] {"bar", "baz", 3}, merged);
}
@Test(expected = IndexOutOfBoundsException.class)
public void testMergeArraysObjectBadOffset() {
CollectionUtil.mergeArrays(stringObjects, 4, 2, integerObjects, 2, 1);
}
@Test(expected = IndexOutOfBoundsException.class)
public void testMergeArraysObjectBadSecondOffset() {
CollectionUtil.mergeArrays(stringObjects, 1, 2, integerObjects, 4, 1);
}
@Test(expected = IndexOutOfBoundsException.class)
public void testMergeArraysObjectBadLength() {
CollectionUtil.mergeArrays(stringObjects, 1, 4, integerObjects, 2, 1);
}
@Test(expected = IndexOutOfBoundsException.class)
public void testMergeArraysObjectBadSecondLength() {
CollectionUtil.mergeArrays(stringObjects, 1, 2, integerObjects, 2, 2);
}
@Test(expected = IndexOutOfBoundsException.class)
public void testMergeArraysObjectNegativeOffset() {
CollectionUtil.mergeArrays(stringObjects, -1, 2, integerObjects, 2, 1);
}
@Test(expected = IndexOutOfBoundsException.class)
public void testMergeArraysObjectNegativeSecondOffset() {
CollectionUtil.mergeArrays(stringObjects, 1, 2, integerObjects, -1, 1);
}
@Test(expected = IndexOutOfBoundsException.class)
public void testMergeArraysObjectNegativeLength() {
CollectionUtil.mergeArrays(stringObjects, 1, -1, integerObjects, 2, 1);
}
@Test(expected = IndexOutOfBoundsException.class)
public void testMergeArraysObjectNegativeSecondLength() {
CollectionUtil.mergeArrays(stringObjects, 1, 2, integerObjects, 2, -1);
}
@Test
public void testMergeArraysObjectAssignable() {
Integer[] integers = {1, 2, 3}; // Integer assignable to Object
Object[] merged = (Object[]) CollectionUtil.mergeArrays(stringObjects, integers);
assertArrayEquals(new Object[] {"foo", "bar", "baz", 1, 2, 3}, merged);
}
@Test(expected = ArrayStoreException.class)
public void testMergeArraysObjectIllegalType() {
String[] strings = {"foo", "bar", "baz"};
Integer[] integers = {1, 2, 3}; // Integer not assignable to String
CollectionUtil.mergeArrays(strings, integers);
}
@Test(expected = ArrayStoreException.class)
public void testMergeArraysNativeIllegalType() {
char[] chars = {'a', 'b', 'c'};
int[] integers = {1, 2, 3}; // Integer not assignable to String
CollectionUtil.mergeArrays(chars, integers);
}
@Test
public void testMergeArraysNative() {
char[] chars = {'a', 'b', 'c'};
char[] more = {'x', 'y', 'z'};
char[] merged = (char[]) CollectionUtil.mergeArrays(chars, more);
assertArrayEquals(new char[] {'a', 'b', 'c', 'x', 'y', 'z'}, merged);
}
@Test
public void testSubArrayObject() {
String[] strings = CollectionUtil.subArray(new String[] {"foo", "bar", "baz", "xyzzy"}, 1, 2);
assertArrayEquals(new String[] {"bar", "baz"}, strings);
}
@Test
public void testSubArrayNative() {
int[] numbers = (int[]) CollectionUtil.subArray(new int[] {1, 2, 3, 4, 5}, 1, 3);
assertArrayEquals(new int[] {2, 3, 4}, numbers);
}
@Test(expected = IllegalArgumentException.class)
public void testEnumIteratorNull() {
CollectionUtil.iterator((Enumeration<Object>) null);
}
@Test
public void testEnumIterator() {
@SuppressWarnings("unchecked")
Iterator<String> iterator = CollectionUtil.iterator((Enumeration) new StringTokenizer("foo, bar, baz", ", "));
int count = 0;
for (Object stringObject : stringObjects) {
assertTrue(iterator.hasNext());
assertEquals(stringObject, iterator.next());
try {
iterator.remove();
fail("Enumeration has no remove method, iterator.remove() must throw exception");
}
catch (UnsupportedOperationException expected) {
}
count++;
}
assertEquals(3, count);
assertFalse(iterator.hasNext());
try {
iterator.next();
fail("Iterator has more elements than enumeration");
}
catch (NoSuchElementException expected) {
}
}
@Test(expected = IllegalArgumentException.class)
public void testArrayIteratorNull() {
CollectionUtil.iterator((Object[]) null);
}
@Test
public void testArrayIterator() {
Iterator<String> iterator = CollectionUtil.iterator(new String[] {"foo", "bar", "baz"});
int count = 0;
for (Object stringObject : stringObjects) {
assertTrue(iterator.hasNext());
assertEquals(stringObject, iterator.next());
try {
iterator.remove();
fail("Array have fixed length, iterator.remove() must throw exception");
}
catch (UnsupportedOperationException expected) {
}
count++;
}
assertEquals(3, count);
assertFalse(iterator.hasNext());
try {
iterator.next();
fail("Iterator has more elements than array");
}
catch (NoSuchElementException expected) {
}
}
@Test
public void testArrayListIterator() {
assertCorrectListIterator(CollectionUtil.iterator(new String[] {"foo", "bar", "baz"}), stringObjects);
}
@Test
public void testArrayListIteratorRange() {
assertCorrectListIterator(CollectionUtil.iterator(new String[] {"foo", "bar", "baz", "boo"}, 1, 2), new String[] {"bar", "baz"});
}
@Test
public void testArrayListIteratorSanityCheckArraysAsList() {
assertCorrectListIterator(Arrays.asList(new String[] {"foo", "bar", "baz"}).listIterator(), stringObjects);
}
@Test
public void testArrayListIteratorSanityCheckArraysAsListRange() {
// NOTE: sublist(fromInc, toExcl) vs iterator(start, length)
assertCorrectListIterator(Arrays.asList(new String[] {"foo", "bar", "baz", "boo"}).subList(1, 3).listIterator(0), new String[] {"bar", "baz"}, false, true);
}
@Test
public void testArrayListIteratorSanityCheckArraysList() {
assertCorrectListIterator(new ArrayList<String>(Arrays.asList(new String[] {"foo", "bar", "baz"})).listIterator(), stringObjects, true, true);
}
@Test
public void testArrayListIteratorSanityCheckArraysListRange() {
// NOTE: sublist(fromInc, toExcl) vs iterator(start, length)
assertCorrectListIterator(new ArrayList<String>(Arrays.asList(new String[] {"foo", "bar", "baz", "boo"})).subList(1, 3).listIterator(0), new String[] {"bar", "baz"}, true, true);
}
private void assertCorrectListIterator(ListIterator<String> iterator, final Object[] elements) {
assertCorrectListIterator(iterator, elements, false, false);
}
// NOTE: The test is can only test list iterators with a starting index == 0
private void assertCorrectListIterator(ListIterator<String> iterator, final Object[] elements, boolean skipRemove, boolean skipAdd) {
// Index is now "before 0"
assertEquals(-1, iterator.previousIndex());
assertEquals(0, iterator.nextIndex());
int count = 0;
for (Object element : elements) {
assertTrue("No next element for element '" + element + "' at index: " + count, iterator.hasNext());
assertEquals(count > 0, iterator.hasPrevious());
assertEquals(count, iterator.nextIndex());
assertEquals(count - 1, iterator.previousIndex());
assertEquals(element, iterator.next());
count++;
if (!skipRemove) {
try {
iterator.remove();
fail("Array has fixed length, iterator.remove() must throw exception");
}
catch (UnsupportedOperationException expected) {
}
// Verify cursor not moving
assertEquals(count, iterator.nextIndex());
assertEquals(count - 1, iterator.previousIndex());
}
// NOTE: AbstractlList.ListItr.add() moves cursor forward, even if backing list's add() throws Exception
// (coll) AbstractList.ListItr.add might corrupt iterator state if enclosing add throws
// See: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6533203, fixed in Java 7
if (!skipAdd && !("java.util.AbstractList$ListItr".equals(iterator.getClass().getName()) /* && isJava7OrLater() */)) {
try {
iterator.add("xyzzy");
fail("Array has fixed length, iterator.add() must throw exception");
}
catch (UnsupportedOperationException expected) {
}
// Verify cursor not moving
assertEquals(count, iterator.nextIndex());
assertEquals(count - 1, iterator.previousIndex());
}
// Set is supported
iterator.set(String.valueOf(count));
}
assertEquals(elements.length, count);
assertFalse(iterator.hasNext());
try {
iterator.next();
fail("Iterator has more elements than array");
}
catch (NoSuchElementException expected) {
}
// Index should now be "before last"
assertEquals(elements.length - 1, iterator.previousIndex());
assertEquals(elements.length, iterator.nextIndex());
for (int i = count; i > 0; i--) {
assertTrue("No previous element for element '" + elements[i - 1] + "' at index: " + (i - 1), iterator.hasPrevious());
assertEquals(i < elements.length, iterator.hasNext());
assertEquals(i - 1, iterator.previousIndex());
assertEquals(i, iterator.nextIndex());
assertEquals(String.valueOf(i), iterator.previous());
}
// Index should now be back "before 0"
assertEquals(-1, iterator.previousIndex());
assertEquals(0, iterator.nextIndex());
assertFalse(iterator.hasPrevious());
try {
iterator.previous();
fail("Iterator has more elements than array");
}
catch (NoSuchElementException expected) {
}
}
@Test(expected = IllegalArgumentException.class)
public void testArrayIteratorRangeNull() {
CollectionUtil.iterator(null, 0, 0);
}
@Test(expected = IllegalArgumentException.class)
public void testArrayIteratorRangeBadStart() {
CollectionUtil.iterator(stringObjects, stringObjects.length + 1, 2);
}
@Test(expected = IllegalArgumentException.class)
public void testArrayIteratorRangeBadLength() {
CollectionUtil.iterator(stringObjects, 1, stringObjects.length);
}
@Test
public void testArrayIteratorRange() {
Iterator<String> iterator = CollectionUtil.iterator(new String[] {"foo", "bar", "baz", "xyzzy"}, 1, 2);
for (int i = 1; i < 3; i++) {
assertTrue(iterator.hasNext());
assertEquals(stringObjects[i], iterator.next());
try {
iterator.remove();
fail("Array has no remove method, iterator.remove() must throw exception");
}
catch (UnsupportedOperationException expected) {
}
}
assertFalse(iterator.hasNext());
try {
iterator.next();
fail("Iterator has more elements than array range");
}
catch (NoSuchElementException expected) {
}
}
@Test(expected = IllegalArgumentException.class)
public void testReverseOrderNull() {
CollectionUtil.reverseOrder(null);
}
@Test
public void testReverseOrder() {
Comparator<String> naturalOrder = new NaturalOrder<String>();
Comparator<String> reverse = CollectionUtil.reverseOrder(naturalOrder);
assertNotNull(reverse);
assertEquals(0, naturalOrder.compare("foo", "foo"));
assertEquals(0, reverse.compare("foo", "foo"));
assertTrue(naturalOrder.compare("bar", "baz") < 0);
assertTrue(reverse.compare("bar", "baz") > 0);
assertTrue(naturalOrder.compare("baz", "bar") > 0);
assertTrue(reverse.compare("baz", "bar") < 0);
}
@Test
public void testReverseOrderRandomIntegers() {
Comparator<Integer> naturalOrder = new NaturalOrder<Integer>();
Comparator<Integer> reverse = CollectionUtil.reverseOrder(naturalOrder);
Random random = new Random(243249878l); // Stable "random" sequence
for (int i = 0; i < 65536; i++) {
// Verified to be ~ 50/50 lt/gt
int integer = random.nextInt();
int integerToo = random.nextInt();
assertEquals(0, reverse.compare(integer, integer));
assertEquals(0, reverse.compare(integerToo, integerToo));
int natural = naturalOrder.compare(integer, integerToo);
if (natural == 0) {
// Actually never hits, but eq case is tested above
assertEquals(0, reverse.compare(integer, integerToo));
}
else if (natural < 0) {
assertTrue(reverse.compare(integer, integerToo) > 0);
}
else {
assertTrue(reverse.compare(integer, integerToo) < 0);
}
}
}
@Ignore("For development only")
@Test
@SuppressWarnings({"UnusedDeclaration"})
public void testGenerify() {
List list = Collections.singletonList("foo");
@SuppressWarnings({"unchecked"})
Set set = new HashSet(list);
List<String> strs0 = CollectionUtil.generify(list, String.class);
List<Object> objs0 = CollectionUtil.generify(list, String.class);
// List<String> strs01 = CollectionUtil.generify(list, Object.class); // Not okay
try {
List<String> strs1 = CollectionUtil.generify(set, String.class); // Not ok, runtime CCE unless set is null
}
catch (RuntimeException e) {
e.printStackTrace();
}
try {
ArrayList<String> strs01 = CollectionUtil.generify(list, String.class); // Not ok, runtime CCE unless list is null
}
catch (RuntimeException e) {
e.printStackTrace();
}
Set<String> setstr1 = CollectionUtil.generify(set, String.class);
Set<Object> setobj1 = CollectionUtil.generify(set, String.class);
try {
Set<Object> setobj44 = CollectionUtil.generify(list, String.class); // Not ok, runtime CCE unless list is null
}
catch (RuntimeException e) {
e.printStackTrace();
}
List<String> strs2 = CollectionUtil.<List<String>, String>generify2(list);
List<Object> objs2 = CollectionUtil.<List<Object>, String>generify2(list);
// List<String> morestrs = CollectionUtil.<List<Object>, String>generify2(list); // Not ok
try {
List<String> strs3 = CollectionUtil.<List<String>, String>generify2(set); // Not ok, runtime CCE unless set is null
}
catch (RuntimeException e) {
e.printStackTrace();
}
}
private static class NaturalOrder<T extends Comparable<T>> implements Comparator<T> {
public int compare(T left, T right) {
return left.compareTo(right);
}
}
}