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.biojavax.bio.db.biosql;
023import java.lang.reflect.InvocationTargetException;
024import java.lang.reflect.Method;
025import java.util.Collection;
026import java.util.Collections;
027import java.util.HashMap;
028import java.util.Iterator;
029import java.util.Map;
030import java.util.NoSuchElementException;
031
032import org.biojava.bio.BioRuntimeException;
033import org.biojava.bio.seq.Feature;
034import org.biojava.bio.seq.FeatureFilter;
035import org.biojava.bio.seq.FilterUtils;
036import org.biojava.utils.walker.WalkerFactory;
037import org.biojavax.Note;
038import org.biojavax.RichAnnotation;
039import org.biojavax.RichObjectFactory;
040import org.biojavax.SimpleNote;
041import org.biojavax.bio.seq.RichFeature;
042import org.biojavax.bio.seq.RichLocation;
043import org.biojavax.bio.seq.RichLocation.Strand;
044import org.biojavax.ontology.ComparableTerm;
045
046
047
048/**
049 * A filter for accepting or rejecting a feature.
050 *
051 * <p>
052 * It is possible to write custom <code>FeatureFilter</code>s by implementing this
053 * interface.  There are also a wide range of built-in features, and it is possible
054 * to build complex queries using <code>FeatureFilter.And</code>, <code>FeatureFilter.Or</code>,
055 * and <code>FeatureFilter.Not</code>.  Where possible, use of the built-in filters
056 * is preferable to writing new filters, since the methods in the <code>FilterUtils</code>
057 * class have access to special knowledge about the built-in filter types and how they
058 * relate to one another.
059 * </p>
060 *
061 * <p>
062 * If the filter is to be used in a remote process, it is recognized that it may
063 * be serialized and sent over to run remotely, rather than each feature being
064 * retrieved locally.
065 * </p>
066 *
067 * <p>
068 * This class requires the Hibernate JAR files to be on your classpath at runtime. It is
069 * designed ONLY for use with BioSQLRichSequenceDB and BioSQLBioEntryDB.
070 * </p>
071 *
072 * @author Matthew Pocock
073 * @author Thomas Down
074 * @author Richard Holland
075 * @since 1.5
076 * @since 1.5
077 */
078
079public interface BioSQLFeatureFilter extends FeatureFilter {
080    
081    /**
082     * This method returns a Hibernate Criterion object that can be used to
083     * query the database.
084     * @return a Hibernate Criterion object representing this filter.
085     */
086    public Object asCriterion();
087    
088    /**
089     * Returns a map of property names (keys) to aliases (values), if the criterion
090     * returned by asCriterion() uses aliases at all. If not, then it must at least
091     * return the empty map else you'll get NullPointerExceptions thrown elsewhere.
092     * @return Map a map of property names to aliases used in the criterion.
093     */
094    public Map criterionAliasMap();
095    
096    /**
097     * A class representing some useful stuff you can do with BioSQLFeatureFilters,
098     * for instance converting plain FeatureFilters into a their BioSQLFeatureFilter
099     * equivalents (where possible).
100     */
101    public static class Tools {
102        /**
103         * Convert a non-BioSQL FeatureFilter into a BioSQL one. We do this
104         * by walking through it, converting any ones we recognise into their
105         * BioSQLFeatureFilter equivalents. If we don't recognise them, we take
106         * special action. For the child of an And, we can just ignore the missing
107         * side and replace the And itself with the remaining side. For everything else,
108         * the entire FeatureFilter is replaced by BioSQLFeatureFilter.all else we
109         * run the risk of missing out potential candidates.
110         * The end result is a filter that can be applied to the
111         * database to filter out potential candidates for more rigorous selection
112         * in-memory by the default filter() method in AbstractRichSequenceDB. Whether or
113         * not the filter picks out everything correctly depends entirely on whether it
114         * is made up of BioSQLFeatureFilter elements, or can be converted into them.
115         */
116        public static BioSQLFeatureFilter convert(FeatureFilter ff) {
117            // The easy case first.
118            if (ff instanceof BioSQLFeatureFilter) return (BioSQLFeatureFilter)ff;
119            else {
120                BioSQLFeatureFilter bff = attemptConversion(ff);
121                if (bff!=null) return bff;
122                else return BioSQLFeatureFilter.all; // catch-all case.
123            }
124        }
125        
126        private static BioSQLFeatureFilter attemptConversion(FeatureFilter ff) {
127            // AND - convert both children. If both are convertible, return the And
128            // of them. If only one is, return just that child. If neither are,
129            // return null.
130            if (ff instanceof FeatureFilter.And) {
131                FeatureFilter.And ffand = (FeatureFilter.And)ff;
132                BioSQLFeatureFilter child1 = attemptConversion(ffand.getChild1());
133                BioSQLFeatureFilter child2 = attemptConversion(ffand.getChild2());
134                if (child1==null && child2==null) return null;
135                else if (child1==null && child2!=null) return child2;
136                else if (child1!=null && child2==null) return child1;
137                else return new BioSQLFeatureFilter.And(child1,child2);
138            }
139            // OR - convert both children. If both are convertible, return the Or
140            // of them. Otherwise, return null.
141            else if (ff instanceof FeatureFilter.Or) {
142                FeatureFilter.Or ffor = (FeatureFilter.Or)ff;
143                BioSQLFeatureFilter child1 = attemptConversion(ffor.getChild1());
144                BioSQLFeatureFilter child2 = attemptConversion(ffor.getChild2());
145                if (child1==null || child2==null) return null;
146                else return new BioSQLFeatureFilter.Or(child1,child2);
147            }
148            // NOT - convert the child. If convertible, return the Not of it. Else,
149            // return null.
150            else if (ff instanceof FeatureFilter.Not) {
151                FeatureFilter.Not ffnot = (FeatureFilter.Not)ff;
152                BioSQLFeatureFilter child = attemptConversion(ffnot.getChild());
153                if (child==null) return null;
154                else return new BioSQLFeatureFilter.Not(child);
155            }
156            // BySource - convert the term to a Term from the default ontology then
157            // try BySourceTerm.
158            else if (ff instanceof FeatureFilter.BySource) {
159                FeatureFilter.BySource ffsrc = (FeatureFilter.BySource)ff;
160                String name = ffsrc.getSource();
161                return new BioSQLFeatureFilter.BySourceTermName(name);
162            }
163            // ByType - convert the term to a Term from the default ontology then
164            // try ByTypeTerm.
165            else if (ff instanceof FeatureFilter.ByType) {
166                FeatureFilter.ByType ffsrc = (FeatureFilter.ByType)ff;
167                String name = ffsrc.getType();
168                return new BioSQLFeatureFilter.ByTypeTermName(name);
169            }
170            // ContainedByLocation - simple pass-through
171            else if (ff instanceof FeatureFilter.ContainedByLocation) {
172                FeatureFilter.ContainedByLocation ffloc = (FeatureFilter.ContainedByLocation)ff;
173                return new BioSQLFeatureFilter.ContainedByRichLocation(RichLocation.Tools.enrich(ffloc.getLocation()));
174            }
175            // BySequenceName - simple pass-through
176            else if (ff instanceof FeatureFilter.BySequenceName) {
177                FeatureFilter.BySequenceName ffsn = (FeatureFilter.BySequenceName)ff;
178                return new BioSQLFeatureFilter.BySequenceName(ffsn.getSequenceName());
179            }
180            // ShadowOverlapsLocation - simple pass-through to OverlapsRichLocation, as we have no concept
181            // of shadows within BioSQL so they are effectively the same thing.
182            else if (ff instanceof FeatureFilter.ShadowOverlapsLocation) {
183                FeatureFilter.ShadowOverlapsLocation ffloc = (FeatureFilter.ShadowOverlapsLocation)ff;
184                return new BioSQLFeatureFilter.OverlapsRichLocation(RichLocation.Tools.enrich(ffloc.getLocation()));
185            }
186            // AnnotationContains - attempt to convert the key to a ComparableTerm, and the value to a string (retrieve the
187            // sole member if it is a collection), then wrap the whole thing in a Note with rank 0 and try using
188            // ByNoteWithValue to retrieve.
189            else if (ff instanceof FeatureFilter.AnnotationContains) {
190                FeatureFilter.AnnotationContains ffann = (FeatureFilter.AnnotationContains)ff;
191                if (!(ffann.getValue() instanceof String)) return null;
192                String noteValue = (String)ffann.getValue();
193                ComparableTerm noteTerm;
194                Object key = ffann.getKey();
195                if (key instanceof Collection) {
196                    Collection coll = (Collection)key;
197                    if (coll.size()<1) return null;
198                    else key = coll.toArray()[0];
199                }
200                if (key instanceof ComparableTerm) noteTerm = (ComparableTerm)key;
201                else if (key instanceof String) noteTerm =  RichObjectFactory.getDefaultOntology().getOrCreateTerm((String)key);
202                else return null;
203                return new BioSQLFeatureFilter.ByNote(new SimpleNote(noteTerm,noteValue,0));
204            }
205            // StrandFilter - attempt to convert the StrandedFeature.Strand to a RichLocation.Strand then pass through
206            // to ByStrand.
207            else if (ff instanceof FeatureFilter.StrandFilter) {
208                FeatureFilter.StrandFilter ffstr = (FeatureFilter.StrandFilter)ff;
209                Strand strand = RichLocation.Strand.forName(""+ffstr.getStrand().getToken());
210                return new BioSQLFeatureFilter.ByStrand(strand);
211            }
212            // HasAnnotation - attempt to convert the term into a ComparableTerm, then use ByNoteTermOnly.
213            else if (ff instanceof FeatureFilter.HasAnnotation) {
214                FeatureFilter.HasAnnotation ffann = (FeatureFilter.HasAnnotation)ff;
215                ComparableTerm noteTerm;
216                if (ffann.getKey() instanceof ComparableTerm) noteTerm = (ComparableTerm)ffann.getKey();
217                else if (ffann.getKey() instanceof String) noteTerm =  RichObjectFactory.getDefaultOntology().getOrCreateTerm((String)ffann.getKey());
218                else return null;
219                return new BioSQLFeatureFilter.ByNoteTermOnly(noteTerm);
220            }
221            // ByAnnotation - attempt to convert the key to a ComparableTerm, and the value to a string, then wrap the
222            // whole thing in a Note with rank 0 and try using ByNoteWithValue to retrieve.
223            else if (ff instanceof FeatureFilter.ByAnnotation) {
224                FeatureFilter.ByAnnotation ffann = (FeatureFilter.ByAnnotation)ff;
225                if (!(ffann.getValue() instanceof String)) return null;
226                String noteValue = (String)ffann.getValue();
227                ComparableTerm noteTerm;
228                Object key = ffann.getKey();
229                if (key instanceof ComparableTerm) noteTerm = (ComparableTerm)key;
230                else if (key instanceof String) noteTerm =  RichObjectFactory.getDefaultOntology().getOrCreateTerm((String)key);
231                else return null;
232                return new BioSQLFeatureFilter.ByNote(new SimpleNote(noteTerm,noteValue,0));
233            }
234            // OverlapsLocation - simple pass-through to OverlapsRichLocation.
235            else if (ff instanceof FeatureFilter.OverlapsLocation) {
236                FeatureFilter.OverlapsLocation ffloc = (FeatureFilter.OverlapsLocation)ff;
237                return new BioSQLFeatureFilter.OverlapsRichLocation(RichLocation.Tools.enrich(ffloc.getLocation()));
238            }
239            // ShadowContainedByLocation - simple pass-through to ContainedByRichLocation, as we have no concept
240            // of shadows within BioSQL so they are effectively the same thing.
241            else if (ff instanceof FeatureFilter.ShadowContainedByLocation) {
242                FeatureFilter.ShadowContainedByLocation ffloc = (FeatureFilter.ShadowContainedByLocation)ff;
243                return new BioSQLFeatureFilter.ContainedByRichLocation(RichLocation.Tools.enrich(ffloc.getLocation()));
244            }
245            // Anything else we don't recognise? Return null!
246            else {
247                return null;
248            }
249        }
250    }
251    
252    // Now for some useful filters.
253    
254    /**
255     * All features are selected by this filter.
256     */
257    static final public BioSQLFeatureFilter all = new BioSQLAcceptAllFilter();
258    
259    /**
260     * No features are selected by this filter.
261     */
262    static final public BioSQLFeatureFilter none = new BioSQLAcceptNoneFilter();
263    
264    /**
265     * A filter for Hibernate-BioSQL filters to extend.
266     */
267    public abstract static class HibernateFeatureFilter implements BioSQLFeatureFilter {
268        protected Method not;
269        protected Method and;
270        protected Method or;
271        protected Method eq;
272        protected Method le;
273        protected Method ge;
274        protected Method conjunction;
275        protected Method disjunction;
276        protected Method conjunctAdd;
277        protected Method disjunctAdd;
278        
279        public HibernateFeatureFilter() {
280            try {
281                // Lazy load the Restrictions class from Hibernate.
282                Class restrictions = Class.forName("org.hibernate.criterion.Restrictions");
283                // Lazy load the Criterion class from Hibernate.
284                Class criterion = Class.forName("org.hibernate.criterion.Criterion");
285                // Lookup the methods
286                this.not = restrictions.getMethod("not", new Class[]{criterion});
287                this.and = restrictions.getMethod("and", new Class[]{criterion,criterion});
288                this.or = restrictions.getMethod("or", new Class[]{criterion,criterion});
289                this.eq = restrictions.getMethod("eq", new Class[]{String.class,Object.class});
290                this.le = restrictions.getMethod("le", new Class[]{String.class,Object.class});
291                this.ge = restrictions.getMethod("ge", new Class[]{String.class,Object.class});
292                this.conjunction = restrictions.getMethod("conjunction", new Class[]{});
293                this.disjunction = restrictions.getMethod("disjunction", new Class[]{});
294                // Lazy load the Conjunction(Or)+Disjunction(And) class from Hibernate.
295                Class conjunctClass = Class.forName("org.hibernate.criterion.Conjunction");
296                Class disjunctClass = Class.forName("org.hibernate.criterion.Disjunction");
297                // Lookup the methods
298                this.conjunctAdd = conjunctClass.getMethod("add", new Class[]{criterion});
299                this.disjunctAdd = disjunctClass.getMethod("add", new Class[]{criterion});
300            } catch (ClassNotFoundException e) {
301                throw new RuntimeException(e);
302            } catch (NoSuchMethodException e) {
303                throw new RuntimeException(e);
304            }
305        }
306        
307        public Map criterionAliasMap() {
308            return Collections.EMPTY_MAP;
309        }
310    }
311    
312    /**
313     *  A filter that returns all features not accepted by a child filter.
314     *
315     * @author Thomas Down
316     * @author Matthew Pocock
317     * @author Richard Holland
318     * @since 1.5
319     */
320    public final static class Not extends HibernateFeatureFilter {
321        static { WalkerFactory.getInstance().addTypeWithParent(Not.class); }
322        
323        BioSQLFeatureFilter child;
324        
325        public BioSQLFeatureFilter getChild() {
326            return child;
327        }
328        
329        public Not(BioSQLFeatureFilter child) {
330            super();
331            if (!(child instanceof BioSQLFeatureFilter))
332                throw new BioRuntimeException("Cannot use non-BioSQLFeatureFilter instances with this class");
333            this.child = child;
334        }
335        
336        public boolean accept(Feature f) {
337            return !(child.accept(f));
338        }
339        
340        public Object asCriterion() {
341            try {
342                return this.not.invoke(null,new Object[]{child.asCriterion()});
343            } catch (InvocationTargetException e) {
344                throw new RuntimeException(e);
345            } catch (IllegalAccessException e) {
346                throw new RuntimeException(e);
347            }
348        }
349        
350        public Map criterionAliasMap() {
351            return child.criterionAliasMap();
352        }
353        
354        public boolean equals(Object o) {
355            return
356                    (o instanceof Not) &&
357                    (((Not) o).getChild().equals(this.getChild()));
358        }
359        
360        public int hashCode() {
361            return getChild().hashCode();
362        }
363        
364        public String toString() {
365            return "Not(" + child + ")";
366        }
367    }
368    
369    
370    /**
371     *  A filter that returns all features accepted by both child filter.
372     *
373     * @author Thomas Down
374     * @author Matthew Pocock
375     * @author Richard Holland
376     * @since 1.5
377     */
378    public final static class And extends HibernateFeatureFilter {
379        static { WalkerFactory.getInstance().addTypeWithParent(And.class); }
380        
381        BioSQLFeatureFilter c1, c2;
382        
383        public BioSQLFeatureFilter getChild1() {
384            return c1;
385        }
386        
387        public BioSQLFeatureFilter getChild2() {
388            return c2;
389        }
390        
391        public And(BioSQLFeatureFilter c1, BioSQLFeatureFilter c2) {
392            super();
393            if (!(c1 instanceof BioSQLFeatureFilter) || !(c2 instanceof BioSQLFeatureFilter))
394                throw new BioRuntimeException("Cannot use non-BioSQLFeatureFilter instances with this class");
395            this.c1 = c1;
396            this.c2 = c2;
397        }
398        
399        public boolean accept(Feature f) {
400            return (c1.accept(f) && c2.accept(f));
401        }
402        
403        public Object asCriterion() {
404            try {
405                return this.and.invoke(null,new Object[]{c1.asCriterion(),c2.asCriterion()});
406            } catch (InvocationTargetException e) {
407                throw new RuntimeException(e);
408            } catch (IllegalAccessException e) {
409                throw new RuntimeException(e);
410            }
411        }
412        
413        public Map criterionAliasMap() {
414            Map results = new HashMap();
415            results.putAll(c1.criterionAliasMap());
416            results.putAll(c2.criterionAliasMap());
417            return results;
418        }
419        
420        public boolean equals(Object o) {
421            if(o instanceof BioSQLFeatureFilter) {
422                return FilterUtils.areEqual(this, (FeatureFilter) o);
423            } else {
424                return false;
425            }
426        }
427        
428        public int hashCode() {
429            return getChild1().hashCode() ^ getChild2().hashCode();
430        }
431        
432        public String toString() {
433            return "And(" + c1 + " , " + c2 + ")";
434        }
435    }
436    
437    /**
438     *  A filter that returns all features accepted by at least one child filter.
439     *
440     * @author Thomas Down
441     * @author Matthew Pocock
442     * @author Richard Holland
443     * @since 1.5
444     */
445    public final static class Or extends HibernateFeatureFilter {
446        static { WalkerFactory.getInstance().addTypeWithParent(Or.class); }
447        
448        BioSQLFeatureFilter c1, c2;
449        
450        public BioSQLFeatureFilter getChild1() {
451            return c1;
452        }
453        
454        public BioSQLFeatureFilter getChild2() {
455            return c2;
456        }
457        
458        public Or(BioSQLFeatureFilter c1, BioSQLFeatureFilter c2) {
459            super();
460            if (!(c1 instanceof BioSQLFeatureFilter) || !(c2 instanceof BioSQLFeatureFilter))
461                throw new BioRuntimeException("Cannot use non-BioSQLFeatureFilter instances with this class");
462            this.c1 = c1;
463            this.c2 = c2;
464        }
465        
466        public boolean accept(Feature f) {
467            return (c1.accept(f) || c2.accept(f));
468        }
469        
470        public Object asCriterion() {
471            try {
472                return this.or.invoke(null,new Object[]{c1.asCriterion(),c2.asCriterion()});
473            } catch (InvocationTargetException e) {
474                throw new RuntimeException(e);
475            } catch (IllegalAccessException e) {
476                throw new RuntimeException(e);
477            }
478        }
479        
480        public Map criterionAliasMap() {
481            Map results = new HashMap();
482            results.putAll(c1.criterionAliasMap());
483            results.putAll(c2.criterionAliasMap());
484            return results;
485        }
486        
487        public boolean equals(Object o) {
488            if(o instanceof BioSQLFeatureFilter) {
489                return FilterUtils.areEqual(this, (FeatureFilter) o);
490            } else {
491                return false;
492            }
493        }
494        
495        public int hashCode() {
496            return getChild1().hashCode() ^ getChild2().hashCode();
497        }
498        
499        public String toString() {
500            return "Or(" + c1 + " , " + c2 + ")";
501        }
502    }
503    
504    /**
505     * Construct one of these to filter features by display name.
506     *
507     * @author Richard Holland
508     * @since 1.5
509     */
510    final public static class ByName extends HibernateFeatureFilter {
511        private String name;
512        
513        public String getName() {
514            return name;
515        }
516        
517        /**
518         * Create a ByType filter that filters in all features with type fields
519         * equal to type.
520         *
521         * @param name  the String to match type fields against
522         */
523        public ByName(String name) {
524            super();
525            if (name == null) {
526                throw new NullPointerException("Name may not be null");
527            }
528            this.name = name;
529        }
530        
531        /**
532         * Returns true if the feature has a matching type property.
533         */
534        public boolean accept(Feature f) {
535            if (f instanceof RichFeature) {
536                return name.equals(((RichFeature)f).getName());
537            }
538            return false;
539        }
540        
541        public Object asCriterion() {
542            try {
543                return this.eq.invoke(null,new Object[]{"name",name});
544            } catch (InvocationTargetException e) {
545                throw new RuntimeException(e);
546            } catch (IllegalAccessException e) {
547                throw new RuntimeException(e);
548            }
549        }
550        
551        public boolean equals(Object o) {
552            return
553                    (o instanceof ByName) &&
554                    (((ByName) o).getName().equals(this.getName()));
555        }
556        
557        public int hashCode() {
558            return getName().hashCode();
559        }
560        
561        public String toString() {
562            return "ByName(" + name + ")";
563        }
564    }
565    
566    /**
567     * Construct one of these to filter features by rank.
568     *
569     * @author Richard Holland
570     * @since 1.5
571     */
572    final public static class ByRank extends HibernateFeatureFilter {
573        private int rank;
574        
575        public int getRank() {
576            return rank;
577        }
578        
579        /**
580         * Create a Rank filter that filters in all features with rank fields
581         * equal to rank.
582         *
583         * @param rank  the rank to match type fields against
584         */
585        public ByRank(int rank) {
586            super();
587            this.rank = rank;
588        }
589        
590        /**
591         * Returns true if the feature has a matching type property.
592         */
593        public boolean accept(Feature f) {
594            if (f instanceof RichFeature) {
595                return rank==((RichFeature)f).getRank();
596            }
597            return false;
598        }
599        
600        public Object asCriterion() {
601            try {
602                return this.eq.invoke(null,new Object[]{"rank",new Integer(rank)});
603            } catch (InvocationTargetException e) {
604                throw new RuntimeException(e);
605            } catch (IllegalAccessException e) {
606                throw new RuntimeException(e);
607            }
608        }
609        
610        public boolean equals(Object o) {
611            return
612                    (o instanceof ByRank) &&
613                    (((ByRank) o).getRank() == this.getRank());
614        }
615        
616        public int hashCode() {
617            return rank;
618        }
619        
620        public String toString() {
621            return "ByRank(" + rank + ")";
622        }
623    }
624    
625    /**
626     * Construct one of these to filter features by type.
627     *
628     * @author Matthew Pocock
629     * @author Richard Holland
630     * @since 1.5
631     */
632    final public static class ByTypeTerm extends HibernateFeatureFilter {
633        private ComparableTerm typeTerm;
634        
635        public ComparableTerm getTypeTerm() {
636            return typeTerm;
637        }
638        
639        /**
640         * Create a ByTypeTerm filter that filters in all features with typeTerm fields
641         * equal to typeTerm.
642         *
643         * @param typeTerm  the Term to match typeTerm fields against
644         */
645        public ByTypeTerm(ComparableTerm typeTerm) {
646            super();
647            if (typeTerm == null) {
648                throw new NullPointerException("Type may not be null");
649            }
650            this.typeTerm = typeTerm;
651        }
652        
653        /**
654         * Returns true if the feature has a matching type property.
655         */
656        public boolean accept(Feature f) {
657            return typeTerm.equals(f.getTypeTerm());
658        }
659        
660        public Object asCriterion() {
661            try {
662                return this.eq.invoke(null,new Object[]{"typeTerm",typeTerm});
663            } catch (InvocationTargetException e) {
664                throw new RuntimeException(e);
665            } catch (IllegalAccessException e) {
666                throw new RuntimeException(e);
667            }
668        }
669        
670        public boolean equals(Object o) {
671            return
672                    (o instanceof ByTypeTerm) &&
673                    (((ByTypeTerm) o).getTypeTerm().equals(this.getTypeTerm()));
674        }
675        
676        public int hashCode() {
677            return getTypeTerm().hashCode();
678        }
679        
680        public String toString() {
681            return "ByTypeTerm(" + typeTerm + ")";
682        }
683    }
684    
685    
686    /**
687     * Construct one of these to filter features by source.
688     *
689     * @author Matthew Pocock
690     * @author Richard Holland
691     * @since 1.5
692     */
693    final public static class BySourceTerm extends HibernateFeatureFilter {
694        private ComparableTerm sourceTerm;
695        
696        public ComparableTerm getSourceTerm() {
697            return sourceTerm;
698        }
699        
700        /**
701         * Create a BySourceTerm filter that filters in all features with sourceTerm fields
702         * equal to source.
703         *
704         * @param sourceTerm  the Term to match sourceTerm fields against
705         */
706        public BySourceTerm(ComparableTerm sourceTerm) {
707            super();
708            if (sourceTerm == null) {
709                throw new NullPointerException("Source may not be null");
710            }
711            this.sourceTerm = sourceTerm;
712        }
713        
714        /**
715         * Returns true if the feature has a matching source property.
716         */
717        public boolean accept(Feature f) {
718            return sourceTerm.equals(f.getSourceTerm());
719        }
720        
721        public Object asCriterion() {
722            try {
723                return this.eq.invoke(null,new Object[]{"sourceTerm",sourceTerm});
724            } catch (InvocationTargetException e) {
725                throw new RuntimeException(e);
726            } catch (IllegalAccessException e) {
727                throw new RuntimeException(e);
728            }
729        }
730        
731        public boolean equals(Object o) {
732            return
733                    (o instanceof BySourceTerm) &&
734                    (((BySourceTerm) o).getSourceTerm().equals(this.getSourceTerm()));
735        }
736        
737        public int hashCode() {
738            return getSourceTerm().hashCode();
739        }
740        
741        public String toString() {
742            return "BySourceTerm(" + sourceTerm + ")";
743        }
744    }
745    
746    /**
747     * Construct one of these to filter features by type (name only - parent ontology
748     * is ignored).
749     *
750     * @author Richard Holland
751     * @since 1.5
752     */
753    final public static class ByTypeTermName extends HibernateFeatureFilter {
754        private String typeTermName;
755        
756        public String getTypeTermName() {
757            return typeTermName;
758        }
759        
760        /**
761         * Create a ByTypeTermName filter that filters in all features with typeTerm fields
762         * having name equal to typeTermName.
763         *
764         * @param typeTermName  the Term to match typeTermName fields against
765         */
766        public ByTypeTermName(String typeTermName) {
767            super();
768            if (typeTermName == null) {
769                throw new NullPointerException("Type name may not be null");
770            }
771            this.typeTermName = typeTermName;
772        }
773        
774        /**
775         * Returns true if the feature has a matching type property.
776         */
777        public boolean accept(Feature f) {
778            return typeTermName.equals(f.getTypeTerm().getName());
779        }
780        
781        public Object asCriterion() {
782            try {
783                return this.eq.invoke(null,new Object[]{"tt.name",typeTermName});
784            } catch (InvocationTargetException e) {
785                throw new RuntimeException(e);
786            } catch (IllegalAccessException e) {
787                throw new RuntimeException(e);
788            }
789        }
790        
791        public Map criterionAliasMap() {
792            Map results = new HashMap();
793            results.put("typeTerm","tt");
794            return results;
795        }
796        
797        public boolean equals(Object o) {
798            return
799                    (o instanceof ByTypeTermName) &&
800                    (((ByTypeTermName) o).getTypeTermName().equals(this.getTypeTermName()));
801        }
802        
803        public int hashCode() {
804            return getTypeTermName().hashCode();
805        }
806        
807        public String toString() {
808            return "ByTypeTermName(" + typeTermName + ")";
809        }
810    }
811    
812    
813    /**
814     * Construct one of these to filter features by source (name only - parent ontology
815     * is ignored).
816     *
817     * @author Richard Holland
818     * @since 1.5
819     */
820    final public static class BySourceTermName extends HibernateFeatureFilter {
821        private String sourceTermName;
822        
823        public String getSourceTermName() {
824            return sourceTermName;
825        }
826        
827        /**
828         * Create a BySourceTerm filter that filters in all features with sourceTerm fields
829         * having name equal to sourceTermName.
830         *
831         * @param sourceTermName  the name of the Term to match sourceTerm fields against
832         */
833        public BySourceTermName(String sourceTermName) {
834            super();
835            if (sourceTermName == null) {
836                throw new NullPointerException("Source name may not be null");
837            }
838            this.sourceTermName = sourceTermName;
839        }
840        
841        /**
842         * Returns true if the feature has a matching source property.
843         */
844        public boolean accept(Feature f) {
845            return sourceTermName.equals(f.getSourceTerm().getName());
846        }
847        
848        public Object asCriterion() {
849            try {
850                return this.eq.invoke(null,new Object[]{"st.name",sourceTermName});
851            } catch (InvocationTargetException e) {
852                throw new RuntimeException(e);
853            } catch (IllegalAccessException e) {
854                throw new RuntimeException(e);
855            }
856        }
857        
858        public Map criterionAliasMap() {
859            Map results = new HashMap();
860            results.put("sourceTerm","st");
861            return results;
862        }
863        
864        public boolean equals(Object o) {
865            return
866                    (o instanceof BySourceTermName) &&
867                    (((BySourceTermName) o).getSourceTermName().equals(this.getSourceTermName()));
868        }
869        
870        public int hashCode() {
871            return getSourceTermName().hashCode();
872        }
873        
874        public String toString() {
875            return "BySourceTermName(" + sourceTermName + ")";
876        }
877    }
878    
879    /**
880     * Accept features that reside on a sequence with a particular name.
881     *
882     * @author Matthew Pocock
883     * @author Richard Holland
884     * @since 1.5
885     */
886    public final static class BySequenceName extends HibernateFeatureFilter {
887        private String seqName;
888        
889        public BySequenceName(String seqName) {
890            super();
891            this.seqName = seqName;
892        }
893        
894        public String getSequenceName() {
895            return seqName;
896        }
897        
898        public boolean accept(Feature f) {
899            return f.getSequence().getName().equals(seqName);
900        }
901        
902        public Object asCriterion() {
903            try {
904                return this.eq.invoke(null,new Object[]{"p.name",seqName});
905            } catch (InvocationTargetException e) {
906                throw new RuntimeException(e);
907            } catch (IllegalAccessException e) {
908                throw new RuntimeException(e);
909            }
910        }
911        
912        public Map criterionAliasMap() {
913            Map results = new HashMap();
914            results.put("parent","p");
915            return results;
916        }
917        
918        public boolean equals(Object o) {
919            return
920                    (o instanceof BySequenceName) &&
921                    ((BySequenceName) o).getSequenceName().equals(seqName);
922        }
923        
924        public int hashCode() {
925            return seqName.hashCode();
926        }
927    }
928    
929    /**
930     * A filter that returns all features contained within a location. Contained means
931     * that a feature is entirely within, on the same strand and on the same sequence
932     * as any single member of the flattened query location.
933     *
934     * @author Matthew Pocock
935     * @author Richard Holland
936     * @since 1.5
937     */
938    public final static class ContainedByRichLocation extends HibernateFeatureFilter {
939        private RichLocation loc;
940        
941        public RichLocation getRichLocation() {
942            return loc;
943        }
944        
945        /**
946         * Creates a filter that returns everything contained within loc.
947         *
948         * @param loc  the location that will contain the accepted features
949         */
950        public ContainedByRichLocation(RichLocation loc) {
951            super();
952            if (loc == null) {
953                throw new NullPointerException("Loc may not be null");
954            }
955            this.loc = loc;
956        }
957        
958        /**
959         * Returns true if the feature is within this filter's location.
960         */
961        public boolean accept(Feature f) {
962            return loc.contains(f.getLocation());
963        }
964        
965        public Object asCriterion() {
966            try {
967                // Conjunction of criteria for each member of the query location.
968                Collection members = RichLocation.Tools.flatten(loc);
969                // some combo of Tools.flatten(loc), min(loc.start,feat.start) and min(loc.end,feat.end)
970                Object parentConjunct = this.conjunction.invoke((Object[])null,(Object[])null);
971                for (Iterator i = members.iterator(); i.hasNext(); ) {
972                    RichLocation loc = (RichLocation)i.next();
973                    Object childDisjunct = this.disjunction.invoke((Object[])null,(Object[])null);
974                    // for each member, find features that have start>=member.start,
975                    // end<=member.end and strand=member.strand and crossref=member.crossref
976                    this.disjunctAdd.invoke(childDisjunct,new Object[]{this.eq.invoke(null, new Object[]{"l.strandNum",new Integer(loc.getStrand().intValue())})});
977                    this.disjunctAdd.invoke(childDisjunct,new Object[]{this.eq.invoke(null, new Object[]{"l.crossRef",loc.getCrossRef()})});
978                    this.disjunctAdd.invoke(childDisjunct,new Object[]{this.ge.invoke(null, new Object[]{"l.min",new Integer(loc.getMin())})});
979                    this.disjunctAdd.invoke(childDisjunct,new Object[]{this.le.invoke(null, new Object[]{"l.max",new Integer(loc.getMax())})});
980                    // add the member to the set of restrictions
981                    this.conjunctAdd.invoke(parentConjunct,new Object[]{childDisjunct});
982                }
983                return parentConjunct;
984            } catch (InvocationTargetException e) {
985                throw new RuntimeException(e);
986            } catch (IllegalAccessException e) {
987                throw new RuntimeException(e);
988            }
989        }
990        
991        public Map criterionAliasMap() {
992            Map results = new HashMap();
993            results.put("locationSet","l");
994            return results;
995        }
996        
997        public boolean equals(Object o) {
998            return
999                    (o instanceof ContainedByRichLocation) &&
1000                    (((ContainedByRichLocation) o).getRichLocation().equals(this.getRichLocation()));
1001        }
1002        
1003        public int hashCode() {
1004            return getRichLocation().hashCode();
1005        }
1006        
1007        public String toString() {
1008            return "ContainedBy(" + loc + ")";
1009        }
1010    }
1011    
1012    /**
1013     * A filter that returns all features having locations on a given strand. They
1014     * may actually have features on other strands too, of course.
1015     *
1016     * @author Richard Holland
1017     * @since 1.5
1018     */
1019    public final static class ByStrand extends HibernateFeatureFilter {
1020        private Strand str;
1021        
1022        public Strand getStrand() {
1023            return str;
1024        }
1025        
1026        /**
1027         * Creates a filter that returns everything on strand str.
1028         *
1029         * @param str  the strand that will contain the accepted features
1030         */
1031        public ByStrand(Strand str) {
1032            super();
1033            if (str == null) {
1034                throw new NullPointerException("Strand may not be null");
1035            }
1036            this.str = str;
1037        }
1038        
1039        /**
1040         * Returns true if the feature overlaps this filter's location.
1041         */
1042        public boolean accept(Feature f) {
1043            if (f instanceof RichFeature) {
1044                RichFeature rf = (RichFeature)f;
1045                for (Iterator i = rf.getLocation().blockIterator(); i.hasNext(); ) {
1046                    RichLocation l = (RichLocation)i.next();
1047                    if (l.getStrand().equals(str)) return true;
1048                }
1049            }
1050            return false;
1051        }
1052        
1053        public Object asCriterion() {
1054            try {
1055                // any location on the feature with a matching strand?
1056                return this.eq.invoke(null,new Object[]{"l.strandNum",new Integer(str.intValue())});
1057            } catch (InvocationTargetException e) {
1058                throw new RuntimeException(e);
1059            } catch (IllegalAccessException e) {
1060                throw new RuntimeException(e);
1061            }
1062        }
1063        
1064        public Map criterionAliasMap() {
1065            Map results = new HashMap();
1066            results.put("locationSet","l");
1067            return results;
1068        }
1069        
1070        public boolean equals(Object o) {
1071            return
1072                    (o instanceof ByStrand) &&
1073                    (((ByStrand) o).getStrand().equals(this.getStrand()));
1074        }
1075        
1076        public int hashCode() {
1077            return getStrand().hashCode();
1078        }
1079        
1080        public String toString() {
1081            return "ByStrand(" + str + ")";
1082        }
1083    }
1084    
1085    /**
1086     * A filter that returns all features overlapping a location. Overlaps means
1087     * that a feature includes part of, on the same strand and on the same sequence
1088     * any single member of the flattened query location.
1089     *
1090     * @author Matthew Pocock
1091     * @author Richard Holland
1092     * @since 1.5
1093     */
1094    public final static class OverlapsRichLocation extends HibernateFeatureFilter {
1095        private RichLocation loc;
1096        
1097        public RichLocation getRichLocation() {
1098            return loc;
1099        }
1100        
1101        /**
1102         * Creates a filter that returns everything overlapping loc.
1103         *
1104         * @param loc  the location that will overlap the accepted features
1105         */
1106        public OverlapsRichLocation(RichLocation loc) {
1107            super();
1108            if (loc == null) {
1109                throw new NullPointerException("Loc may not be null");
1110            }
1111            this.loc = loc;
1112        }
1113        
1114        /**
1115         * Returns true if the feature overlaps this filter's location.
1116         */
1117        public boolean accept(Feature f) {
1118            return loc.overlaps(f.getLocation());
1119        }
1120        
1121        public Object asCriterion() {
1122            try {
1123                // Conjunction of criteria for each member of the query location.
1124                Collection members = RichLocation.Tools.flatten(loc);
1125                // some combo of Tools.flatten(loc), min(loc.start,feat.start) and min(loc.end,feat.end)
1126                Object parentConjunct = this.conjunction.invoke((Object[])null,(Object[])null);
1127                for (Iterator i = members.iterator(); i.hasNext(); ) {
1128                    RichLocation loc = (RichLocation)i.next();
1129                    Object childDisjunct = this.disjunction.invoke((Object[])null,(Object[])null);
1130                    // for each member, find features that have start<=member.end,  end>=member.start,
1131                    // strand=member.strand and crossref=member.crossref
1132                    this.disjunctAdd.invoke(childDisjunct,new Object[]{this.eq.invoke(null, new Object[]{"l.strandNum",new Integer(loc.getStrand().intValue())})});
1133                    this.disjunctAdd.invoke(childDisjunct,new Object[]{this.eq.invoke(null, new Object[]{"l.crossRef",loc.getCrossRef()})});
1134                    this.disjunctAdd.invoke(childDisjunct,new Object[]{this.ge.invoke(null, new Object[]{"l.max",new Integer(loc.getMin())})});
1135                    this.disjunctAdd.invoke(childDisjunct,new Object[]{this.le.invoke(null, new Object[]{"l.min",new Integer(loc.getMax())})});
1136                    // add the member to the set of restrictions
1137                    this.conjunctAdd.invoke(parentConjunct,new Object[]{childDisjunct});
1138                }
1139                return parentConjunct;
1140            } catch (InvocationTargetException e) {
1141                throw new RuntimeException(e);
1142            } catch (IllegalAccessException e) {
1143                throw new RuntimeException(e);
1144            }
1145        }
1146        
1147        public Map criterionAliasMap() {
1148            Map results = new HashMap();
1149            results.put("locationSet","l");
1150            return results;
1151        }
1152        
1153        public boolean equals(Object o) {
1154            return
1155                    (o instanceof OverlapsRichLocation) &&
1156                    (((OverlapsRichLocation) o).getRichLocation().equals(this.getRichLocation()));
1157        }
1158        
1159        public int hashCode() {
1160            return getRichLocation().hashCode();
1161        }
1162        
1163        public String toString() {
1164            return "Overlaps(" + loc + ")";
1165        }
1166    }
1167    
1168    /**
1169     * A filter that returns all features that have the given note, and
1170     * the value and rank is checked as well.
1171     *
1172     * @author Richard Holland
1173     * @author George Waldon
1174     * @since 1.5
1175     */
1176    public final static class ByNote extends HibernateFeatureFilter {
1177        private Note note;
1178        
1179        public ByNote(Note note) {
1180            super();
1181            this.note = note;
1182        }
1183        
1184        public Note getNote() {
1185            return note;
1186        }
1187        
1188        public boolean accept(Feature f) {
1189            if (f instanceof RichFeature) {
1190                RichAnnotation ra = ((RichFeature)f).getRichAnnotation();
1191                try {
1192                    Note n = ra.getNote(note);
1193                    return (n.getValue()==note.getValue()) || (n.getValue()!=null && note.getValue()!=null && n.getValue().equals(note.getValue()));
1194                } catch (NoSuchElementException e) {
1195                    return false;
1196                }
1197            }
1198            return false;
1199        }
1200        
1201        public Object asCriterion() {
1202            try {
1203                Object conjunct = this.conjunction.invoke((Object[])null,(Object[])null);
1204                this.conjunctAdd.invoke(conjunct,new Object[]{this.eq.invoke(null, new Object[]{"n.term",note.getTerm()})});
1205                this.conjunctAdd.invoke(conjunct,new Object[]{this.eq.invoke(null, new Object[]{"n.value",note.getValue()})});
1206                this.conjunctAdd.invoke(conjunct,new Object[]{this.eq.invoke(null, new Object[]{"n.rank",new Integer(note.getRank())})});
1207                return conjunct;
1208            } catch (InvocationTargetException e) {
1209                throw new RuntimeException(e);
1210            } catch (IllegalAccessException e) {
1211                throw new RuntimeException(e);
1212            }
1213        }
1214        
1215        public Map criterionAliasMap() {
1216            Map results = new HashMap();
1217            results.put("noteSet","n");
1218            return results;
1219        }
1220        
1221        public boolean equals(Object o) {
1222            if(o instanceof ByNote) {
1223                ByNote that = (ByNote) o;
1224                return this.getNote() == that.getNote();
1225            }
1226            
1227            return false;
1228        }
1229        
1230        public int hashCode() {
1231            return getNote().hashCode();
1232        }
1233        
1234        public String toString() {
1235            return "ByNote {" + note + "}";
1236        }
1237    }
1238    
1239    /**
1240     * A filter that returns all features that have a note with the given term. The value
1241     * and rank is not checked.
1242     *
1243     * @author Richard Holland
1244     * @author George Waldon
1245     * @since 1.5
1246     */
1247    public final static class ByNoteTermOnly extends HibernateFeatureFilter {
1248        private ComparableTerm term;
1249        
1250        public ByNoteTermOnly(ComparableTerm term) {
1251            super();
1252            this.term = term;
1253        }
1254        
1255        public ComparableTerm getTerm() {
1256            return term;
1257        }
1258        
1259        public boolean accept(Feature f) {
1260            if (f instanceof RichFeature) {
1261                RichAnnotation ra = ((RichFeature)f).getRichAnnotation();
1262                try {
1263                    for (Iterator i = ra.getNoteSet().iterator(); i.hasNext(); ) if (((Note)i.next()).getTerm().equals(term)) return true;
1264                } catch (NoSuchElementException e) {
1265                    return false;
1266                }
1267            }
1268            return false;
1269        }
1270        
1271        public Object asCriterion() {
1272            try {
1273                return this.eq.invoke(null, new Object[]{"n.term",term});
1274            } catch (InvocationTargetException e) {
1275                throw new RuntimeException(e);
1276            } catch (IllegalAccessException e) {
1277                throw new RuntimeException(e);
1278            }
1279        }
1280        
1281        public Map criterionAliasMap() {
1282            Map results = new HashMap();
1283            results.put("noteSet","n");
1284            return results;
1285        }
1286        
1287        public boolean equals(Object o) {
1288            if(o instanceof ByNoteTermOnly) {
1289                ByNoteTermOnly that = (ByNoteTermOnly) o;
1290                return this.getTerm() == that.getTerm();
1291            }
1292            
1293            return false;
1294        }
1295        
1296        public int hashCode() {
1297            return getTerm().hashCode();
1298        }
1299        
1300        public String toString() {
1301            return "ByNoteTermOnly {" + term + "}";
1302        }
1303    }
1304}
1305