/*
* This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT).
*
* Copyright (c) JCThePants (www.jcwhatever.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.jcwhatever.nucleus.utils;
import com.google.common.collect.Multimap;
import com.jcwhatever.nucleus.utils.validate.IValidator;
import java.util.AbstractList;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.PriorityQueue;
import java.util.Set;
import javax.annotation.Nullable;
/**
* Collection utilities.
*/
public class CollectionUtils {
private CollectionUtils() {}
/**
* Search a collection for valid candidates using an {@link IValidator} to validate.
*
* @param searchCandidates The search candidates.
* @param validator The validator.
*/
public static <T> List<T> search(Collection<T> searchCandidates, IValidator<T> validator) {
PreCon.notNull(searchCandidates);
PreCon.notNull(validator);
List<T> result = new ArrayList<>(searchCandidates.size());
for (T candidate : searchCandidates) {
if (validator.isValid(candidate)) {
result.add(candidate);
}
}
return result;
}
/**
* Search a collection for elements that contain the specified text search term.
*
* <p>Collection is ordered by best matches. Best match is based on case sensitivity match,
* the index position of the text, and alphabetical sorting.</p>
*
* <p>The search term is matched against the elements {@link Object#toString} result.</p>
*
* @param candidates A collection of candidate elements to search.
* @param searchTerm The text to match.
*
* @param <T> The element type.
*/
public static <T> List<T> textSearch(Collection<T> candidates, String searchTerm) {
return textSearch(candidates, searchTerm, new ISearchTextGetter<T>() {
@Override
public String getText(T element) {
return element.toString();
}
});
}
/**
* Search a collection for elements that contain the specified text search term.
*
* <p>Collection is ordered by best matches. Best match is based on case sensitivity match,
* the index position of the text, and alphabetical sorting.</p>
*
* @param candidates A collection of candidate elements to search.
* @param searchTerm The text to match.
* @param textGetter A {@link ISearchTextGetter} to get text to match against from elements.
*
* @param <T> The element type.
*/
public static <T> List<T> textSearch(Collection<T> candidates, String searchTerm,
ISearchTextGetter<T> textGetter) {
PreCon.notNull(candidates);
PreCon.notNull(searchTerm);
PreCon.notNull(textGetter);
if (candidates.size() == 0)
return new ArrayList<>(0);
PriorityQueue<WeightedSearchResult<T>> queue = new PriorityQueue<>(candidates.size());
String caseSearch = searchTerm.toUpperCase();
for (T candidate : candidates) {
String text = textGetter.getText(candidate);
if (text == null)
continue;
int weight = text.indexOf(searchTerm);
if (weight == -1) {
String caseText = text.toUpperCase();
weight = caseText.indexOf(caseSearch);
if (weight == -1) {
continue;
}
else {
weight++; // increase weight of non-case matching
}
}
queue.add(new WeightedSearchResult<T>(weight, candidate, text));
}
List<T> results = new ArrayList<>(queue.size());
while (!queue.isEmpty()) {
results.add(queue.remove().item);
}
return results;
}
/**
* Removes all matching instances of a value from the specified
* {@link com.google.common.collect.Multimap}.
*
* @param multimap The multimap.
* @param value The value to remove.
*
* @param <K> The key type.
* @param <V> The value type.
*
* @return True if the {@link com.google.common.collect.Multimap} was modified.
*/
public static <K, V> boolean removeValue(Multimap<K, V> multimap, @Nullable Object value) {
return removeValue(multimap.entries(), value);
}
/**
* Removes all matching instances of a value from the specified
* {@link java.util.Map}.
*
* @param map The map.
* @param value The value to remove.
*
* @param <K> The key type.
* @param <V> The value type.
*
* @return True if the {@link java.util.Map} was modified.
*/
public static <K, V> boolean removeValue(Map<K, V> map, @Nullable Object value) {
return removeValue(map.entrySet(), value);
}
/**
* Removes all entries with matching specified value from the specified
* {@link java.util.Collection} of {@link java.util.Map.Entry}.
*
* @param entries The map.
* @param value The value to remove.
*
* @param <K> The key type.
* @param <V> The value type.
*
* @return True if the {@link Collection} was modified.
*/
public static <K, V> boolean removeValue(Collection<Entry<K, V>> entries, @Nullable Object value) {
PreCon.notNull(entries);
Iterator<Entry<K, V>> iterator = entries.iterator();
boolean isChanged = false;
while (iterator.hasNext()) {
Entry<? extends K, ? extends V> entry = iterator.next();
if ((value == null && entry.getValue() == null) ||
(value != null && value.equals(entry.getValue()))) {
iterator.remove();
isChanged = true;
}
}
return isChanged;
}
/**
* Removes all elements from the target collection that are not present
* in the retain collection. Useful if the removed elements are needed.
*
* @param target The target collection.
* @param retain The collection of items that must be retained.
*
* @param <T> The element type.
*
* @return A {@link java.util.List} of elements removed from the target.
*/
public static <T> List<T> retainAll(Collection<T> target, Collection<?> retain) {
List<T> removed = new ArrayList<>(Math.abs(target.size() - retain.size()));
Iterator<T> iterator = target.iterator();
while (iterator.hasNext()) {
T element = iterator.next();
if (!retain.contains(element)) {
removed.add(element);
iterator.remove();
}
}
return removed;
}
/**
* Removes all elements from the target collection that are not valid
* according to the supplied {@link IValidator}.
*
* @param target The target collection.
* @param validator The validator that will validate each element in the collection.
*
* @param <T> The element type.
*
* @return A {@link java.util.List} of elements removed from the target.
*/
public static <T> List<T> retainAll(Collection<T> target, IValidator<T> validator) {
List<T> removed = new ArrayList<>(10);
Iterator<T> iterator = target.iterator();
while (iterator.hasNext()) {
T element = iterator.next();
if (!validator.isValid(element)) {
removed.add(element);
iterator.remove();
}
}
return removed;
}
/**
* Wrap a {@link java.util.Collection} in an unmodifiable {@link java.util.List}.
* If the collection is already a {@link java.util.List} then it is cast, otherwise
* its elements are copied into a new {@link java.util.List}.
*
* @param collection The collection to wrap.
*
* @param <E> The collection element type.
*/
public static <E> List<E> unmodifiableList(Collection<E> collection) {
return collection instanceof List
? Collections.unmodifiableList((List<E>) collection)
: Collections.unmodifiableList(new ArrayList<E>(collection));
}
/**
* Get an empty unmodifiable {@link java.util.List}.
*
* @param <E> The collection element type.
*/
public static <E> List<E> unmodifiableList() {
@SuppressWarnings("unchecked")
List<E> list = (List<E>) UNMODIFIABLE_EMPTY_LIST;
return list;
}
/**
* Get an empty unmodifiable {@link java.util.List}.
*
* <p>Used when the empty signature method cannot be used.
* Prevents errors and warnings.</p>
*
* @param clazz The component type class.
*
* @param <E> The collection element type.
*/
public static <E> List<E> unmodifiableList(
@SuppressWarnings("unused ")Class<E> clazz) {
@SuppressWarnings("unchecked")
List<E> list = (List<E>) UNMODIFIABLE_EMPTY_LIST;
return list;
}
/**
* Wrap a {@link java.util.Collection} in an unmodifiable {@link java.util.Set}.
* If the collection is already a {@link java.util.Set} then it is cast, otherwise
* its elements are copied into a new {@link java.util.Set}.
*
* @param collection The collection to wrap.
*
* @param <E> The collection element type.
*/
public static <E> Set<E> unmodifiableSet(Collection<E> collection) {
return collection instanceof Set
? Collections.unmodifiableSet((Set<E>) collection)
: Collections.unmodifiableSet(new HashSet<E>(collection));
}
/**
* Get an empty unmodifiable {@link java.util.Set}.
*
* @param <E> The collection element type.
*/
public static <E> Set<E> unmodifiableSet() {
@SuppressWarnings("unchecked")
Set<E> set = (Set<E>)UNMODIFIABLE_EMPTY_SET;
return set;
}
/**
* Get an empty unmodifiable {@link java.util.Set}.
*
* <p>Convenience method to use when the empty signature method
* cannot be used. Prevents errors and warnings.</p>
*
* @param clazz The component type class.
*
* @param <E> The collection element type.
*/
public static <E> Set<E> unmodifiableSet(
@SuppressWarnings("unused")Class<E> clazz) {
@SuppressWarnings("unchecked")
Set<E> set = (Set<E>)UNMODIFIABLE_EMPTY_SET;
return set;
}
/**
* Interface used for {@link CollectionUtils#textSearch} method.
*/
public interface ISearchTextGetter<T> {
/**
* Get the text to match against from an element.
*
* @param element The element to get text from.
*/
String getText(T element);
}
private static class WeightedSearchResult<T> implements Comparable<WeightedSearchResult<T>> {
int weight;
T item;
String text;
WeightedSearchResult(int weight, T item, String text) {
this.weight = weight;
this.item = item;
this.text = text;
}
@Override
public int compareTo(WeightedSearchResult<T> o) {
int result = Integer.compare(weight, o.weight);
if (result == 0)
result = text.compareTo(o.text);
return result;
}
}
public static final List UNMODIFIABLE_EMPTY_LIST = new AbstractList() {
@Override
public int size() {
return 0;
}
@Override
public Object get(int index) {
throw new UnsupportedOperationException();
}
};
public static final Set UNMODIFIABLE_EMPTY_SET = new AbstractSet() {
@Override
public int size() {
return 0;
}
@Override
public Iterator iterator() {
return new Iterator() {
@Override
public boolean hasNext() {
return false;
}
@Override
public Object next() {
return null;
}
@Override
public void remove() {
}
};
}
};
}