package helpers; 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.Set; /** * New mapping tried to implement * a simple Suffix Array. * Allows users to make substring searches. * i.e. if a mapping has the key "bananach" * search on for example anac will result in a hit. * * * * @author Quicksilver * */ public class SubStringMap3<V> implements ISearchMap<V> { private final int minimumlength; private final ISubstringMapping2<V> mapping; private boolean sorted = true; private final ArrayList<Node> all = new ArrayList<Node>(); /** * creates mapping that maps items in the way specified.. * by the provided mapping */ public SubStringMap3(ISubstringMapping2<V> mapping) { this.minimumlength = 3; this.mapping = mapping; } /** * creates substring map that simply maps items to their toString result * */ public SubStringMap3() { this(new ISubstringMapping2<V>() { public String getMappingString(V item) { return item.toString(); } }); } /* (non-Javadoc) * @see helpers.ISearchMap#put(V) */ public synchronized void put(V toMap) { if (toMap == null) { throw new IllegalArgumentException("argument toMap is null"); } String text = mapping.getMappingString(toMap); if (text.length() >= Short.MAX_VALUE ) { throw new IllegalArgumentException("too long String"); } sorted = false; for (short i = 0,max =(short) (text.length() - minimumlength+1); i < max; i++) { all.add(new Node(i,toMap)); } } /* (non-Javadoc) * @see helpers.ISearchMap#search(java.lang.String) */ public Set<V> search(String s) { return search(Collections.singleton(s)); //return search(Collections.singleton(s),Collections.<String>emptySet(),null); } private void prepareForSearch() { Collections.sort(all); List<Node> newList = new ArrayList<Node>(); List<Node> currentList = new ArrayList<Node>(); String current = ""; for (Node n : all) { String nString = n.toString(); if (current.equals(nString)) { currentList.add(n); } else { if (currentList.size() > 1 ) { newList.add(createFatNode(currentList)); } else if (!currentList.isEmpty()) { newList.add(currentList.get(0)); } currentList.clear(); currentList.add(n); current = nString; } } all.clear(); all.addAll(newList); all.trimToSize(); } /* (non-Javadoc) * @see helpers.ISearchMap#search(java.util.Set) */ public Set<V> search(Set<String> searchStrings) { return search(searchStrings,Collections.<String>emptySet(),new IFilter<V>() { //empty filter.. public boolean filter(V item) { return true; } public Set<V> mapItems(Set<V> nodeItems) { return nodeItems; }}); } /* (non-Javadoc) * @see helpers.ISearchMap#search(java.util.Set, java.util.Set, helpers.IFilter) */ public Set<V> search(Set<String> searchStrings,Set<String> excludes,IFilter<V> filter) { synchronized(this) { if (!sorted) { prepareForSearch(); sorted = true; } } List<String> searches = new ArrayList<String>(); //normalise the strings and remove doublets for (String s:searchStrings) { String normS = normalize(s).trim(); if (!GH.isEmpty(normS) && !searches.contains(normS)) { searches.add(normS); } } Set<V> current = null; //do the searches.. for (String s:searches) { Set<V> found = getMatching(s); found = filter.mapItems(found); if (current != null) { found.retainAll(current); } else { //remove filtered items for (Iterator<V> it = found.iterator(); it.hasNext();) { if (!filter.filter(it.next())) { it.remove(); } } } current = found; if (current.isEmpty()) { break; } } if (current == null) { current = Collections.<V>emptySet(); } //remove all excludes ... for (String exclude : excludes) { Set<V> found = getMatching(exclude); found = filter.mapItems(found); current.removeAll(found); } return current; } /** * normalises a string so it can be used for searching * @param unnormalized * @return */ public static String normalize(String unnormalized) { return unnormalized.toLowerCase(); } private String getMapping(V v) { return normalize(mapping.getMappingString(v)); } /** * * @param s * @return */ private Set<V> getMatching(String s) { int found = binarySearch(all,s); if (found < 0) { return Collections.<V>emptySet(); } else { Set<V> matching = new HashSet<V>(); all.get(found).addAll(matching); for (int up = found+1; up < all.size() ; up++) { if (all.get(up).matches(s)) { all.get(up).addAll(matching); } else { break; } } for (int down = found-1; down >= 0 ; down--) { if (all.get(down).matches(s)) { all.get(down).addAll(matching); } else { break; } } return matching; } } /** * * copied from java Arrays.. does a binary Search on a List */ private int binarySearch(List<Node> a, String key) { return binarySearch0(a, 0, a.size(), key); } //Like public version, but without range checks. private int binarySearch0(List<Node> a, int fromIndex, int toIndex, String key) { int low = fromIndex; int high = toIndex - 1; while (low <= high) { int mid = (low + high) >>> 1; Node midVal = (Node)a.get(mid); // a[mid]; int cmp = midVal.compareTo(key); if (cmp < 0) low = mid + 1; else if (cmp > 0) high = mid - 1; else return mid; // key found } return -(low + 1); // key not found. } private class Node implements Comparable<Node> { private final short beginIndex; private final V mapped; public Node(short beginIndex, V mapped) { this.beginIndex = beginIndex; this.mapped = mapped; } public int compareTo(Node o) { String own = toString(); String other = o.toString(); return own.compareTo(other); } public int compareTo(String o) { String own = toString(); if (own.length() > o.length()) { own = own.substring(0, o.length()); } return own.compareTo(o); } public boolean matches(String s) { return toString().startsWith(s); } public void addAll(Set<V> where) { where.add(mapped); } public String toString() { return getMapping(mapped).substring(beginIndex); } } public FatNode createFatNode(List<Node> nodes) { if (nodes.size() < 1) { throw new IllegalArgumentException("bad number of nodes"); } Node first= nodes.get(0); Set<V> items = new HashSet<V>(); for (Node n: nodes) { n.addAll(items); } items.remove(first.mapped); return new FatNode(first.beginIndex,first.mapped, items); } private class FatNode extends Node { private final Object[] moreItems; public FatNode(short beginIndex, V mapped,Collection<V> other) { super(beginIndex, mapped); moreItems = other.toArray(); } @SuppressWarnings("unchecked") @Override public void addAll(Set<V> where) { super.addAll(where); for (Object o:moreItems) { where.add((V)o); } } } }