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.seq;
023
024import java.io.Serializable;
025import java.util.HashSet;
026import java.util.Iterator;
027import java.util.Set;
028
029import org.biojava.bio.AnnotationTools;
030import org.biojava.bio.AnnotationType;
031import org.biojava.bio.CardinalityConstraint;
032import org.biojava.bio.CollectionConstraint;
033import org.biojava.bio.PropertyConstraint;
034import org.biojava.bio.seq.homol.SimilarityPairFeature;
035import org.biojava.bio.symbol.Location;
036import org.biojava.bio.symbol.RangeLocation;
037import org.biojava.utils.walker.WalkerFactory;
038
039/**
040 * A filter for accepting or rejecting a feature.
041 *
042 * <p>
043 * It is possible to write custom <code>FeatureFilter</code>s by implementing this
044 * interface.  There are also a wide range of built-in features, and it is possible
045 * to build complex queries using <code>FeatureFilter.And</code>, <code>FeatureFilter.Or</code>,
046 * and <code>FeatureFilter.Not</code>.  Where possible, use of the built-in filters
047 * is preferable to writing new filters, since the methods in the <code>FilterUtils</code>
048 * class have access to special knowledge about the built-in filter types and how they
049 * relate to one another.
050 * </p>
051 *
052 * <p>
053 * If the filter is to be used in a remote process, it is recognized that it may
054 * be serialized and sent over to run remotely, rather than each feature being
055 * retrieved locally.
056 * </p>
057 *
058 * @since 1.0
059 * @author Matthew Pocock
060 * @author Thomas Down
061 */
062
063public interface FeatureFilter extends Serializable {
064  /**
065   * This method determines whether a feature is to be accepted.
066   *
067   * @param f the Feature to evaluate
068   * @return  true if this feature is to be selected in, or false if it is to be ignored
069   */
070  boolean accept(Feature f);
071
072  /**
073   * All features are selected by this filter.
074   */
075  static final public FeatureFilter all = new AcceptAllFilter();
076
077  /**
078   * No features are selected by this filter.
079   */
080  static final public FeatureFilter none = new AcceptNoneFilter();
081
082
083  /**
084   *  A filter that returns all features not accepted by a child filter.
085   *
086   * @author Thomas Down
087   * @author Matthew Pocock
088   * @since 1.0
089   */
090  public final static class Not implements FeatureFilter {
091    static { WalkerFactory.getInstance().addTypeWithParent(Not.class); }
092
093    FeatureFilter child;
094
095    public FeatureFilter getChild() {
096      return child;
097    }
098
099    public Not(FeatureFilter child) {
100        this.child = child;
101    }
102
103    public boolean accept(Feature f) {
104        return !(child.accept(f));
105    }
106
107    public boolean equals(Object o) {
108      return
109        (o instanceof Not) &&
110        (((Not) o).getChild().equals(this.getChild()));
111    }
112
113    public int hashCode() {
114      return getChild().hashCode();
115    }
116
117    public String toString() {
118      return "Not(" + child + ")";
119    }
120  }
121
122  /**
123   *  A filter that returns all features accepted by both child filter.
124   *
125   * @author Thomas Down
126   * @author Matthew Pocock
127   * @since 1.0
128   */
129  public final static class And implements FeatureFilter {
130    static { WalkerFactory.getInstance().addTypeWithParent(And.class); }
131
132    FeatureFilter c1, c2;
133
134    public FeatureFilter getChild1() {
135      return c1;
136    }
137
138    public FeatureFilter getChild2() {
139      return c2;
140    }
141
142    public And(FeatureFilter c1, FeatureFilter c2) {
143        this.c1 = c1;
144        this.c2 = c2;
145    }
146
147    public boolean accept(Feature f) {
148        return (c1.accept(f) && c2.accept(f));
149    }
150
151    public boolean equals(Object o) {
152      if(o instanceof FeatureFilter) {
153        return FilterUtils.areEqual(this, (FeatureFilter) o);
154      } else {
155        return false;
156      }
157    }
158
159    public int hashCode() {
160      return getChild1().hashCode() ^ getChild2().hashCode();
161    }
162
163      public String toString() {
164          return "And(" + c1 + " , " + c2 + ")";
165       }
166  }
167
168  /**
169   *  A filter that returns all features accepted by at least one child filter.
170   *
171   * @author Thomas Down
172   * @author Matthew Pocock
173   * @since 1.0
174   */
175  public final static class Or implements FeatureFilter {
176    static { WalkerFactory.getInstance().addTypeWithParent(Or.class); }
177
178    FeatureFilter c1, c2;
179
180    public FeatureFilter getChild1() {
181      return c1;
182    }
183
184    public FeatureFilter getChild2() {
185      return c2;
186    }
187
188    public Or(FeatureFilter c1, FeatureFilter c2) {
189        this.c1 = c1;
190        this.c2 = c2;
191    }
192
193    public boolean accept(Feature f) {
194        return (c1.accept(f) || c2.accept(f));
195    }
196
197    public boolean equals(Object o) {
198      if(o instanceof FeatureFilter) {
199        return FilterUtils.areEqual(this, (FeatureFilter) o);
200      } else {
201        return false;
202      }
203    }
204
205    public int hashCode() {
206      return getChild1().hashCode() ^ getChild2().hashCode();
207    }
208
209    public String toString() {
210      return "Or(" + c1 + " , " + c2 + ")";
211    }
212  }
213
214  /**
215   * Construct one of these to filter features by type.
216   *
217   * @author Matthew Pocock
218   * @since 1.0
219   */
220  final public static class ByType implements OptimizableFilter {
221    private String type;
222
223    public String getType() {
224      return type;
225    }
226
227    /**
228     * Create a ByType filter that filters in all features with type fields
229     * equal to type.
230     *
231     * @param type  the String to match type fields against
232     */
233    public ByType(String type) {
234        if (type == null)
235            throw new NullPointerException("Type may not be null");
236        this.type = type;
237    }
238
239    /**
240     * Returns true if the feature has a matching type property.
241     */
242    public boolean accept(Feature f) {
243      return type.equals(f.getType());
244    }
245
246    public boolean equals(Object o) {
247      return
248        (o instanceof ByType) &&
249        (((ByType) o).getType().equals(this.getType()));
250    }
251
252    public int hashCode() {
253      return getType().hashCode();
254    }
255
256    public boolean isProperSubset(FeatureFilter sup) {
257      return this.equals(sup) || (sup instanceof AcceptAllFilter);
258    }
259
260    public boolean isDisjoint(FeatureFilter filt) {
261      return (filt instanceof AcceptNoneFilter) || (
262        (filt instanceof ByType) &&
263        !getType().equals(((ByType) filt).getType())
264      );
265    }
266
267    public String toString() {
268      return "ByType(" + type + ")";
269    }
270  }
271
272  /**
273   * Construct one of these to filter features by source.
274   *
275   * @author Matthew Pocock
276   * @since 1.0
277   */
278  public final static class BySource implements OptimizableFilter {
279    private String source;
280
281    public String getSource() {
282      return source;
283    }
284
285    /**
286     * Create a BySource filter that filters in all features which have sources
287     * equal to source.
288     *
289     * @param source  the String to match source fields against
290     */
291    public BySource(String source) {
292        if (source == null)
293            throw new NullPointerException("Source may not be null");
294        this.source = source;
295    }
296
297    public boolean accept(Feature f) { return source.equals(f.getSource()); }
298
299    public boolean equals(Object o) {
300      return
301        (o instanceof BySource) &&
302        (((BySource) o).getSource().equals(this.getSource()));
303    }
304
305    public boolean isProperSubset(FeatureFilter sup) {
306      return this.equals(sup) || (sup instanceof AcceptAllFilter);
307    }
308
309    public int hashCode() {
310      return getSource().hashCode();
311    }
312
313    public boolean isDisjoint(FeatureFilter filt) {
314      return (filt instanceof AcceptNoneFilter) || (
315        (filt instanceof BySource) &&
316        !getSource().equals(((BySource) filt).getSource())
317      );
318    }
319
320      public String toString() {
321          return "BySource(" + source + ")";
322       }
323  }
324
325  /**
326   * Filter which accepts only those filters which are an instance
327   * of a specific Java class
328   *
329   * @author Thomas Down
330   * @author Matthew Pocock
331   * @since 1.1
332   */
333
334  public final static class ByClass implements OptimizableFilter {
335    private Class clazz;
336
337    public ByClass(Class clazz) {
338        if (clazz == null) {
339            throw new NullPointerException("Clazz may not be null");
340        }
341        if(!Feature.class.isAssignableFrom(clazz)) {
342            throw new ClassCastException(
343              "Filters by class must be over Feature classes: " +
344              clazz
345            );
346        }
347        this.clazz = clazz;
348    }
349
350    public boolean accept(Feature f) {
351      return clazz.isInstance(f);
352    }
353
354    public Class getTestClass() {
355      return clazz;
356    }
357
358    public boolean equals(Object o) {
359      return
360        (o instanceof ByClass) &&
361        (((ByClass) o).getTestClass() == this.getTestClass());
362    }
363
364    public int hashCode() {
365      return getTestClass().hashCode();
366    }
367
368    public boolean isProperSubset(FeatureFilter sup) {
369      if(sup instanceof ByClass) {
370        Class supC = ((ByClass) sup).getTestClass();
371        return supC.isAssignableFrom(this.getTestClass());
372      }
373      return (sup instanceof AcceptAllFilter);
374    }
375
376    public boolean isDisjoint(FeatureFilter feat) {
377      if(feat instanceof ByClass) {
378        Class featC = ((ByClass) feat).getTestClass();
379        return
380          ! (featC.isAssignableFrom(getTestClass())) &&
381          ! (getTestClass().isAssignableFrom(featC));
382      } else if (feat instanceof ByComponentName) {
383          return !getTestClass().isAssignableFrom(ComponentFeature.class);
384      }
385
386      return (feat instanceof AcceptNoneFilter);
387    }
388
389    public String toString() {
390      return "ByClass(" + clazz.getName() + ")";
391    }
392  }
393
394
395  /**
396   * Accept features with a given strandedness.
397   *
398   * @author Matthew Pocock
399   * @since 1.1
400   */
401  public final static class StrandFilter implements OptimizableFilter {
402    private StrandedFeature.Strand strand;
403
404    /**
405     * Build a new filter that matches all features of a given strand.
406     *
407     * @param strand the Strand to match
408     */
409    public StrandFilter(StrandedFeature.Strand strand) {
410      this.strand = strand;
411    }
412
413    /**
414     * Retrieve the strand this matches.
415     *
416     * @return the Strand matched
417     */
418    public StrandedFeature.Strand getStrand() {
419      return strand;
420    }
421
422    /**
423     * Accept the Feature if it is an instance of StrandedFeature and matches
424     * the value of getStrand().
425     *
426     * @param f the Feature to check
427     * @return true if the strand matches, or false otherwise
428     */
429    public boolean accept(Feature f) {
430      if(f instanceof StrandedFeature) {
431        StrandedFeature sf = (StrandedFeature) f;
432        return sf.getStrand() == strand;
433      } else {
434        return strand == StrandedFeature.UNKNOWN;
435      }
436    }
437
438    public boolean equals(Object o) {
439      return
440        (o instanceof StrandFilter) &&
441        (((StrandFilter) o).getStrand() == this.getStrand());
442    }
443
444    public int hashCode() {
445      return getStrand().hashCode();
446    }
447
448    public String toString() {
449      return "StrandedFilter(" + strand + ")";
450    }
451
452    public boolean isProperSubset(FeatureFilter sup) {
453      return this.equals(sup);
454    }
455
456    public boolean isDisjoint(FeatureFilter filt) {
457      return (filt instanceof AcceptNoneFilter) || (
458        (filt instanceof StrandFilter) &&
459        ((StrandFilter) filt).getStrand() == getStrand()
460      );
461    }
462  }
463
464  /**
465   * Accept features that reside on a sequence with a particular name.
466   *
467   * @author Matthew Pocock
468   * @since 1.3
469   */
470  public final static class BySequenceName
471  implements OptimizableFilter {
472    private String seqName;
473
474    public BySequenceName(String seqName) {
475      this.seqName = seqName;
476    }
477
478    public String getSequenceName() {
479      return seqName;
480    }
481
482    public boolean accept(Feature f) {
483      return f.getSequence().getName().equals(seqName);
484    }
485
486    public boolean isProperSubset(FeatureFilter sup) {
487      return equals(sup);
488    }
489
490    public boolean isDisjoint(FeatureFilter filt) {
491        if (filt instanceof BySequenceName) {
492            return !equals(this);
493        } else {
494            return false;
495        }
496    }
497
498    public boolean equals(Object o) {
499      return
500       (o instanceof BySequenceName) &&
501       ((BySequenceName) o).getSequenceName().equals(seqName);
502    }
503
504    public int hashCode() {
505      return seqName.hashCode();
506    }
507  }
508
509  /**
510   *  A filter that returns all features contained within a location.
511   *
512   * @author Matthew Pocock
513   * @since 1.0
514   */
515  public final static class ContainedByLocation implements OptimizableFilter {
516    private Location loc;
517
518    public Location getLocation() {
519      return loc;
520    }
521
522    /**
523     * Creates a filter that returns everything contained within loc.
524     *
525     * @param loc  the location that will contain the accepted features
526     */
527    public ContainedByLocation(Location loc) {
528        if (loc == null) {
529            throw new NullPointerException("Loc may not be null");
530        }
531        this.loc = loc;
532    }
533
534    /**
535     * Returns true if the feature is within this filter's location.
536     */
537    public boolean accept(Feature f) {
538      return loc.contains(f.getLocation());
539    }
540
541    public boolean equals(Object o) {
542      return
543        (o instanceof ContainedByLocation) &&
544        (((ContainedByLocation) o).getLocation().equals(this.getLocation()));
545    }
546
547    public int hashCode() {
548      return getLocation().hashCode();
549    }
550
551    public boolean isProperSubset(FeatureFilter sup) {
552      if(sup instanceof ContainedByLocation) {
553        Location supL = ((ContainedByLocation) sup).getLocation();
554        return supL.contains(this.getLocation());
555      } else if(sup instanceof OverlapsLocation) {
556        Location supL = ((OverlapsLocation) sup).getLocation();
557        return supL.contains(this.getLocation());
558      } else if (sup instanceof ShadowOverlapsLocation) {
559        Location supL = ((ShadowOverlapsLocation) sup).getLocation();
560        return supL.contains(this.getLocation());
561      } else if (sup instanceof ShadowContainedByLocation) {
562        Location supL = ((ShadowContainedByLocation) sup).getLocation();
563        return supL.contains(this.getLocation());
564      }
565      return (sup instanceof AcceptAllFilter);
566    }
567
568    public boolean isDisjoint(FeatureFilter filt) {
569      if(filt instanceof ContainedByLocation) {
570        Location loc = ((ContainedByLocation) filt).getLocation();
571        return !getLocation().overlaps(loc);
572      } else if (filt instanceof OverlapsLocation) {
573            Location filtL = ((OverlapsLocation) filt).getLocation();
574            return !filtL.overlaps(this.getLocation());
575      } else if (filt instanceof ShadowOverlapsLocation) {
576        Location filtL = ((ShadowOverlapsLocation) filt).getLocation();
577        return filtL.getMax() < loc.getMin() || filtL.getMin() > loc.getMax();
578      } else if (filt instanceof ShadowContainedByLocation) {
579        Location filtL = ((ShadowContainedByLocation) filt).getLocation();
580        return filtL.getMax() < loc.getMin() || filtL.getMin() > loc.getMax();
581      }
582
583      return (filt instanceof AcceptNoneFilter);
584    }
585
586    public String toString() {
587      return "ContainedBy(" + loc + ")";
588    }
589  }
590
591  /**
592   *  A filter that returns all features overlapping a location.
593   *
594   * @author Matthew Pocock
595   * @since 1.0
596   */
597  public final static class OverlapsLocation implements OptimizableFilter {
598    private Location loc;
599
600    public Location getLocation() {
601      return loc;
602    }
603
604    /**
605     * Creates a filter that returns everything overlapping loc.
606     *
607     * @param loc  the location that will overlap the accepted features
608     */
609    public OverlapsLocation(Location loc) {
610        if (loc == null) {
611            throw new NullPointerException("Loc may not be null");
612        }
613        this.loc = loc;
614    }
615
616    /**
617     * Returns true if the feature overlaps this filter's location.
618     */
619    public boolean accept(Feature f) {
620      return loc.overlaps(f.getLocation());
621    }
622
623    public boolean equals(Object o) {
624      return
625        (o instanceof OverlapsLocation) &&
626        (((OverlapsLocation) o).getLocation().equals(this.getLocation()));
627    }
628
629    public int hashCode() {
630      return getLocation().hashCode();
631    }
632
633    public boolean isProperSubset(FeatureFilter sup) {
634      if(sup instanceof OverlapsLocation) {
635          Location supL = ((OverlapsLocation) sup).getLocation();
636          return supL.contains(this.getLocation());
637      } else if (sup instanceof ShadowOverlapsLocation) {
638          Location supL = ((ShadowOverlapsLocation) sup).getLocation();
639          return supL.contains(this.getLocation());
640      }
641      return (sup instanceof AcceptAllFilter);
642    }
643
644    public boolean isDisjoint(FeatureFilter filt) {
645      if (filt instanceof ContainedByLocation)  {
646        Location loc = ((ContainedByLocation) filt).getLocation();
647        return !getLocation().overlaps(loc);
648      } else if (filt instanceof ShadowContainedByLocation) {
649        Location loc = ((ShadowContainedByLocation) filt).getLocation();
650        return getLocation().getMax() < loc.getMin() || getLocation().getMin() > loc.getMax();
651      }
652      return (filt instanceof AcceptNoneFilter);
653    }
654
655    public String toString() {
656      return "Overlaps(" + loc + ")";
657    }
658  }
659
660  /**
661   *  A filter that accepts all features whose shadow overlaps a specified
662   * <code>Location</code>.  The shadow is defined as the interval between the
663   * minimum and maximum positions of the feature's location.  For features
664   * with contiguous locations, this filter is equivalent to
665   * <code>FeatureFilter.OverlapsLocation</code>..
666   *
667   * <p>
668   * A typical use of this filter is in graphics code where you are rendering
669   * features with non-contiguous locations in a `blocks and connectors' style,
670   * and wish to draw the connector even when no blocks fall within the
671   * selected field of view
672   * </p>
673   *
674   * @author Thomas Down
675   * @since 1.3
676   */
677
678  public final static class ShadowOverlapsLocation implements OptimizableFilter {
679    private Location loc;
680
681    public Location getLocation() {
682      return loc;
683    }
684
685    /**
686     * Creates a filter that returns everything overlapping loc.
687     *
688     * @param loc  the location that will overlap the accepted features
689     */
690    public ShadowOverlapsLocation(Location loc) {
691        if (loc == null) {
692            throw new NullPointerException("Loc may not be null");
693        }
694        this.loc = loc;
695    }
696
697    /**
698     * Returns true if the feature overlaps this filter's location.
699     */
700    public boolean accept(Feature f) {
701        Location floc = f.getLocation();
702        if (!floc.isContiguous()) {
703            floc = new RangeLocation(floc.getMin(), floc.getMax());
704        }
705        return loc.overlaps(floc);
706    }
707
708    public boolean equals(Object o) {
709      return
710        (o instanceof ShadowOverlapsLocation) &&
711        (((ShadowOverlapsLocation) o).getLocation().equals(this.getLocation()));
712    }
713
714    public int hashCode() {
715      return getLocation().hashCode() +77;
716    }
717
718    public boolean isProperSubset(FeatureFilter sup) {
719      if(sup instanceof ShadowOverlapsLocation) {
720        Location supL = ((ShadowOverlapsLocation) sup).getLocation();
721        return supL.contains(this.getLocation());
722      }
723      return (sup instanceof AcceptAllFilter);
724    }
725
726    public boolean isDisjoint(FeatureFilter filt) {
727      if (filt instanceof ShadowContainedByLocation)  {
728          Location loc = ((ShadowContainedByLocation) filt).getLocation();
729          return !getLocation().overlaps(loc);
730      }  else if (filt instanceof ContainedByLocation) {
731          Location loc = ((ContainedByLocation) filt).getLocation();
732          return (loc.getMax() < getLocation().getMin() || loc.getMin() > getLocation().getMax());
733      }
734      return (filt instanceof AcceptNoneFilter);
735    }
736
737    public String toString() {
738      return "ShadowOverlaps(" + loc + ")";
739    }
740  }
741
742  /**
743   *  A filter that accepts all features whose shadow is contained by a specified
744   * <code>Location</code>.  The shadow is defined as the interval between the
745   * minimum and maximum positions of the feature's location.  For features
746   * with contiguous locations, this filter is equivalent to
747   * <code>FeatureFilter.ContainedByLocation</code>.
748   *
749   * @author Thomas Down
750   * @since 1.3
751   */
752
753  public final static class ShadowContainedByLocation implements OptimizableFilter {
754    private Location loc;
755
756    public Location getLocation() {
757      return loc;
758    }
759
760    /**
761     * Creates a filter that returns everything contained within loc.
762     *
763     * @param loc  the location that will contain the accepted features
764     */
765    public ShadowContainedByLocation(Location loc) {
766        if (loc == null) {
767            throw new NullPointerException("Loc may not be null");
768        }
769        this.loc = loc;
770    }
771
772    /**
773     * Returns true if the feature is within this filter's location.
774     */
775    public boolean accept(Feature f) {
776        Location floc = f.getLocation();
777        if (!floc.isContiguous()) {
778            floc = new RangeLocation(floc.getMin(), floc.getMax());
779        }
780        return loc.contains(floc);
781    }
782
783    public boolean equals(Object o) {
784      return
785        (o instanceof ShadowContainedByLocation) &&
786        (((ShadowContainedByLocation) o).getLocation().equals(this.getLocation()));
787    }
788
789    public int hashCode() {
790      return getLocation().hashCode() + 88;
791    }
792
793    public boolean isProperSubset(FeatureFilter sup) {
794      if(sup instanceof ShadowContainedByLocation) {
795        Location supL = ((ShadowContainedByLocation) sup).getLocation();
796        return supL.contains(this.getLocation());
797      } else if (sup instanceof ShadowOverlapsLocation) {
798        Location supL = ((ShadowOverlapsLocation) sup).getLocation();
799        return supL.contains(this.getLocation());
800      }
801      return (sup instanceof AcceptAllFilter);
802    }
803
804    public boolean isDisjoint(FeatureFilter filt) {
805      if(filt instanceof ContainedByLocation) {
806        Location filtL = ((ShadowContainedByLocation) filt).getLocation();
807        return filtL.getMax() < loc.getMin() || filtL.getMin() > loc.getMax();
808      } if(filt instanceof ShadowContainedByLocation) {
809        Location loc = ((ShadowContainedByLocation) filt).getLocation();
810        return !getLocation().overlaps(loc);
811      } else if (filt instanceof OverlapsLocation) {
812            Location filtL = ((OverlapsLocation) filt).getLocation();
813            return filtL.getMax() < loc.getMin() || filtL.getMin() > loc.getMax();
814      } else if (filt instanceof ShadowOverlapsLocation) {
815        Location filtL = ((ShadowOverlapsLocation) filt).getLocation();
816        return !filtL.overlaps(this.getLocation());
817      }
818
819      return (filt instanceof AcceptNoneFilter);
820    }
821
822    public String toString() {
823      return "ShadowContainedBy(" + loc + ")";
824    }
825  }
826
827  /**
828   * A filter that returns all features that have an annotation bundle that is of a given
829   * annotation type.
830   *
831   * @author Matthew Pocock
832   * @author Thomas Down
833   * @since 1.3
834   */
835  public static class ByAnnotationType
836  implements OptimizableFilter {
837    private AnnotationType type;
838
839    protected ByAnnotationType() {
840      this(AnnotationType.ANY);
841    }
842
843    public ByAnnotationType(AnnotationType type) {
844      this.type = type;
845    }
846
847    public AnnotationType getType() {
848      return type;
849    }
850
851    protected void setType(AnnotationType type) {
852      this.type = type;
853    }
854
855    public boolean accept(Feature f) {
856      return type.instanceOf(f.getAnnotation());
857    }
858
859    public boolean equals(Object o) {
860      if(o instanceof ByAnnotationType) {
861        ByAnnotationType that = (ByAnnotationType) o;
862        return this.getType() == that.getType();
863      }
864
865      return false;
866    }
867
868    public int hashCode() {
869      return getType().hashCode();
870    }
871
872    public boolean isDisjoint(FeatureFilter filter) {
873      if(filter instanceof AcceptNoneFilter) {
874        return true;
875      } else if(filter instanceof ByAnnotationType) {
876        // check for common property names
877        ByAnnotationType that = (ByAnnotationType) filter;
878        Set props = that.getType().getProperties();
879        Set ourProps = this.getType().getProperties();
880        Set allProps = new HashSet(props);
881        allProps.addAll(ourProps);
882        for(Iterator i = allProps.iterator(); i.hasNext(); ) {
883          Object prop = i.next();
884
885          CollectionConstraint thisC = this.getType().getConstraint(prop);
886          CollectionConstraint thatC = that.getType().getConstraint(prop);
887          if (AnnotationTools.intersection(thisC, thatC) == CollectionConstraint.NONE) {
888              return true;
889          }
890        }
891      }
892
893      return false;
894    }
895
896    public boolean isProperSubset(FeatureFilter filter) {
897      if(filter instanceof ByAnnotationType) {
898        ByAnnotationType that = (ByAnnotationType) filter;
899
900        Set thisProps = this.getType().getProperties();
901        Set thatProps = that.getType().getProperties();
902        for(Iterator i = thatProps.iterator(); i.hasNext(); ) {
903          Object prop = i.next();
904
905          if(!thisProps.contains(prop)) {
906            return false;
907          }
908
909          CollectionConstraint thisP = this.getType().getConstraint(prop);
910          CollectionConstraint thatP = that.getType().getConstraint(prop);
911
912          if(
913            !thatP.subConstraintOf(thisP)
914          ) {
915            return false;
916          }
917        }
918
919        return true;
920      }
921
922      return false;
923    }
924
925    public String toString() {
926      return "ByAnnotationType {" + type + "}";
927    }
928  }
929
930  /**
931   * Retrieve features that contain a given annotation with a given value.
932   *
933   * @author Matthew Pocock
934   * @author Keith James
935   * @since 1.1
936   */
937  public final static class ByAnnotation
938  extends ByAnnotationType {
939    private Object key;
940    private Object value;
941
942    /**
943     * Make a new ByAnnotation that will accept features with an annotation
944     * bundle containing 'value' associated with 'key'.
945     *
946     * @param key  the Object used as a key in the annotation
947     * @param value the Object associated with key in the annotation
948     */
949    public ByAnnotation(Object key, Object value) {
950      this.key = key;
951      this.value = value;
952
953      AnnotationType.Impl type = new AnnotationType.Impl();
954      type.setConstraints(
955        key,
956        new PropertyConstraint.ExactValue(value),
957        CardinalityConstraint.ONE
958      );
959      setType(type);
960    }
961
962    public Object getKey() {
963      return key;
964    }
965
966    public Object getValue() {
967      return value;
968    }
969  }
970
971  /**
972   * Retrieve features that contain a given annotation, and that the set of values
973   * contains the value given.
974   *
975   * @author Thomas Down
976   * @since 1.3
977   */
978  public final static class AnnotationContains
979  extends ByAnnotationType {
980    private Object key;
981    private Object value;
982
983    /**
984     * Make a new AnnotationContains that will accept features with an annotation
985     * bundle where the value-set assosiated with the property <code>key</code>
986     * contains a member equal to <code>value</code>.
987     *
988     * @param key  the Object used as a key in the annotation
989     * @param value the Object associated with key in the annotation
990     */
991    public AnnotationContains(Object key, Object value) {
992      this.key = key;
993      this.value = value;
994
995      AnnotationType.Impl type = new AnnotationType.Impl();
996      type.setConstraint(
997        key,
998        new CollectionConstraint.Contains(
999            new PropertyConstraint.ExactValue(value),
1000            CardinalityConstraint.ONE
1001        )
1002      );
1003      setType(type);
1004    }
1005
1006    public Object getKey() {
1007      return key;
1008    }
1009
1010    public Object getValue() {
1011      return value;
1012    }
1013  }
1014
1015  /**
1016   * Retrieve features that contain a given annotation with any value.
1017   *
1018   * @author Matthew Pocock
1019   * @author Keith James
1020   * @since 1.1
1021   */
1022  public final static class HasAnnotation
1023  extends ByAnnotationType {
1024    private Object key;
1025
1026    /**
1027     * Make a new ByAnnotation that will accept features with an annotation
1028     * bundle containing any value associated with 'key'.
1029     *
1030     * @param key  the Object used as a key in the annotation
1031     */
1032    public HasAnnotation(Object key) {
1033      this.key = key;
1034
1035      AnnotationType.Impl type = new AnnotationType.Impl();
1036      type.setConstraints(
1037        key,
1038        PropertyConstraint.ANY,
1039        CardinalityConstraint.ONE_OR_MORE
1040      );
1041      setType(type);
1042    }
1043
1044    public Object getKey() {
1045      return key;
1046    }
1047  }
1048
1049    /**
1050     * Filter by applying a nested <code>FeatureFilter</code> to the
1051     * parent feature.  Always <code>false</code> if the parent
1052     * is not a feature (e.g. top-level features, where the
1053     * parent is a sequence).
1054     *
1055     * @author Thomas Down
1056     * @since 1.2
1057     */
1058
1059    public static class ByParent implements OptimizableFilter, Up {
1060      static { WalkerFactory.getInstance().addTypeWithParent(ByParent.class); }
1061
1062        private FeatureFilter filter;
1063
1064        public ByParent(FeatureFilter ff) {
1065            filter = ff;
1066        }
1067
1068        public FeatureFilter getFilter() {
1069            return filter;
1070        }
1071
1072        public boolean accept(Feature f) {
1073            FeatureHolder fh = f.getParent();
1074            if (fh instanceof Feature) {
1075                return filter.accept((Feature) fh);
1076            }
1077
1078            return false;
1079        }
1080
1081        public int hashCode() {
1082            return filter.hashCode() + 173;
1083        }
1084
1085        public boolean equals(Object o) {
1086            if (! (o instanceof FeatureFilter.ByParent)) {
1087                return false;
1088            }
1089
1090            FeatureFilter.ByParent ffbp = (FeatureFilter.ByParent) o;
1091            return ffbp.getFilter().equals(filter);
1092        }
1093
1094        public boolean isProperSubset(FeatureFilter ff) {
1095            FeatureFilter ancFilter = null;
1096            if (ff instanceof FeatureFilter.ByParent) {
1097                ancFilter = ((FeatureFilter.ByParent) ff).getFilter();
1098            } else if (ff instanceof FeatureFilter.ByAncestor) {
1099                ancFilter = ((FeatureFilter.ByAncestor) ff).getFilter();
1100            }
1101
1102            if (ancFilter != null) {
1103                return FilterUtils.areProperSubset(ancFilter, filter);
1104            } else {
1105                return false;
1106            }
1107        }
1108
1109        public boolean isDisjoint(FeatureFilter ff) {
1110            // System.err.println("Disjunction test for " + toString());
1111            // System.err.println("Against " + ff.toString());
1112            if (ff instanceof IsTopLevel) {
1113                return true;
1114            } else if (ff instanceof FeatureFilter.ByParent) {
1115                return FilterUtils.areDisjoint(
1116                        ((FeatureFilter.ByParent) ff).getFilter(),
1117                        getFilter()
1118                );
1119            }  else if (ff instanceof FeatureFilter.ByAncestor) {
1120                return FilterUtils.areDisjoint(
1121                        ((FeatureFilter.ByAncestor) ff).getFilter(),
1122                        getFilter()
1123                );
1124            } else {
1125                FeatureFilter childFilter = FilterUtils.getOnlyChildrenFilter(getFilter());
1126                if (childFilter != null) {
1127                    return FilterUtils.areDisjoint(
1128                            childFilter,
1129                            ff
1130                    );
1131                }
1132            }
1133
1134            return false;
1135        }
1136    }
1137
1138    /**
1139     * Filter by applying a nested <code>FeatureFilter</code> to all
1140     * ancestor features.  Returns <code>true</code> if at least one
1141     * of them matches the filter.  Always <code>false</code> if the
1142     * parent is not a feature (e.g. top-level features, where the
1143     * parent is a sequence).
1144     *
1145     * @author Thomas Down
1146     * @since 1.2
1147     */
1148
1149    public static class ByAncestor implements OptimizableFilter, Up {
1150      static { WalkerFactory.getInstance().addTypeWithParent(ByAncestor.class); }
1151
1152        private FeatureFilter filter;
1153
1154        public ByAncestor(FeatureFilter ff) {
1155            filter = ff;
1156        }
1157
1158        public FeatureFilter getFilter() {
1159            return filter;
1160        }
1161
1162        public boolean accept(Feature f) {
1163            do {
1164                FeatureHolder fh = f.getParent();
1165                if (fh instanceof Feature) {
1166                    f = (Feature) fh;
1167                    if (filter.accept(f)) {
1168                        return true;
1169                    }
1170                } else {
1171                    return false;
1172                }
1173            } while (true);
1174        }
1175
1176        public int hashCode() {
1177            return filter.hashCode() + 186;
1178        }
1179
1180        public boolean equals(Object o) {
1181            if (! (o instanceof FeatureFilter.ByAncestor)) {
1182                return false;
1183            }
1184
1185            FeatureFilter.ByAncestor ffba = (FeatureFilter.ByAncestor) o;
1186            return ffba.getFilter().equals(filter);
1187        }
1188
1189        public boolean isProperSubset(FeatureFilter ff) {
1190            FeatureFilter ancFilter = null;
1191            if (ff instanceof FeatureFilter.ByAncestor) {
1192                ancFilter = ((FeatureFilter.ByAncestor) ff).getFilter();
1193            }
1194
1195            if (ancFilter != null) {
1196                return FilterUtils.areProperSubset(ancFilter, filter);
1197            } else {
1198                return false;
1199            }
1200        }
1201
1202        public boolean isDisjoint(FeatureFilter ff) {
1203            // System.err.println("Disjunction test for " + toString());
1204            // System.err.println("Against " + ff.toString());
1205            if (ff instanceof IsTopLevel) {
1206                return true;
1207            }
1208
1209            if (ff instanceof FeatureFilter.ByParent) {
1210                return FilterUtils.areDisjoint(
1211                        ((FeatureFilter.ByParent) ff).getFilter(),
1212                        getFilter()
1213                );
1214            }  else if (ff instanceof FeatureFilter.ByAncestor) {
1215                return FilterUtils.areDisjoint(
1216                        ((FeatureFilter.ByAncestor) ff).getFilter(),
1217                        getFilter()
1218                );
1219            } else {
1220                FeatureFilter descFilter = FilterUtils.getOnlyDescendantsFilter(getFilter());
1221                if (descFilter != null) {
1222                    return FilterUtils.areDisjoint(
1223                            descFilter,
1224                            ff
1225                    );
1226                }
1227
1228                FeatureFilter childFilter = FilterUtils.getOnlyChildrenFilter(getFilter());
1229                if (childFilter != null) {
1230                    // System.err.println("Child filter is: " + childFilter.toString());
1231                    if (FilterUtils.areProperSubset(childFilter, leaf)) {
1232                        // System.err.println("Leaf case");
1233                        return FilterUtils.areDisjoint(
1234                                childFilter,
1235                                ff
1236                        );
1237                    } else {
1238                        // System.err.println("Tree case");
1239                        return FilterUtils.areDisjoint(
1240                                new FeatureFilter.Or(
1241                                        childFilter,
1242                                        new FeatureFilter.ByAncestor(childFilter)
1243                                ),
1244                                ff
1245                        );
1246                    }
1247                }
1248            }
1249
1250            return false;
1251        }
1252
1253        public String toString() {
1254            return "ByAncestor(" + getFilter().toString() + ")";
1255        }
1256    }
1257
1258    /**
1259     * Accepts features where all immediate children meet the supplied filter.  This
1260     * will be <code>true</code> in the case where no child features exist.  Mainly useful
1261     * for defining schemas of feature-trees.
1262     *
1263     * @author Thomas Down
1264     * @since 1.3
1265     */
1266
1267    public static class OnlyChildren implements OptimizableFilter, ByHierarchy {
1268      static { WalkerFactory.getInstance().addTypeWithParent(OnlyChildren.class); }
1269
1270        private FeatureFilter filter;
1271
1272        public OnlyChildren(FeatureFilter ff) {
1273            this.filter = ff;
1274        }
1275
1276        public FeatureFilter getFilter() {
1277            return filter;
1278        }
1279
1280        public boolean accept(Feature f) {
1281            for (Iterator i = f.features(); i.hasNext(); ) {
1282                if (!filter.accept((Feature) i.next())) {
1283                    return false;
1284                }
1285            }
1286            return true;
1287        }
1288
1289        public int hashCode() {
1290            return filter.hashCode() + 762;
1291        }
1292
1293        public boolean equals(Object o) {
1294            if (! (o instanceof FeatureFilter.OnlyChildren)) {
1295                return false;
1296            }
1297
1298            FeatureFilter.OnlyChildren ffoc = (FeatureFilter.OnlyChildren) o;
1299            return ffoc.getFilter().equals(filter);
1300        }
1301
1302        public boolean isProperSubset(FeatureFilter ff) {
1303            if (ff == FeatureFilter.all) {
1304                return true;
1305            } else if (ff instanceof OnlyChildren) {
1306                return FilterUtils.areProperSubset(
1307                    getFilter(),
1308                    ((OnlyChildren) ff).getFilter()
1309                ) ;
1310            } else if (ff instanceof OnlyDescendants) {
1311                return FilterUtils.areProperSubset(
1312                    getFilter(),
1313                    ((OnlyDescendants) ff).getFilter()
1314                ) ;
1315            } else {
1316                return false;
1317            }
1318        }
1319
1320        public boolean isDisjoint(FeatureFilter ff) {
1321            if (ff instanceof ByChild) {
1322                return FilterUtils.areDisjoint(
1323                    getFilter(),
1324                    ((ByChild) ff).getFilter()
1325                );
1326            } else {
1327                return false;
1328            }
1329        }
1330
1331        public String toString() {
1332            return "OnlyChildren(" + filter.toString() + ")";
1333        }
1334    }
1335
1336    /**
1337     * Accepts features where all descendants meet the supplied filter.  This
1338     * will be <code>true</code> in the case where no child features exist.  Mainly useful
1339     * for defining schemas of feature-trees.
1340     *
1341     * @author Thomas Down
1342     * @since 1.3
1343     */
1344
1345    public static class OnlyDescendants implements OptimizableFilter, ByHierarchy {
1346      static { WalkerFactory.getInstance().addTypeWithParent(OnlyDescendants.class); }
1347
1348        private FeatureFilter filter;
1349
1350        public OnlyDescendants(FeatureFilter ff) {
1351            this.filter = ff;
1352        }
1353
1354        public FeatureFilter getFilter() {
1355            return filter;
1356        }
1357
1358        public boolean accept(Feature f) {
1359            return f.filter(FeatureFilter.all).countFeatures() == f.filter(filter).countFeatures();
1360        }
1361
1362        public int hashCode() {
1363            return filter.hashCode() + 763;
1364        }
1365
1366        public boolean equals(Object o) {
1367            if (! (o instanceof FeatureFilter.OnlyDescendants)) {
1368                return false;
1369            }
1370
1371            FeatureFilter.OnlyDescendants ffoc = (FeatureFilter.OnlyDescendants) o;
1372            return ffoc.getFilter().equals(filter);
1373        }
1374
1375
1376        public boolean isProperSubset(FeatureFilter ff) {
1377            if (ff == FeatureFilter.all) {
1378                return true;
1379            } else if (ff instanceof OnlyDescendants) {
1380                return FilterUtils.areProperSubset(
1381                    getFilter(),
1382                    ((OnlyDescendants) ff).getFilter()
1383                ) ;
1384            } else {
1385                return false;
1386            }
1387        }
1388
1389        public boolean isDisjoint(FeatureFilter ff) {
1390            if (ff instanceof ByChild) {
1391                return FilterUtils.areDisjoint(
1392                    getFilter(),
1393                    ((ByChild) ff).getFilter()
1394                );
1395            } else if (ff instanceof ByDescendant) {
1396                return FilterUtils.areDisjoint(
1397                    getFilter(),
1398                    ((ByDescendant) ff).getFilter()
1399                );
1400            } else {
1401                return false;
1402            }
1403        }
1404    }
1405
1406    /**
1407     * Filter by applying a nested <code>FeatureFilter</code> to the
1408     * child features.  Always <code>false</code> if there are no children.
1409     *
1410     * @author Matthew Pocock
1411     * @author Thomas Down
1412     * @since 1.3
1413     */
1414
1415    public static class ByChild implements OptimizableFilter, Down {
1416      static { WalkerFactory.getInstance().addTypeWithParent(ByChild.class); }
1417
1418        private FeatureFilter filter;
1419
1420        public ByChild(FeatureFilter ff) {
1421            filter = ff;
1422        }
1423
1424        public FeatureFilter getFilter() {
1425            return filter;
1426        }
1427
1428        public boolean accept(Feature f) {
1429          for(Iterator i = f.features(); i.hasNext(); ) {
1430            if(filter.accept((Feature) i.next())) {
1431              return true;
1432            }
1433          }
1434
1435          return false;
1436        }
1437
1438        public int hashCode() {
1439            return filter.hashCode() + 173;
1440        }
1441
1442        public boolean equals(Object o) {
1443            if (! (o instanceof FeatureFilter.ByChild)) {
1444                return false;
1445            }
1446
1447            FeatureFilter.ByChild ffbc = (FeatureFilter.ByChild) o;
1448            return ffbc.getFilter().equals(filter);
1449        }
1450
1451        public boolean isProperSubset(FeatureFilter ff) {
1452            FeatureFilter descFilter = null;
1453            if (ff instanceof FeatureFilter.ByChild) {
1454                descFilter = ((FeatureFilter.ByChild) ff).getFilter();
1455            } else if (ff instanceof FeatureFilter.ByDescendant) {
1456                descFilter = ((FeatureFilter.ByDescendant) ff).getFilter();
1457            }
1458
1459            if (descFilter != null) {
1460                return FilterUtils.areProperSubset(descFilter, filter);
1461            } else {
1462                return false;
1463            }
1464        }
1465
1466        public boolean isDisjoint(FeatureFilter ff) {
1467            if (ff instanceof OnlyChildren) {
1468                return FilterUtils.areDisjoint(
1469                    getFilter(),
1470                    ((OnlyChildren) ff).getFilter()
1471                );
1472            } else if (ff instanceof OnlyDescendants) {
1473                return FilterUtils.areDisjoint(
1474                    getFilter(),
1475                    ((OnlyDescendants) ff).getFilter()
1476                );
1477            } else {
1478                return false;
1479            }
1480        }
1481    }
1482
1483
1484    /**
1485     * Filter by applying a nested <code>FeatureFilter</code> to all
1486     * descendant features.  Returns <code>true</code> if at least one
1487     * of them matches the filter.  Always <code>false</code> if the
1488     * feature has no children.
1489     *
1490     * @author Matthew Pocock
1491     * @author Thomas Down
1492     * @since 1.2
1493     */
1494
1495    public static class ByDescendant implements OptimizableFilter, Down {
1496      static { WalkerFactory.getInstance().addTypeWithParent(ByDescendant.class); }
1497
1498        private FeatureFilter filter;
1499
1500        public ByDescendant(FeatureFilter ff) {
1501            filter = ff;
1502        }
1503
1504        public FeatureFilter getFilter() {
1505            return filter;
1506        }
1507
1508        public boolean accept(Feature f) {
1509            do {
1510                FeatureHolder fh = f.getParent();
1511                if (fh instanceof Feature) {
1512                    f = (Feature) fh;
1513                    if (filter.accept(f)) {
1514                        return true;
1515                    }
1516                } else {
1517                    return false;
1518                }
1519            } while (true);
1520        }
1521
1522        public int hashCode() {
1523            return filter.hashCode() + 186;
1524        }
1525
1526        public boolean equals(Object o) {
1527            if (! (o instanceof FeatureFilter.ByDescendant)) {
1528                return false;
1529            }
1530
1531            FeatureFilter.ByDescendant ffba = (FeatureFilter.ByDescendant) o;
1532            return ffba.getFilter().equals(filter);
1533        }
1534
1535        public boolean isProperSubset(FeatureFilter ff) {
1536            FeatureFilter ancFilter = null;
1537            if (ff instanceof FeatureFilter.ByDescendant) {
1538                ancFilter = ((FeatureFilter.ByDescendant) ff).getFilter();
1539            }
1540
1541            if (ancFilter != null) {
1542                return FilterUtils.areProperSubset(ancFilter, filter);
1543            } else {
1544                return false;
1545            }
1546        }
1547
1548        public boolean isDisjoint(FeatureFilter ff) {
1549           if (ff instanceof OnlyDescendants) {
1550                return FilterUtils.areDisjoint(
1551                    getFilter(),
1552                    ((OnlyDescendants) ff).getFilter()
1553                );
1554            } else {
1555                return false;
1556            }
1557        }
1558    }
1559
1560  /**
1561   * Accept features with a given reading frame.
1562   *
1563   * @author Mark Schreiber
1564   * @since 1.2
1565   */
1566  public final static class FrameFilter implements OptimizableFilter {
1567    private FramedFeature.ReadingFrame frame;
1568
1569    /**
1570     * Build a new filter that matches all features of a reading frame.
1571     *
1572     * @param frame the ReadingFrame to match
1573     */
1574    public FrameFilter(FramedFeature.ReadingFrame frame) {
1575      this.frame = frame;
1576    }
1577
1578    /**
1579     * Retrieve the reading frame this filter matches.
1580     */
1581     public FramedFeature.ReadingFrame getFrame(){
1582       return frame;
1583     }
1584
1585    /**
1586     * Accept the Feature if it is an instance of FramedFeature and matches
1587     * the value of getFrame().
1588     *
1589     * @param f the Feature to check
1590     * @return true if the frame matches, or false otherwise
1591     */
1592    public boolean accept(Feature f) {
1593      if(f instanceof FramedFeature) {
1594        FramedFeature ff = (FramedFeature) f;
1595        return ff.getReadingFrame() == frame;
1596      } else {
1597        return false;
1598      }
1599    }
1600
1601    public int hashCode() {
1602        return frame.getFrame() + 99;
1603    }
1604
1605    public boolean equals(Object o) {
1606      return (o instanceof FrameFilter && ((FrameFilter) o).getFrame() == getFrame());
1607    }
1608
1609    public boolean isProperSubset(FeatureFilter sup) {
1610      return this.equals(sup);
1611    }
1612
1613    public boolean isDisjoint(FeatureFilter filt) {
1614      return (filt instanceof AcceptNoneFilter) || (
1615        (filt instanceof FrameFilter) &&
1616        ((FrameFilter) filt).getFrame() == getFrame()
1617      );
1618    }
1619  }
1620
1621    /**
1622     * <code>ByPairwiseScore</code> is used to filter
1623     * <code>SimilarityPairFeature</code>s by their score. Features
1624     * are accepted if their score falls between the filter's minimum
1625     * and maximum values, inclusive. Features are rejected if they
1626     * are not <code>SimilarityPairFeature</code>s. The minimum value
1627     * accepted must be less than the maximum value.
1628     *
1629     * @author Keith James
1630     * @since 1.3
1631     */
1632    public static final class ByPairwiseScore implements OptimizableFilter {
1633        private double minScore;
1634        private double maxScore;
1635        private double score;
1636        private int    hashCode;
1637
1638        /**
1639         * Creates a new <code>ByPairwiseScore</code>.
1640         *
1641         * @param minScore a <code>double</code>.
1642         * @param maxScore a <code>double</code>.
1643         */
1644        public ByPairwiseScore(double minScore, double maxScore) {
1645            if (minScore > maxScore)
1646                throw new IllegalArgumentException("Filter minimum score must be less than maximum score");
1647
1648            this.minScore = minScore;
1649            this.maxScore = maxScore;
1650
1651            hashCode += (minScore == 0.0 ? 0L : Double.doubleToLongBits(minScore));
1652            hashCode += (maxScore == 0.0 ? 0L : Double.doubleToLongBits(maxScore));
1653        }
1654
1655        /**
1656         * Accept a Feature if it is an instance of
1657         * SimilarityPairFeature and its score is <= filter's minimum
1658         * score and >= filter's maximum score.
1659         *
1660         * @param f a <code>Feature</code>.
1661         * @return a <code>boolean</code>.
1662         */
1663        public boolean accept(Feature f) {
1664            if (! (f instanceof SimilarityPairFeature)) {
1665                return false;
1666            }
1667
1668            score = ((SimilarityPairFeature) f).getScore();
1669            return (score >= minScore &&
1670                    score <= maxScore);
1671        }
1672
1673        /**
1674         * <code>getMinScore</code> returns the minimum score
1675         * accepted.
1676         *
1677         * @return a <code>double</code>.
1678         */
1679        public double getMinScore() {
1680            return minScore;
1681        }
1682
1683        /**
1684         * <code>getMaxScore</code> returns the maximum score
1685         * accepted.
1686         *
1687         * @return a <code>double</code>.
1688         */
1689        public double getMaxScore() {
1690            return maxScore;
1691        }
1692
1693        public boolean equals(Object o) {
1694            if (o instanceof ByPairwiseScore) {
1695                ByPairwiseScore psf = (ByPairwiseScore) o;
1696                if (psf.getMinScore() == minScore &&
1697                    psf.getMaxScore() == maxScore) {
1698                    return true;
1699                }
1700            }
1701            return false;
1702        }
1703
1704        public int hashCode() {
1705            return hashCode;
1706        }
1707
1708        public boolean isProperSubset(FeatureFilter sup) {
1709            if (sup instanceof ByPairwiseScore) {
1710                ByPairwiseScore psf = (ByPairwiseScore) sup;
1711                return (psf.getMinScore() <= minScore &&
1712                        psf.getMaxScore() >= maxScore);
1713            }
1714            return false;
1715        }
1716
1717        public boolean isDisjoint(FeatureFilter filt) {
1718            if (filt instanceof AcceptNoneFilter)
1719                return true;
1720
1721            if (filt instanceof ByPairwiseScore) {
1722                ByPairwiseScore psf = (ByPairwiseScore) filt;
1723                return (psf.getMaxScore() < minScore ||
1724                        psf.getMinScore() > maxScore);
1725            }
1726            return false;
1727        }
1728
1729        public String toString() {
1730            return minScore + " >= score <= " + maxScore;
1731        }
1732    }
1733
1734    /**
1735     * Accepts features which are ComponentFeatures and have a <code>componentSequenceName</code>
1736     * property of the specified value.
1737     *
1738     * @author Thomas Down
1739     * @since 1.3
1740     */
1741
1742    public final static class ByComponentName implements OptimizableFilter {
1743        private String cname;
1744
1745        public ByComponentName(String cname) {
1746            this.cname = cname;
1747        }
1748
1749        public boolean accept(Feature f) {
1750            if (f instanceof ComponentFeature) {
1751                return cname.equals(((ComponentFeature) f).getComponentSequenceName());
1752            } else {
1753                return false;
1754            }
1755        }
1756
1757        public String getComponentName() {
1758            return cname;
1759        }
1760
1761        public boolean equals(Object o) {
1762            return (o instanceof ByComponentName) && ((ByComponentName) o).getComponentName().equals(cname);
1763        }
1764
1765        public int hashCode() {
1766            return getComponentName().hashCode();
1767        }
1768
1769        public boolean isProperSubset(FeatureFilter sup) {
1770            if (sup instanceof ByComponentName) {
1771                return equals(sup);
1772            } else if (sup instanceof ByClass) {
1773                return ((ByClass) sup).getTestClass().isAssignableFrom(ComponentFeature.class);
1774            } else {
1775                return (sup instanceof AcceptAllFilter);
1776            }
1777        }
1778
1779        public boolean isDisjoint(FeatureFilter feat) {
1780            if (feat instanceof ByComponentName) {
1781                return !equals(feat);
1782            } else if (feat instanceof ByClass) {
1783                Class featC = ((ByClass) feat).getTestClass();
1784                return ! (featC.isAssignableFrom(ComponentFeature.class));
1785            } else {
1786                return (feat instanceof AcceptNoneFilter);
1787            }
1788        }
1789
1790        public String toString() {
1791            return "ByComponentName(" + cname + ")";
1792        }
1793    }
1794
1795    /**
1796     * A filter which accepts only top-level Features.  This is true
1797     * is <code>getParent()</code> returns a <code>Sequence</code> instance.
1798     *
1799     * @since 1.3
1800     */
1801
1802    public static final FeatureFilter top_level = new IsTopLevel();
1803
1804    /**
1805     * A filter which accepts features with no children
1806     *
1807     * @since 1.3
1808     */
1809
1810    // public static final FeatureFilter leaf = new IsLeaf();
1811    public static final FeatureFilter leaf = new FeatureFilter.OnlyChildren(FeatureFilter.none);
1812
1813    // Note: this implements OptimizableFilter, but cheats :-).  Consequently,
1814    // other optimizablefilters don't know anything about it.  The convenience
1815    // methods on FilterUtils give ByFeature a higher precedence to make
1816    // sure this works out.
1817
1818    /**
1819     * Accept only features which are equal to the specified feature
1820     *
1821     * @author Thomas Down
1822     * @since 1.3
1823     */
1824
1825    public static final class ByFeature implements OptimizableFilter {
1826        private final Feature feature;
1827
1828        public ByFeature(Feature f) {
1829            this.feature = f;
1830        }
1831
1832        public Feature getFeature() {
1833            return feature;
1834        }
1835
1836        public boolean accept(Feature f) {
1837            return f.equals(feature);
1838        }
1839
1840        public boolean isProperSubset(FeatureFilter ff) {
1841            return ff.accept(feature);
1842        }
1843
1844        public boolean isDisjoint(FeatureFilter ff) {
1845            return !ff.accept(feature);
1846        }
1847
1848        public int hashCode() {
1849            return feature.hashCode() + 65;
1850        }
1851
1852        public boolean equals(Object o) {
1853            if (o instanceof FeatureFilter.ByFeature) {
1854                return ((FeatureFilter.ByFeature) o).getFeature().equals(feature);
1855            } else {
1856                return false;
1857            }
1858        }
1859    }
1860}
1861