package com.wilutions.itol.db;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.function.Function;
/**
* Default implementation for interface Suggest.
*
* @param <T>
* Item type
*/
public class DefaultSuggest<T> implements Suggest<T> {
/**
* All items. Passed in the constructor.
*/
protected Collection<T> allItems;
/**
* Function used to build the string representation of T.
*/
protected Function<T, String> toStringFunction;
/**
* Constructor.
*
* @param allItems
* Collection of all items.
*/
public DefaultSuggest(Collection<T> allItems) {
this(allItems, (item) -> item.toString());
}
/**
* Constructor.
*
* @param allItems
* Collection of all items.
* @param toStringFunction
* Function used to build the string representation of T
*/
public DefaultSuggest(Collection<T> allItems, Function<T, String> toStringFunction) {
this.allItems = allItems != null ? allItems : new ArrayList<T>(0);
this.toStringFunction = toStringFunction;
}
/**
* Constructor.
*/
public DefaultSuggest() {
}
/**
* Find suggestions for given text. All items are returned that contain the
* given text in their return value of toString. Those items that start with
* the given text are ordered at the beginning of the returned collection.
*
* @param text
* Text
* @param max
* Maximum number of items to return.
* @return Collection of items.
*/
public Collection<T> find(String text, int max, Collection<T> ignoreHits) {
Collection<T> ret = allItems;
String textLC = text.toLowerCase();
if (textLC.isEmpty()) {
ret = new ArrayList<T>();
for (T item : allItems) {
if (ret.size() == max) break;
ret.add(item);
}
}
else {
ArrayList<T> matches = new ArrayList<T>();
if (ignoreHits != null) {
for (T t : allItems) {
if (ignoreHits.contains(t)) continue;
matches.add(t);
}
}
else {
matches.addAll(allItems);
}
Collections.sort(matches, new Comparator<T>() {
public int compare(T o1, T o2) {
String s1 = toStringFunction.apply(o1).toLowerCase();
String s2 = toStringFunction.apply(o2).toLowerCase();
int p1 = s1.indexOf(textLC);
int p2 = s2.indexOf(textLC);
p1 = makeCompareFromPosition(p1);
p2 = makeCompareFromPosition(p2);
int cmp = p1 - p2;
return cmp;
}
});
// Return only items that contain the text.
// Therefore, find the first item that does not contain the text.
int endIdx = 0;
for (; endIdx < matches.size(); endIdx++) {
T item = matches.get(endIdx);
if (toStringFunction.apply(item).toLowerCase().indexOf(textLC) < 0) {
break;
}
}
endIdx = Math.min(endIdx, max);
// Cut the list at the item that does not contain the text.
ret = matches.subList(0, endIdx);
}
return ret;
}
private int makeCompareFromPosition(int p) {
if (p == 0) {
// item starts with the given text.
// This item should be positioned at the beginning of the list.
}
else if (p > 0) {
// The item contains the text but does not start with it.
// Set p=1 since it does not matter where the text is found.
p = 1;
}
else if (p < 0) {
// The item does not contain the text.
// Move this item at the end of the list.
p = Integer.MAX_VALUE;
}
return p;
}
}