/*
* Copyright (c) 2008, 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 of the copyright holder 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 HOLDER 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 com.twelvemonkeys.lang.
Validate;
import java.lang.reflect.
Array;
import java.util.*;
import static com.twelvemonkeys.lang.
Validate.isTrue;
import static com.twelvemonkeys.lang.
Validate.notNull;
/**
* A utility class with some useful collection-related functions.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author <A href="mailto:eirik.torske@twelvemonkeys.no">Eirik Torske</A>
* @author last modified by $Author: haku $
* @version $Id: com/twelvemonkeys/util/CollectionUtil.java#3 $
* @see Collections
* @see Arrays
*/
public final class
CollectionUtil {
/**
* Testing only.
*
* @param pArgs command line arguents
*/
@
SuppressWarnings({"UnusedDeclaration", "UnusedAssignment", "unchecked"})
public static void
main(
String[]
pArgs) {
int
howMany = 1000;
if (
pArgs.length > 0) {
howMany =
Integer.
parseInt(
pArgs[0]);
}
long
start;
long
end;
/*
int[] intArr1 = new int[] {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
};
int[] intArr2 = new int[] {
10, 11, 12, 13, 14, 15, 16, 17, 18, 19
};
start = System.currentTimeMillis();
for (int i = 0; i < howMany; i++) {
intArr1 = (int[]) mergeArrays(intArr1, 0, intArr1.length, intArr2, 0, intArr2.length);
}
end = System.currentTimeMillis();
System.out.println("mergeArrays: " + howMany + " * " + intArr2.length + " ints took " + (end - start) + " milliseconds (" + intArr1.length
+ ")");
*/
////////////////////////////////
String[]
stringArr1 = new
String[]{
"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"
};
/*
String[] stringArr2 = new String[] {
"ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"
};
start = System.currentTimeMillis();
for (int i = 0; i < howMany; i++) {
stringArr1 = (String[]) mergeArrays(stringArr1, 0, stringArr1.length, stringArr2, 0, stringArr2.length);
}
end = System.currentTimeMillis();
System.out.println("mergeArrays: " + howMany + " * " + stringArr2.length + " Strings took " + (end - start) + " milliseconds ("
+ stringArr1.length + ")");
start = System.currentTimeMillis();
while (intArr1.length > stringArr2.length) {
intArr1 = (int[]) subArray(intArr1, 0, intArr1.length - stringArr2.length);
}
end = System.currentTimeMillis();
System.out.println("subArray: " + howMany + " * " + intArr2.length + " ints took " + (end - start) + " milliseconds (" + intArr1.length
+ ")");
start = System.currentTimeMillis();
while (stringArr1.length > stringArr2.length) {
stringArr1 = (String[]) subArray(stringArr1, stringArr2.length);
}
end = System.currentTimeMillis();
System.out.println("subArray: " + howMany + " * " + stringArr2.length + " Strings took " + (end - start) + " milliseconds ("
+ stringArr1.length + ")");
*/
stringArr1 = new
String[]{
"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
"fifteen", "sixteen", "seventeen", "eighteen", "nineteen"
};
System.
out.
println("\nFilterIterators:\n");
List list =
Arrays.
asList(
stringArr1);
Iterator iter = new
FilterIterator(
list.
iterator(), new
FilterIterator.
Filter() {
public boolean
accept(
Object pElement) {
return ((
String)
pElement).
length() > 5;
}
});
while (
iter.
hasNext()) {
String str = (
String)
iter.
next();
System.
out.
println(
str + " has more than 5 letters!");
}
iter = new
FilterIterator(
list.
iterator(), new
FilterIterator.
Filter() {
public boolean
accept(
Object pElement) {
return ((
String)
pElement).
length() <= 5;
}
});
while (
iter.
hasNext()) {
String str = (
String)
iter.
next();
System.
out.
println(
str + " has less than, or exactly 5 letters!");
}
start =
System.
currentTimeMillis();
for (int
i = 0;
i <
howMany;
i++) {
iter = new
FilterIterator(
list.
iterator(), new
FilterIterator.
Filter() {
public boolean
accept(
Object pElement) {
return ((
String)
pElement).
length() <= 5;
}
});
while (
iter.
hasNext()) {
iter.
next();
System.
out.
print("");
}
}
// end = System.currentTimeMillis();
// System.out.println("Time: " + (end - start) + " ms");
// System.out.println("\nClosureCollection:\n");
// forEach(list, new Closure() {
//
// public void execute(Object pElement) {
//
// String str = (String) pElement;
//
// if (str.length() > 5) {
// System.out.println(str + " has more than 5 letters!");
// }
// else {
// System.out.println(str + " has less than, or exactly 5 letters!");
// }
// }
// });
// start = System.currentTimeMillis();
// for (int i = 0; i < howMany; i++) {
// forEach(list, new Closure() {
//
// public void execute(Object pElement) {
//
// String str = (String) pElement;
//
// if (str.length() <= 5) {
// System.out.print("");
// }
// }
// });
// }
// end = System.currentTimeMillis();
// System.out.println("Time: " + (end - start) + " ms");
}
// Disallow creating objects of this type
private
CollectionUtil() {}
/**
* Merges two arrays into a new array. Elements from array1 and array2 will
* be copied into a new array, that has array1.length + array2.length
* elements.
*
* @param pArray1 First array
* @param pArray2 Second array, must be compatible with (assignable from)
* the first array
* @return A new array, containing the values of array1 and array2. The
* array (wrapped as an object), will have the length of array1 +
* array2, and can be safely cast to the type of the array1
* parameter.
* @see #mergeArrays(Object,int,int,Object,int,int)
* @see java.lang.System#arraycopy(Object,int,Object,int,int)
*/
public static
Object mergeArrays(
Object pArray1,
Object pArray2) {
return
mergeArrays(
pArray1, 0,
Array.
getLength(
pArray1),
pArray2, 0,
Array.
getLength(
pArray2));
}
/**
* Merges two arrays into a new array. Elements from pArray1 and pArray2 will
* be copied into a new array, that has pLength1 + pLength2 elements.
*
* @param pArray1 First array
* @param pOffset1 the offset into the first array
* @param pLength1 the number of elements to copy from the first array
* @param pArray2 Second array, must be compatible with (assignable from)
* the first array
* @param pOffset2 the offset into the second array
* @param pLength2 the number of elements to copy from the second array
* @return A new array, containing the values of pArray1 and pArray2. The
* array (wrapped as an object), will have the length of pArray1 +
* pArray2, and can be safely cast to the type of the pArray1
* parameter.
* @see java.lang.System#arraycopy(Object,int,Object,int,int)
*/
@
SuppressWarnings({"SuspiciousSystemArraycopy"})
public static
Object mergeArrays(
Object pArray1, int
pOffset1, int
pLength1,
Object pArray2, int
pOffset2, int
pLength2) {
Class class1 =
pArray1.
getClass();
Class type =
class1.
getComponentType();
// Create new array of the new length
Object array =
Array.
newInstance(
type,
pLength1 +
pLength2);
System.
arraycopy(
pArray1,
pOffset1,
array, 0,
pLength1);
System.
arraycopy(
pArray2,
pOffset2,
array,
pLength1,
pLength2);
return
array;
}
/**
* Creates an array containing a subset of the original array.
* If the sub array is same length as the original
* ({@code pStart == 0}), the original array will be returned.
*
* @param pArray the original array
* @param pStart the start index of the original array
* @return a subset of the original array, or the original array itself,
* if {@code pStart} is 0.
*
* @throws IllegalArgumentException if {@code pArray} is {@code null} or
* if {@code pArray} is not an array.
* @throws ArrayIndexOutOfBoundsException if {@code pStart} < 0
*/
public static
Object subArray(
Object pArray, int
pStart) {
return
subArray(
pArray,
pStart, -1);
}
/**
* Creates an array containing a subset of the original array.
* If the sub array is same length as the original
* ({@code pStart == 0}), the original array will be returned.
*
* @param pArray the original array
* @param pStart the start index of the original array
* @return a subset of the original array, or the original array itself,
* if {@code pStart} is 0.
*
* @throws IllegalArgumentException if {@code pArray} is {@code null}
* @throws ArrayIndexOutOfBoundsException if {@code pStart} < 0
*/
public static <T> T[]
subArray(T[]
pArray, int
pStart) {
return
subArray(
pArray,
pStart, -1);
}
/**
* Creates an array containing a subset of the original array.
* If the {@code pLength} parameter is negative, it will be ignored.
* If there are not {@code pLength} elements in the original array
* after {@code pStart}, the {@code pLength} parameter will be
* ignored.
* If the sub array is same length as the original, the original array will
* be returned.
*
* @param pArray the original array
* @param pStart the start index of the original array
* @param pLength the length of the new array
* @return a subset of the original array, or the original array itself,
* if {@code pStart} is 0 and {@code pLength} is either
* negative, or greater or equal to {@code pArray.length}.
*
* @throws IllegalArgumentException if {@code pArray} is {@code null} or
* if {@code pArray} is not an array.
* @throws ArrayIndexOutOfBoundsException if {@code pStart} < 0
*/
@
SuppressWarnings({"SuspiciousSystemArraycopy"})
public static
Object subArray(
Object pArray, int
pStart, int
pLength) {
Validate.
notNull(
pArray, "array");
// Get component type
Class type;
// Sanity check start index
if (
pStart < 0) {
throw new
ArrayIndexOutOfBoundsException(
pStart + " < 0");
}
// Check if argument is array
else if ((
type =
pArray.
getClass().
getComponentType()) == null) {
// NOTE: No need to test class.isArray(), really
throw new
IllegalArgumentException("Not an array: " +
pArray);
}
// Store original length
int
originalLength =
Array.
getLength(
pArray);
// Find new length, stay within bounds
int
newLength = (
pLength < 0)
?
Math.
max(0,
originalLength -
pStart)
:
Math.
min(
pLength,
Math.
max(0,
originalLength -
pStart));
// Store result
Object result;
if (
newLength <
originalLength) {
// Create sub array & copy into
result =
Array.
newInstance(
type,
newLength);
System.
arraycopy(
pArray,
pStart,
result, 0,
newLength);
}
else {
// Just return original array
// NOTE: This can ONLY happen if pStart == 0
result =
pArray;
}
// Return
return
result;
}
/**
* Creates an array containing a subset of the original array.
* If the {@code pLength} parameter is negative, it will be ignored.
* If there are not {@code pLength} elements in the original array
* after {@code pStart}, the {@code pLength} parameter will be
* ignored.
* If the sub array is same length as the original, the original array will
* be returned.
*
* @param pArray the original array
* @param pStart the start index of the original array
* @param pLength the length of the new array
* @return a subset of the original array, or the original array itself,
* if {@code pStart} is 0 and {@code pLength} is either
* negative, or greater or equal to {@code pArray.length}.
*
* @throws IllegalArgumentException if {@code pArray} is {@code null}
* @throws ArrayIndexOutOfBoundsException if {@code pStart} < 0
*/
@
SuppressWarnings("unchecked")
public static <T> T[]
subArray(T[]
pArray, int
pStart, int
pLength) {
return (T[])
subArray((
Object)
pArray,
pStart,
pLength);
}
public static <T>
Iterator<T>
iterator(final
Enumeration<T>
pEnum) {
notNull(
pEnum, "enumeration");
return new
Iterator<T>() {
public boolean
hasNext() {
return
pEnum.
hasMoreElements();
}
public T
next() {
return
pEnum.
nextElement();
}
public void
remove() {
throw new
UnsupportedOperationException();
}
};
}
/**
* Adds all elements of the iterator to the collection.
*
* @param pCollection the collection
* @param pIterator the elements to add
*
* @throws UnsupportedOperationException if {@code add} is not supported by
* the given collection.
* @throws ClassCastException class of the specified element prevents it
* from being added to this collection.
* @throws NullPointerException if the specified element is {@code null} and this
* collection does not support {@code null} elements.
* @throws IllegalArgumentException some aspect of this element prevents
* it from being added to this collection.
*/
public static <E> void
addAll(
Collection<E>
pCollection,
Iterator<? extends E>
pIterator) {
while (
pIterator.
hasNext()) {
pCollection.
add(
pIterator.
next());
}
}
// Is there a use case where Arrays.asList(pArray).iterator() can't ne used?
/**
* Creates a thin {@link Iterator} wrapper around an array.
*
* @param pArray the array to iterate
* @return a new {@link ListIterator}
* @throws IllegalArgumentException if {@code pArray} is {@code null},
* {@code pStart < 0}, or
* {@code pLength > pArray.length - pStart}
*/
public static <E>
ListIterator<E>
iterator(final E[]
pArray) {
return
iterator(
pArray, 0,
notNull(
pArray).length);
}
/**
* Creates a thin {@link Iterator} wrapper around an array.
*
* @param pArray the array to iterate
* @param pStart the offset into the array
* @param pLength the number of elements to include in the iterator
* @return a new {@link ListIterator}
* @throws IllegalArgumentException if {@code pArray} is {@code null},
* {@code pStart < 0}, or
* {@code pLength > pArray.length - pStart}
*/
public static <E>
ListIterator<E>
iterator(final E[]
pArray, final int
pStart, final int
pLength) {
return new
ArrayIterator<E>(
pArray,
pStart,
pLength);
}
/**
* Creates an inverted mapping of the key/value pairs in the given map.
*
* @param pSource the source map
* @return a new {@code Map} of same type as {@code pSource}
* @throws IllegalArgumentException if {@code pSource == null},
* or if a new map can't be instantiated,
* or if source map contains duplicates.
*
* @see #invert(java.util.Map, java.util.Map, DuplicateHandler)
*/
public static <K, V>
Map<V, K>
invert(
Map<K, V>
pSource) {
return
invert(
pSource, null, null);
}
/**
* Creates an inverted mapping of the key/value pairs in the given map.
* Optionally, a duplicate handler may be specified, to resolve duplicate keys in the result map.
*
* @param pSource the source map
* @param pResult the map used to contain the result, may be {@code null},
* in that case a new {@code Map} of same type as {@code pSource} is created.
* The result map <em>should</em> be empty, otherwise duplicate values will need to be resolved.
* @param pHandler duplicate handler, may be {@code null} if source map don't contain duplicate values
* @return {@code pResult}, or a new {@code Map} if {@code pResult == null}
* @throws IllegalArgumentException if {@code pSource == null},
* or if result map is {@code null} and a new map can't be instantiated,
* or if source map contains duplicate values and {@code pHandler == null}.
*/
// TODO: Create a better duplicate handler, that takes Entries as parameters and returns an Entry
public static <K, V>
Map<V, K>
invert(
Map<K, V>
pSource,
Map<V, K>
pResult,
DuplicateHandler<K>
pHandler) {
if (
pSource == null) {
throw new
IllegalArgumentException("source == null");
}
Map<V, K>
result =
pResult;
if (
result == null) {
try {
//noinspection unchecked
result =
pSource.
getClass().
newInstance();
}
catch (
InstantiationException e) {
// Handled below
}
catch (
IllegalAccessException e) {
// Handled below
}
if (
result == null) {
throw new
IllegalArgumentException("result == null and source class " +
pSource.
getClass() + " cannot be instantiated.");
}
}
// Copy entries into result map, inversed
Set<
Map.
Entry<K, V>>
entries =
pSource.
entrySet();
for (
Map.
Entry<K, V>
entry :
entries) {
V
newKey =
entry.
getValue();
K
newValue =
entry.
getKey();
// Handle dupliates
if (
result.
containsKey(
newKey)) {
if (
pHandler != null) {
newValue =
pHandler.
resolve(
result.
get(
newKey),
newValue);
}
else {
throw new
IllegalArgumentException("Result would include duplicate keys, but no DuplicateHandler specified.");
}
}
result.
put(
newKey,
newValue);
}
return
result;
}
public static <T>
Comparator<T>
reverseOrder(final
Comparator<T>
pOriginal) {
return new
ReverseComparator<T>(
pOriginal);
}
private static class
ReverseComparator<T> implements
Comparator<T> {
private final
Comparator<T>
comparator;
public
ReverseComparator(final
Comparator<T>
pComparator) {
comparator =
notNull(
pComparator);
}
public int
compare(T
pLeft, T
pRight) {
int
result =
comparator.
compare(
pLeft,
pRight);
// We can't simply return -result, as -Integer.MIN_VALUE == Integer.MIN_VALUE.
return -(
result | (
result >>> 1));
}
}
@
SuppressWarnings({"unchecked", "UnusedDeclaration"})
static <T extends
Iterator<? super E>, E> T
generify(final
Iterator<?>
pIterator, final
Class<E>
pElementType) {
return (T)
pIterator;
}
@
SuppressWarnings({"unchecked", "UnusedDeclaration"})
static <T extends
Collection<? super E>, E> T
generify(final
Collection<?>
pCollection, final
Class<E>
pElementType) {
return (T)
pCollection;
}
@
SuppressWarnings({"unchecked", "UnusedDeclaration"})
static <T extends
Map<? super K, ? super V>, K, V> T
generify(final
Map<?, ?>
pMap, final
Class<K>
pKeyType, final
Class<V>
pValueType) {
return (T)
pMap;
}
@
SuppressWarnings({"unchecked"})
static <T extends
Collection<? super E>, E> T
generify2(
Collection<?>
pCollection) {
return (T)
pCollection;
}
private static class
ArrayIterator<E> implements
ListIterator<E> {
private int
next;
private final int
start;
private final int
length;
private final E[]
array;
public
ArrayIterator(final E[]
pArray, final int
pStart, final int
pLength) {
array =
notNull(
pArray, "array");
start =
isTrue(
pStart >= 0,
pStart, "start < 0: %d");
length =
isTrue(
pLength <=
pArray.length -
pStart,
pLength, "length > array.length - start: %d");
next =
start;
}
public boolean
hasNext() {
return
next <
length +
start;
}
public E
next() {
if (!
hasNext()) {
throw new
NoSuchElementException();
}
try {
return
array[
next++];
}
catch (
ArrayIndexOutOfBoundsException e) {
NoSuchElementException nse = new
NoSuchElementException(
e.
getMessage());
nse.
initCause(
e);
throw
nse;
}
}
public void
remove() {
throw new
UnsupportedOperationException();
}
public void
add(E
pElement) {
throw new
UnsupportedOperationException();
}
public boolean
hasPrevious() {
return
next >
start;
}
public int
nextIndex() {
return
next -
start;
}
public E
previous() {
if (!
hasPrevious()) {
throw new
NoSuchElementException();
}
try {
return
array[--
next];
}
catch (
ArrayIndexOutOfBoundsException e) {
NoSuchElementException nse = new
NoSuchElementException(
e.
getMessage());
nse.
initCause(
e);
throw
nse;
}
}
public int
previousIndex() {
return
nextIndex() - 1;
}
public void
set(E
pElement) {
array[
next - 1] =
pElement;
}
}
}