/* * IncrementalSearchModel.java * * Copyright (C) 2009 Leo Osvald <leo.osvald@gmail.com> * * This file is part of SGLJ. * * SGLJ is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * SGLJ is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see <http://www.gnu.org/licenses/>. */ package org.sglj.search; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; import javax.swing.event.EventListenerList; import org.sglj.util.OccurrenceMap; import org.sglj.util.PATTrie; import org.sglj.util.SetUtils; /** * Model which provides incremental search. * * @author Leo Osvald * @version 0.99 * * @param <E> type of data which is incrementally searched */ public abstract class IncrementalSearchModel<E> implements SortedSet<E>, Serializable { private static final long serialVersionUID = 1L; protected PATTrie<E> trie; protected TreeSet<E> addedResults; protected TreeSet<E> removedResults; protected Comparator<? super E> resultComparator; /** mapa trenutnih rezultata po broju keyworda koji se matchaju*/ private OccurrenceMap<E, TreeMap<E, Integer> > resultMap; private String lastPrefix; private E currResult; private TreeSet<E> searchSpace; private Collection<E> addedList; private Collection<E> removedList; private IncrementalSearchEvent<E> event; private EventListenerList listeners = new EventListenerList(); public IncrementalSearchModel(Comparator<E> resultComparator) { this.resultComparator = resultComparator; resultMap = new OccurrenceMap<E, TreeMap<E,Integer> >( new TreeMap<E, Integer>(resultComparator)); searchSpace = new TreeSet<E>(resultComparator); addedResults = new TreeSet<E>(resultComparator); removedResults = new TreeSet<E>(resultComparator); addedList = new ArrayList<E>(); removedList = new ArrayList<E>(); trie = new PATTrie<E>(addedList, removedList); resetFind(); } public Set<E> getSearchSpace() { return searchSpace; } private void startSession() { addedResults.clear(); removedResults.clear(); addedList.clear(); removedList.clear(); } private boolean sessionedPut(E clazz) { String[] criteria = searchBy(clazz); boolean hasChanged = false; for(int i = 0; i < criteria.length; ++i) if(trie.put(criteria[i], clazz)) { if(lastPrefix != null && criteria[i].startsWith(lastPrefix)) { if(addedList != null) addedList.add(clazz); } hasChanged = true; } if(hasChanged) searchSpace.add(clazz); return hasChanged; } private boolean sessionedRemove(E clazz) { // if(!searchSpace.contains(clazz)) return false; String[] criteria = searchBy(clazz); boolean hasChanged = false; for(int i = 0; i < criteria.length; ++i) if(trie.remove(criteria[i], clazz)) { if(lastPrefix != null && criteria[i].startsWith(lastPrefix)) { if(removedList != null) removedList.add(clazz); } hasChanged = true; } if(hasChanged) searchSpace.remove(clazz); return hasChanged; } public boolean myRemoveAll(Collection<E> searchSpace) { startSession(); boolean hasChanged = false; List<E> list = new ArrayList<E>(searchSpace); for(E curr : list) hasChanged |= sessionedRemove(curr); if(!removedList.isEmpty()) { removedResults.addAll(removedList); for(E curr : removedList) resultMap.decrement(curr); fireResultsRemoved(); fireResultsChanged(); } return hasChanged; } public boolean myRetainAll(Collection<E> searchSpace) { Set<E> toRemove = SetUtils.difference(this.searchSpace, searchSpace, new TreeSet<E>(comparator())); //izbrisi ono sto vise ne postoji u novom search spaceu return removeAll(toRemove); } public boolean containsKey(String key) { return trie.getValues(key) != null; } @Deprecated public TreeSet<E> find(E clazz) throws IllegalStateException { throw new IllegalStateException("find is depracated"); } public boolean continueFind(String s) { startSession(); boolean wasContinued = trie.continueFindPrefix(s, lastPrefix, addedList, removedList); if(wasContinued) { if(!addedList.isEmpty()) { for(E curr : addedList) { if(resultMap.increment(curr)) addedResults.add(curr); } fireResultsAdded(); fireResultsChanged(); } else if(!removedList.isEmpty()) { for(E curr : removedList) { if(resultMap.decrement(curr)) removedResults.remove(curr); } fireResultsRemoved(); fireResultsChanged(); } System.out.println("Continued - Added: " + addedResults.size() + "Removed: " + removedResults.size()); } else { resultMap.getMap().clear(); if(!addedList.isEmpty()) { for(E curr : addedList) resultMap.increment(curr); } System.out.println("new find: " + resultMap.getMap().size()); fireResultsChanged(); } //zapamti zadnji prefix lastPrefix = s; return wasContinued; } private void resetSearch() { lastPrefix = ""; startSession(); } public void resetFind() { resultMap.getMap().clear(); resetSearch(); ArrayList<E> allResults = new ArrayList<E>(); trie.findPrefix("", allResults); for(E curr : allResults) resultMap.increment(curr); rewind(); fireResultsChanged(); } public Set<E> getResultSet() { return resultMap.getMap().keySet(); } public TreeSet<E> getAddedResults() { return addedResults; } public TreeSet<E> getRemovedResults() { return removedResults; } public boolean next() { currResult = resultMap.getMap().higherKey(currResult); if(currResult != null) return true; currResult = resultMap.getMap().firstKey(); return false; } public boolean previous() { currResult = resultMap.getMap().lowerKey(currResult); if(currResult != null) return true; currResult = resultMap.getMap().lastKey(); return false; } public E getCurrent() { return currResult; } public void rewind() { if(resultMap.getMap().isEmpty()) currResult = null; else currResult = resultMap.getMap().firstKey(); } public abstract String[] searchBy(E clazz); public void setResultComparator(Comparator<? super E> resultComparator) { this.resultComparator = resultComparator; addedResults = SetUtils.changeComparator(addedResults, resultComparator); removedResults = SetUtils.changeComparator(removedResults, resultComparator); OccurrenceMap<E, TreeMap<E,Integer> > nextResultMap = new OccurrenceMap<E, TreeMap<E, Integer> >( new TreeMap<E, Integer>(resultComparator)); nextResultMap.getMap().putAll(resultMap.getMap()); resultMap.getMap().clear(); resultMap = nextResultMap; fireResultsChanged(); } public void reverseResultOrder() { setResultComparator(SetUtils.reverseComparator( resultMap.getMap().comparator())); } public void addIncrementalFinderEventListener(IncrementalSearchModelListener<E> l) { listeners.add(IncrementalSearchModelListener.class, l); } @SuppressWarnings("unchecked") public void fireResultsChanged() { // System.out.println("Results changed - count: " + resultMap.getMap().size()); Object[] listeners = this.listeners.getListenerList(); for (int i = listeners.length-2; i>=0; i-=2) { if (listeners[i]==IncrementalSearchModelListener.class) { if (event == null) event = new IncrementalSearchEvent<E>(this); event.setModel(this); ((IncrementalSearchModelListener<E>)listeners[i+1]) .resultsChanged(event); } } } @SuppressWarnings("unchecked") public void fireResultsAdded() { // System.out.println("New results added"); // for(T curr : addedResults) { // if(curr instanceof ProjectAccess) { // ProjectAccess pa = (ProjectAccess) curr; // System.out.println(pa.getName() + "@" + pa.getOwner()); // } // } Object[] listeners = this.listeners.getListenerList(); for (int i = listeners.length-2; i>=0; i-=2) { if (listeners[i]==IncrementalSearchModelListener.class) { if (event == null) event = new IncrementalSearchEvent<E>(this); event.setModel(this); ((IncrementalSearchModelListener<E>)listeners[i+1]) .resultsAdded(event); } } } @SuppressWarnings("unchecked") public void fireResultsRemoved() { Object[] listeners = this.listeners.getListenerList(); for (int i = listeners.length-2; i>=0; i-=2) { if (listeners[i]==IncrementalSearchModelListener.class) { if (event == null) event = new IncrementalSearchEvent<E>(this); event.setModel(this); ((IncrementalSearchModelListener<E>)listeners[i+1]) .resultsRemoved(event); } } } public List<E> findByKey(String s) { List<E> list = new ArrayList<E>(); trie.findPrefix(s, list); Collections.sort(list, resultComparator); return list; } public boolean putWithKey(String key, E value) { return trie.put(key, value); } public int nodeCount() { return trie.nodeCount(); } @Override public E first() { return searchSpace.first(); } @Override public SortedSet<E> headSet(E toElement) { return searchSpace.headSet(toElement); } @Override public E last() { return searchSpace.last(); } @Override public SortedSet<E> subSet(E fromElement, E toElement) { return searchSpace.subSet(fromElement, toElement); } @Override public SortedSet<E> tailSet(E fromElement) { return searchSpace.tailSet(fromElement); } @Override public boolean add(E clazz) { startSession(); boolean hasChanged = sessionedPut(clazz); if(!addedList.isEmpty()) { addedResults.addAll(addedList); for(E curr : addedList) resultMap.increment(curr); fireResultsAdded(); fireResultsChanged(); } return hasChanged; } @Override public boolean addAll(Collection<? extends E> c) { boolean hasChanged = false; startSession(); for(E curr : c) hasChanged |= sessionedPut(curr); if(!addedList.isEmpty()) { addedResults.addAll(addedList); for(E curr : addedList) resultMap.increment(curr); fireResultsAdded(); fireResultsChanged(); } return hasChanged; } @Override public void clear() { trie.clear(); searchSpace.clear(); addedResults.clear(); removedResults.clear(); fireResultsChanged(); } @Override public Comparator<? super E> comparator() { return resultComparator; } @Override public boolean contains(Object clazz) { return searchSpace.contains(clazz); } @Override public boolean containsAll(Collection<?> c) { return searchSpace.containsAll(c); } @Override public boolean isEmpty() { return trie.isEmpty(); } @Override public Iterator<E> iterator() { return searchSpace.iterator(); } @SuppressWarnings("unchecked") @Override public boolean remove(Object clazz) { if(!searchSpace.contains(clazz)) return false; startSession(); boolean hasChanged = sessionedRemove((E)clazz); if(!removedList.isEmpty()) { removedResults.addAll(removedList); for(E curr : removedList) resultMap.decrement(curr); fireResultsRemoved(); fireResultsChanged(); } return hasChanged; } @SuppressWarnings("unchecked") @Override public boolean removeAll(Collection<?> c) { return myRemoveAll((Collection<E>)c); } @SuppressWarnings("unchecked") @Override public boolean retainAll(Collection<?> c) { return myRetainAll((Collection<E>)c); } @Override public int size() { return trie.size(); } @Override public Object[] toArray() { return searchSpace.toArray(); } @SuppressWarnings("unchecked") @Override public <T> T[] toArray(T[] a) { return (T[])searchSpace.toArray(); } }