001/*
002 *                    BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 *
020 */
021
022package org.biojava.bio.search;
023
024import java.util.ArrayList;
025import java.util.HashMap;
026import java.util.Iterator;
027import java.util.List;
028import java.util.Map;
029
030import org.biojava.bio.BioException;
031import org.biojava.utils.TriState;
032import org.biojava.utils.walker.Visitor;
033import org.biojava.utils.walker.Walker;
034import org.biojava.utils.walker.WalkerFactory;
035
036public class FilteringContentHandler
037    extends SearchContentAdapter
038    implements BlastLikeSearchFilter.Node 
039{
040    // these store properties
041    private Map searchProperty = new HashMap();
042    private Map hitProperty = new HashMap();
043    private Map subHitProperty = new HashMap();
044
045    // these store the filter objects
046    private List searchFilters = new ArrayList();
047    private List hitFilters = new ArrayList();
048    private List subHitFilters = new ArrayList();
049
050    // these keep track of what this class has issued
051    // to its downstream filters.
052    private boolean emittedStartSearch;
053    private boolean emittedStartHit;
054
055    // these determine whether to skip the lower ranked levels
056    private boolean skipHits =false;
057    private boolean skipSubHits = false;
058
059    // these keep track of whether the test for the outcome at a certain level has
060    // been done.
061    private boolean firstSubHit = true;
062    private boolean firstHit = true;    // first hit in a search
063
064    public Object getSearchProperty(Object key) { return searchProperty.get(key); }
065    public Object getHitProperty(Object key) { return hitProperty.get(key); }
066    public Object getSubHitProperty(Object key) { return subHitProperty.get(key); }
067
068    private static final WalkerFactory walkerFactory = WalkerFactory.getInstance(BlastLikeSearchFilter.class);
069
070    /**
071     * Visitor class that parses the filter tree
072     */
073    public class FilterVisitor
074        implements Visitor
075    {
076        public void bySearchProperty(BlastLikeSearchFilter.BySearchProperty sf)
077        {
078            // register this instance
079            searchFilters.add(sf);
080        }
081
082        public void byHitProperty(BlastLikeSearchFilter.ByHitProperty sf)
083        {
084            hitFilters.add(sf);
085        }
086
087        public void bySubHitProperty(BlastLikeSearchFilter.BySubHitProperty sf)
088        {
089            subHitFilters.add(sf);
090        }
091    }
092
093    private BlastLikeSearchFilter filter;
094    private SearchContentHandler delegate;
095
096    public FilteringContentHandler(BlastLikeSearchFilter filter, SearchContentHandler delegate)
097        throws BioException
098    {
099        construct(filter);
100        setSearchContentHandler(delegate);
101    }
102
103    public FilteringContentHandler(BlastLikeSearchFilter filter)
104        throws BioException
105    {
106        construct(filter);
107    }
108
109    private void construct(BlastLikeSearchFilter filter)
110        throws BioException
111    {
112        this.filter = filter;
113
114        FilterVisitor visitor = new FilterVisitor();
115        Walker walker = walkerFactory.getWalker(visitor);
116        walker.walk(filter, visitor);
117    }
118
119    public void setSearchContentHandler(SearchContentHandler delegate)
120    {
121        this.delegate = delegate;
122    }
123
124    public void startHeader()
125    {
126        delegate.startHeader();
127    }
128
129    public void setDatabaseID(String id)
130    {
131        delegate.setDatabaseID(id);
132    }
133
134    public void endHeader()
135    {
136        delegate.endHeader();
137    }
138
139    public void startSearch()
140    {
141        emittedStartSearch = false;
142        firstHit = true;
143    }
144
145    public void setQueryID(String queryID)
146    {
147        addSearchProperty(BlastLikeSearchFilter.KEY_QUERY_ID, queryID);
148    }
149
150    public void addSearchProperty(Object key, Object value)
151    {
152        searchProperty.put(key, value);
153    }
154
155    public void startHit()
156    {
157        emittedStartHit = false;
158        firstSubHit = true;
159
160        // determine if anything at the search level
161        // will cause the rest of the hits/subhits
162        // in this search to be skipped.
163        // do only for first hit in a search
164        if (firstHit) {
165            for (Iterator searchFilterI = searchFilters.iterator(); searchFilterI.hasNext();) {
166                BlastLikeSearchFilter sf = (BlastLikeSearchFilter) searchFilterI.next();
167                sf.evaluate(this);
168            }
169    
170            TriState filterStatus = filter.accept();
171            if (filterStatus == TriState.FALSE) {
172                skipHits = true;
173                skipSubHits = true;
174            }
175
176            firstHit = false;
177        }
178    }
179
180    public void addHitProperty(Object key, Object value)
181    {
182        if (skipHits) return;
183        hitProperty.put(key, value);
184    }
185
186    public void startSubHit()
187    {
188        if (skipSubHits) return;
189
190
191        // clear outcomes that depend on the properties
192        // of this subhit
193        for (Iterator subHitFilterI = hitFilters.iterator(); subHitFilterI.hasNext();) {
194            BlastLikeSearchFilter sf = (BlastLikeSearchFilter) subHitFilterI.next();
195            sf.reset();
196        }
197
198        // test filter at hit level here
199        // determine if anything at the search level
200        // will cause the rest of the hits/subhits
201        // in this search to be skipped.
202        // do only at first subhit in a search.
203        if (firstSubHit) {
204            for (Iterator hitFilterI = hitFilters.iterator(); hitFilterI.hasNext();) {
205                BlastLikeSearchFilter sf = (BlastLikeSearchFilter) hitFilterI.next();
206                sf.evaluate(this);
207            }
208    
209            TriState filterStatus = filter.accept();
210
211            if (filterStatus == TriState.FALSE) {
212                skipSubHits = true;
213            }
214
215            firstSubHit = false;
216        }
217    }
218
219    public void addSubHitProperty(Object key, Object value)
220    {
221        if (skipSubHits) return;
222        subHitProperty.put(key, value);
223    }
224
225    public void endSubHit()
226    {
227
228        // test filter at subhit level here
229        // this will be a decision on whether to emit events or not
230        if (!skipSubHits) {
231            for (Iterator subHitFilterI = subHitFilters.iterator(); subHitFilterI.hasNext();) {
232                BlastLikeSearchFilter sf = (BlastLikeSearchFilter) subHitFilterI.next();
233                sf.evaluate(this);
234            }
235
236            // handle emitting events to delegate
237            if (filter.accept() == TriState.TRUE) {
238                if (!emittedStartSearch) {
239                    delegate.startSearch();
240                    String queryId = (String) searchProperty.get(BlastLikeSearchFilter.KEY_QUERY_ID);
241                    if (queryId != null) delegate.setQueryID(queryId);
242
243                    // dump search properties to delegate omitting special keys
244                    for (Iterator searchPropertyI = searchProperty.entrySet().iterator(); 
245                        searchPropertyI.hasNext(); ) {
246                        Map.Entry entry = (Map.Entry) searchPropertyI.next();
247                        if (BlastLikeSearchFilter.KEY_QUERY_ID.equals(entry.getKey())) continue;
248
249                        delegate.addSearchProperty(entry.getKey(), entry.getValue());
250                    }
251
252                    emittedStartSearch = true;
253                }
254
255                if (!emittedStartHit) {
256                    delegate.startHit();
257                    // dump hit properties to delegate.
258                    for (Iterator hitPropertyI = hitProperty.entrySet().iterator();
259                        hitPropertyI.hasNext(); ) {
260                        Map.Entry entry = (Map.Entry) hitPropertyI.next();
261
262                        delegate.addSearchProperty(entry.getKey(), entry.getValue());
263                    }
264
265                    emittedStartHit = true;
266                }
267
268                // dump subhit properties
269                delegate.startSubHit();
270
271                for (Iterator subHitPropertyI = subHitProperty.entrySet().iterator();
272                    subHitPropertyI.hasNext(); ) {
273                    Map.Entry entry = (Map.Entry) subHitPropertyI.next();
274
275                    delegate.addSearchProperty(entry.getKey(), entry.getValue());
276                }
277
278                delegate.endSubHit();
279            }
280        }
281    }
282
283    public void endHit()
284    {
285        if (emittedStartHit) {
286            delegate.endHit();
287            emittedStartHit = false;
288        }
289    }
290
291    public void endSearch()
292    {
293        if (emittedStartSearch) {
294            delegate.endSearch();
295            emittedStartSearch = false;
296        }
297    }
298
299}
300