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.gui.sequence;
023
024import java.awt.Graphics2D;
025import java.awt.event.MouseEvent;
026import java.util.HashSet;
027import java.util.List;
028import java.util.Set;
029
030import org.biojava.bio.seq.FeatureFilter;
031import org.biojava.bio.seq.FeatureHolder;
032import org.biojava.utils.AssertionFailure;
033import org.biojava.utils.ChangeEvent;
034import org.biojava.utils.ChangeListener;
035import org.biojava.utils.ChangeSupport;
036import org.biojava.utils.ChangeType;
037import org.biojava.utils.ChangeVetoException;
038import org.biojava.utils.Changeable;
039import org.biojava.utils.cache.CacheMap;
040import org.biojava.utils.cache.FixedSizeMap;
041
042/**
043 * @author Matthew Pocock
044 * @author Thomas Down
045 */
046public class FilteringRenderer
047extends SequenceRendererWrapper {
048  public static ChangeType FILTER = new ChangeType(
049    "The filter has changed",
050    "org.biojava.bio.gui.sequence.FilteringRenderer",
051    "FILTER",
052    SequenceRenderContext.LAYOUT
053  );
054  
055  public static ChangeType RECURSE = new ChangeType(
056    "The recurse flag has changed",
057    "org.biojava.bio.gui.sequence.FilteringRenderer",
058    "RECURSE",
059    SequenceRenderContext.LAYOUT
060  );
061
062  protected FeatureFilter filter;
063  protected boolean recurse;
064
065  protected boolean hasListeners() {
066    return super.hasListeners();
067  }
068
069  protected ChangeSupport getChangeSupport(ChangeType ct) {
070    return super.getChangeSupport(ct);
071  }
072  
073  public FilteringRenderer() {
074    filter = FeatureFilter.all;
075    recurse = false;
076  }
077  
078  public FilteringRenderer(
079    SequenceRenderer renderer,
080    FeatureFilter filter,
081    boolean recurse
082  ) {
083    super(renderer);
084    try {
085      setFilter(filter);
086      setRecurse(recurse);
087    } catch (ChangeVetoException cve) {
088      throw new AssertionFailure("Assertion Failure: Should have no listeners", cve);
089    }
090  }
091  
092  public void setFilter(FeatureFilter filter)
093  throws ChangeVetoException {
094    if(hasListeners()) {
095      ChangeSupport cs = getChangeSupport(FILTER);
096      synchronized(cs) {
097        ChangeEvent ce = new ChangeEvent(
098          this, FILTER, this.filter, filter
099        );
100        cs.firePreChangeEvent(ce);
101        this.filter = filter;
102        cs.firePostChangeEvent(ce);
103      }
104    } else {
105      this.filter = filter;
106    }
107  }
108  
109  public FeatureFilter getFilter() {
110    return this.filter;
111  }
112  
113  public void setRecurse(boolean recurse)
114  throws ChangeVetoException {
115    if(hasListeners()) {
116      ChangeSupport cs = getChangeSupport(RECURSE);
117      synchronized(cs) {
118        ChangeEvent ce = new ChangeEvent(
119          this, RECURSE, new Boolean(recurse), new Boolean(this.recurse)
120        );
121        cs.firePreChangeEvent(ce);
122        this.recurse = recurse;
123        cs.firePostChangeEvent(ce);
124      }
125    } else {
126      this.recurse = recurse;
127    }
128  }
129  
130  public boolean getRecurse() {
131    return this.recurse;
132  }
133
134  public double getDepth(SequenceRenderContext src) {
135    return super.getDepth(getContext(src));
136  }    
137  
138  public double getMinimumLeader(SequenceRenderContext src) {
139    return super.getMinimumLeader(getContext(src));
140  }
141  
142  public double getMinimumTrailer(SequenceRenderContext src) {
143    return super.getMinimumTrailer(getContext(src));
144  }
145  
146  public void paint(
147    Graphics2D g,
148    SequenceRenderContext src
149  ) {
150    super.paint(g, getContext(src));
151  }
152  
153  public SequenceViewerEvent processMouseEvent(
154    SequenceRenderContext src,
155    MouseEvent me,
156    List path
157  ) {
158    return super.processMouseEvent(
159      getContext(src),
160      me,
161      path
162    );
163  }
164  
165  private CacheMap contextCache = new FixedSizeMap(500);
166  private Set flushers = new HashSet();
167  
168  protected SequenceRenderContext getContext(
169    SequenceRenderContext src
170  ) {
171    FeatureFilter actual = new FeatureFilter.And(
172      filter,
173      new FeatureFilter.OverlapsLocation(src.getRange())
174    );
175    
176    CtxtFilt gopher = new CtxtFilt(src, actual, recurse);
177    SequenceRenderContext subSrc = (SequenceRenderContext) contextCache.get(gopher);
178    if(subSrc == null) {
179      subSrc = new SubSequenceRenderContext(
180                src,
181                null,
182                src.getFeatures().filter(actual, recurse),
183                null
184      );
185      contextCache.put(gopher, subSrc);
186      CacheFlusher cf = new CacheFlusher(gopher);
187      // System.err.println("Adding changelistener: " + toString());
188      ((Changeable) src.getSymbols()).addChangeListener(cf, FeatureHolder.FEATURES);
189      flushers.add(cf);
190    }
191    
192    return subSrc;
193  }
194  
195  private class CacheFlusher implements ChangeListener {
196    private CtxtFilt ctxtFilt;
197    
198    public CacheFlusher(CtxtFilt ctxtFilt) {
199      this.ctxtFilt = ctxtFilt;
200    }
201    
202    public void preChange(ChangeEvent ce) {
203    }
204    
205    public void postChange(ChangeEvent ce) {
206      contextCache.remove(ctxtFilt);
207      flushers.remove(this);
208      
209      if(hasListeners()) {
210        ChangeSupport cs = getChangeSupport(SequenceRenderContext.LAYOUT);
211        synchronized(cs) {
212          ChangeEvent ce2 = new ChangeEvent(
213            FilteringRenderer.this,
214            SequenceRenderContext.LAYOUT
215          );
216          cs.firePostChangeEvent(ce2);
217        }
218      }
219    }
220  }
221  
222  private class CtxtFilt {
223    private SequenceRenderContext src;
224    private FeatureFilter filter;
225    private boolean recurse;
226    
227    public CtxtFilt(SequenceRenderContext src, FeatureFilter filter, boolean recurse) {
228      this.src = src;
229      this.filter = filter;
230      this.recurse = recurse;
231    }
232    
233    public boolean equals(Object o) {
234      if(! (o instanceof CtxtFilt) ) {
235        return false;
236      }
237      CtxtFilt that = (CtxtFilt) o;
238      return
239        src.equals(that.src) &&
240        filter.equals(that.filter) && 
241        (recurse == that.recurse);
242    }
243    
244    public int hashCode() {
245      return src.hashCode() ^ filter.hashCode();
246    }
247  }
248
249    public String toString() {
250        return "FilteringRenderer(" + filter + ", " + recurse + ", " + getRenderer() + ")";
251    }
252}