001package org.biojava.bio.seq.projection;
002
003import org.biojava.bio.seq.FeatureFilter;
004import org.biojava.bio.seq.FeatureHolder;
005import org.biojava.bio.seq.FilterUtils;
006import org.biojava.bio.seq.StrandedFeature;
007import org.biojava.bio.symbol.Location;
008
009/**
010 * A ProjectionContext that translates and optionaly flips features.
011 *
012 * <p>
013 * Use this to 'reverse complement' a feature hierachy, or just to shift it
014 * sideways a bit.
015 * </p>
016 *
017 * <p>
018 * If the flipping mode is dissabled, then all translated features are projected
019 * as having locations equivalent to feat.getLocation().translate(translation).
020 * If the flipping mode is enabled, then all features are flipped arround
021 * translation so that translation-i becomes translation+i.
022 * </p>
023 *
024 * @author Matthew Pocock
025 */
026public class TranslateFlipContext
027extends ReparentContext {
028  // Fixme: we should have a simple constructor that takes a
029  // translation and a StrandedFeature.Strand and does the flip
030  private final int translation;
031  private final boolean oppositeStrand;
032
033  /**
034   * Create a new TranslateFlipContext with explicit translation and flip.
035   *
036   * <p>
037   * Locations will be mapped according to the rules in @link ProjectionUtils.
038   * </p>
039   *
040   * @param parent          the parent to graft all projected features onto
041   * @param wrapped         the featurs to project
042   * @param translate       the translation
043   * @param oppositeStrand  wether or not to flip
044   */
045  public TranslateFlipContext(FeatureHolder parent,
046                 FeatureHolder wrapped,
047                 int translate,
048                 boolean oppositeStrand)
049  {
050    super(parent, wrapped);
051    this.translation = translate;
052    this.oppositeStrand = oppositeStrand;
053  }
054
055  /**
056   * Create a new TranslateFlipContext that flips all featurs arround min and
057   * max.
058   *
059   * <p>
060   * A Location at exactly min will become one at max, and a Location at exactly
061   * max will become one at min.
062   * </p>
063   *
064   * <p>
065   * This is equivalent to
066   * <code>TranslateFlipContext(parent, wrapped, min + max, true)</code> and is
067   * provided to make client code more readable.
068   * </p>
069   *
070   * @param parent  the parent to graft all projected features ont
071   * @param wrapped the features to project
072   * @param min     the lower position
073   * @param max     the higher position
074   */
075  public TranslateFlipContext(FeatureHolder parent,
076                              FeatureHolder wrapped,
077                              int min,
078                              int max)
079  {
080    super(parent, wrapped);
081
082    if(min > max) {
083      throw new IllegalArgumentException("Max must not be less than min: " +
084                                         min + "," + max);
085    }
086
087    this.translation = min + max;
088    this.oppositeStrand = true;
089  }
090
091
092  /**
093   * Create a new TranslateFlipContext with translation only.
094   *
095   * <p>
096   * This is equivalent to
097   * <code>TranslateFlipContext(parent, wrapped, translation, false)</code> and
098   * is provided to make client code more readable.
099   * </p>
100   *
101   * @param parent          the parent to graft all projected features onto
102   * @param wrapped         the featurs to project
103   * @param translation       the translation
104   */
105  public TranslateFlipContext(FeatureHolder parent,
106                              FeatureHolder wrapped,
107                              int translation)
108  {
109    super(parent, wrapped);
110
111    this.translation = translation;
112    this.oppositeStrand = false;
113  }
114
115  public final int getTranslation() {
116    return translation;
117  }
118
119  public final boolean isOppositeStrand() {
120    return oppositeStrand;
121  }
122
123  public Location projectLocation(Location oldLoc) {
124    return oldLoc.newInstance(ProjectionUtils.transformLocation(
125            oldLoc, translation, oppositeStrand));
126  }
127
128  public final Location revertLocation(Location oldLoc) {
129    return oldLoc.newInstance(ProjectionUtils.revertLocation(
130            oldLoc, translation, oppositeStrand));
131  }
132
133  public final StrandedFeature.Strand projectStrand(StrandedFeature.Strand strand) {
134      if (oppositeStrand) {
135          return strand.flip();
136      } else {
137          return strand;
138      }
139  }
140
141  public final StrandedFeature.Strand revertStrand(StrandedFeature.Strand strand) {
142    return projectStrand(strand);
143  }
144
145  protected FilterUtils.FilterTransformer getTransformer() {
146    final FilterUtils.FilterTransformer delegate = super.getTransformer();
147
148    return new FilterUtils.FilterTransformer() {
149      public FeatureFilter transform(FeatureFilter ff) {
150        ff = delegate.transform(ff);
151
152        if (ff instanceof FeatureFilter.OverlapsLocation) {
153          return new FeatureFilter.OverlapsLocation(projectLocation(((FeatureFilter.OverlapsLocation) ff).getLocation()));
154        } else if (ff instanceof FeatureFilter.ContainedByLocation) {
155          return new FeatureFilter.ContainedByLocation(projectLocation(((FeatureFilter.ContainedByLocation) ff).getLocation()));
156        } else if (ff instanceof FeatureFilter.StrandFilter) {
157          return new FeatureFilter.StrandFilter(projectStrand(((FeatureFilter.StrandFilter) ff).getStrand()));
158        } else {
159          return ff;
160        }
161      }
162    };
163  }
164
165  protected FilterUtils.FilterTransformer getReverter() {
166    final FilterUtils.FilterTransformer delegate = super.getReverter();
167
168    return new FilterUtils.FilterTransformer() {
169      public FeatureFilter transform(FeatureFilter ff) {
170        ff = delegate.transform(ff);
171
172        if (ff instanceof FeatureFilter.OverlapsLocation) {
173          return new FeatureFilter.OverlapsLocation(revertLocation(((FeatureFilter.OverlapsLocation) ff).getLocation()));
174        } else if (ff instanceof FeatureFilter.ContainedByLocation) {
175          return new FeatureFilter.ContainedByLocation(revertLocation(((FeatureFilter.ContainedByLocation) ff).getLocation()));
176        } else if (ff instanceof FeatureFilter.StrandFilter) {
177          return new FeatureFilter.StrandFilter(revertStrand(((FeatureFilter.StrandFilter) ff).getStrand()));
178        } else {
179          return ff;
180        }
181      }
182    };
183  }
184}