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.io.Serializable; 027import java.util.HashSet; 028import java.util.List; 029import java.util.Set; 030 031import org.biojava.bio.BioError; 032import org.biojava.bio.seq.FeatureFilter; 033import org.biojava.bio.seq.FeatureHolder; 034import org.biojava.utils.AbstractChangeable; 035import org.biojava.utils.ChangeEvent; 036import org.biojava.utils.ChangeForwarder; 037import org.biojava.utils.ChangeListener; 038import org.biojava.utils.ChangeSupport; 039import org.biojava.utils.ChangeType; 040import org.biojava.utils.ChangeVetoException; 041import org.biojava.utils.Changeable; 042import org.biojava.utils.cache.CacheMap; 043import org.biojava.utils.cache.FixedSizeMap; 044 045/** 046 * <p><code>PairwiseFilteringRenderer</code> wraps a 047 * <code>PairwiseSequenceRenderer</code> and filters the 048 * <code>PairwiseRenderContext</code>s passed to it. The renderer 049 * receives a new <code>PairwiseRenderContext</code> which has had 050 * both of its <code>FeatureHolder</code>s filtered with the 051 * <code>FeatureFilter</code>.<p> 052 * 053 * <p>Instances of this class cache up to 5 of the derived 054 * <code>PairwiseRenderContext</code>s. Therefore cycling through up 055 * to 5 different <code>FeatureFilter</code>s will only be hitting the 056 * cache rather than recalculating everthing. Should the 057 * <code>FeatureHolder</code>s themselves change, the cache will be 058 * flushed. As only the features overlapping the context's range are 059 * filtered, changing the range will also result in re-filtering.</p> 060 * 061 * @author Keith James 062 * @author Matthew Pocock 063 * @since 1.2 064 */ 065public class PairwiseFilteringRenderer extends AbstractChangeable 066 implements PairwiseSequenceRenderer, Serializable 067{ 068 /** 069 * Constant <code>FILTER</code> indicating a change to the 070 * renderer's filter. 071 */ 072 public static final ChangeType FILTER = 073 new ChangeType("The filter has changed", 074 "org.biojava.bio.gui.sequence.PairwiseFilteringRenderer", 075 "FILTER", 076 SequenceRenderContext.LAYOUT); 077 078 /** 079 * Constant <code>RECURSE</code> indicating a change to the 080 * renderer's filter recursion flag. 081 */ 082 public static final ChangeType RECURSE = 083 new ChangeType("The recurse flag has changed", 084 "org.biojava.bio.gui.sequence.PairwiseFilteringRenderer", 085 "RECURSE", 086 SequenceRenderContext.LAYOUT); 087 088 /** 089 * Constant <code>RENDERER</code> indicating a change to the 090 * renderer. 091 */ 092 public static final ChangeType RENDERER = 093 new ChangeType("The renderer has changed", 094 "org.biojava.bio.gui.sequence.PairwiseFilteringRenderer", 095 "RENDERER", 096 SequenceRenderContext.REPAINT); 097 098 /** 099 * <code>filter</code> is the filter applied to both 100 * <code>FeatureHolder</code>s. 101 */ 102 protected FeatureFilter filter; 103 /** 104 * <code>recurse</code> indicates whether the filter should 105 * recurse through any subfeatures. 106 */ 107 protected boolean recurse; 108 109 // Cache of previously created subcontexts 110 private CacheMap subContextCache = new FixedSizeMap(5); 111 // Set of listeners to cache keys 112 private Set cacheFlushers = new HashSet(); 113 114 // Delegate renderer 115 private PairwiseSequenceRenderer renderer; 116 private transient ChangeForwarder rendererForwarder; 117 118 /** 119 * Creates a new <code>PairwiseFilteringRenderer</code> which uses 120 * a filter which accepts all features. 121 * 122 * @param renderer a <code>PairwiseSequenceRenderer</code>. 123 */ 124 public PairwiseFilteringRenderer(PairwiseSequenceRenderer renderer) 125 { 126 this(renderer, FeatureFilter.all, false); 127 } 128 129 /** 130 * Creates a new <code>PairwiseFilteringRenderer</code>. 131 * 132 * @param renderer a <code>PairwiseSequenceRenderer</code>. 133 * @param filter a <code>FeatureFilter</code>. 134 * @param recurse a <code>boolean</code>. 135 */ 136 public PairwiseFilteringRenderer(PairwiseSequenceRenderer renderer, 137 FeatureFilter filter, 138 boolean recurse) 139 { 140 try 141 { 142 this.renderer = renderer; 143 setFilter(filter); 144 setRecurse(recurse); 145 } 146 catch (ChangeVetoException cve) 147 { 148 throw new BioError("Assertion failed: should have no listeners", cve); 149 } 150 } 151 152 protected ChangeSupport getChangeSupport(ChangeType ct) 153 { 154 ChangeSupport cs = super.getChangeSupport(ct); 155 156 if (rendererForwarder == null) 157 { 158 rendererForwarder = 159 new PairwiseSequenceRenderer.PairwiseRendererForwarder(this, cs); 160 161 if (renderer instanceof Changeable) 162 { 163 Changeable c = (Changeable) renderer; 164 c.addChangeListener(rendererForwarder, 165 SequenceRenderContext.REPAINT); 166 } 167 } 168 169 return cs; 170 } 171 172 /** 173 * <code>getRenderer</code> return the current renderer. 174 * 175 * @return a <code>PairwiseSequenceRenderer</code>. 176 */ 177 public PairwiseSequenceRenderer getRenderer() 178 { 179 return renderer; 180 } 181 182 /** 183 * <code>setRenderer</code> sets the renderer. 184 * 185 * @param renderer a <code>PairwiseSequenceRenderer</code>. 186 * 187 * @exception ChangeVetoException if the change is vetoed. 188 */ 189 public void setRenderer(PairwiseSequenceRenderer renderer) 190 throws ChangeVetoException 191 { 192 if (hasListeners()) 193 { 194 ChangeEvent ce = new ChangeEvent(this, RENDERER, 195 renderer, this.renderer); 196 197 ChangeSupport cs = getChangeSupport(RENDERER); 198 synchronized(cs) 199 { 200 cs.firePreChangeEvent(ce); 201 202 if (this.renderer instanceof Changeable) 203 { 204 Changeable c = (Changeable) this.renderer; 205 c.removeChangeListener(rendererForwarder); 206 } 207 208 this.renderer = renderer; 209 210 if (renderer instanceof Changeable) 211 { 212 Changeable c = (Changeable) renderer; 213 c.addChangeListener(rendererForwarder); 214 } 215 cs.firePostChangeEvent(ce); 216 } 217 } 218 else 219 { 220 this.renderer = renderer; 221 } 222 } 223 224 /** 225 * <code>getFilter</code> returns the current filter. 226 * 227 * @return a <code>FeatureFilter</code>. 228 */ 229 public FeatureFilter getFilter() 230 { 231 return filter; 232 } 233 234 /** 235 * <code>setFilter</code> sets the filter. 236 * 237 * @param filter a <code>FeatureFilter</code>. 238 * 239 * @exception ChangeVetoException if the change is vetoed. 240 */ 241 public void setFilter(FeatureFilter filter) 242 throws ChangeVetoException 243 { 244 if (hasListeners()) 245 { 246 ChangeSupport cs = getChangeSupport(FILTER); 247 synchronized(cs) 248 { 249 ChangeEvent ce = new ChangeEvent(this, FILTER, 250 this.filter, filter); 251 cs.firePreChangeEvent(ce); 252 this.filter = filter; 253 cs.firePostChangeEvent(ce); 254 } 255 } 256 else 257 { 258 this.filter = filter; 259 } 260 } 261 262 /** 263 * <code>getRecurse</code> returns the recursion flag of the 264 * filter. 265 * 266 * @return a <code>boolean</code>. 267 */ 268 public boolean getRecurse() 269 { 270 return recurse; 271 } 272 273 /** 274 * <code>setRecurse</code> sets the recursion flag on the filter. 275 * 276 * @param recurse a <code>boolean</code>. 277 * 278 * @exception ChangeVetoException if the change is vetoed. 279 */ 280 public void setRecurse(boolean recurse) throws ChangeVetoException 281 { 282 if (hasListeners()) 283 { 284 ChangeSupport cs = getChangeSupport(RECURSE); 285 synchronized(cs) 286 { 287 ChangeEvent ce = new ChangeEvent(this, RECURSE, 288 new Boolean(recurse), 289 new Boolean(this.recurse)); 290 cs.firePreChangeEvent(ce); 291 this.recurse = recurse; 292 cs.firePostChangeEvent(ce); 293 } 294 } 295 else 296 { 297 this.recurse = recurse; 298 } 299 } 300 301 public void paint(Graphics2D g2, PairwiseRenderContext context) 302 { 303 renderer.paint(g2, getSubContext(context)); 304 } 305 306 public SequenceViewerEvent processMouseEvent(PairwiseRenderContext context, 307 MouseEvent me, 308 List path) 309 { 310 path.add(this); 311 return renderer.processMouseEvent(getSubContext(context), me, path); 312 } 313 314 /** 315 * <code>getSubContext</code> creates a new context which has 316 * <code>FeatureHolder</code>s filtered using the current filter. 317 * 318 * @param context a <code>PairwiseRenderContext</code>. 319 * 320 * @return a <code>PairwiseRenderContext</code>. 321 */ 322 protected PairwiseRenderContext getSubContext(PairwiseRenderContext context) 323 { 324 // Filter the sequence features 325 FeatureFilter ff = 326 new FeatureFilter.And(filter, 327 new FeatureFilter.OverlapsLocation(context.getRange())); 328 329 // Filter the secondary sequence features 330 FeatureFilter ffSec = 331 new FeatureFilter.And(filter, 332 new FeatureFilter.OverlapsLocation(context.getSecondaryRange())); 333 334 // Create a cache key 335 FilteredSubContext cacheKey = new FilteredSubContext(context, 336 ff, 337 ffSec, 338 recurse); 339 // Try the cache for a context first 340 PairwiseRenderContext filtered = 341 (PairwiseRenderContext) subContextCache.get(cacheKey); 342 343 if (filtered == null) 344 { 345 // None in cache, so make a new one 346 filtered = 347 new SubPairwiseRenderContext(context, // context delegate 348 null, // symbols 349 null, // secondary symbols 350 context.getFeatures().filter(ff, recurse), 351 context.getFeatures().filter(ffSec, recurse), 352 null, // range 353 null); // secondary range 354 355 // Add to cache 356 subContextCache.put(cacheKey, filtered); 357 // Create a listener for changes in the feature holder 358 CacheFlusher cf = new CacheFlusher(cacheKey); 359 // Add the listener for changes in features 360 ((Changeable) context.getSymbols()) 361 .addChangeListener(cf, FeatureHolder.FEATURES); 362 cacheFlushers.add(cf); 363 } 364 365 return filtered; 366 } 367 368 /** 369 * <code>FilteredSubContext</code> is a cache key whose equality 370 * with another such key is determined by context, filter and 371 * recurse values. 372 */ 373 private class FilteredSubContext 374 { 375 private PairwiseRenderContext context; 376 private FeatureFilter filter; 377 private FeatureFilter secondaryFilter; 378 private boolean recurse; 379 380 public FilteredSubContext(PairwiseRenderContext context, 381 FeatureFilter filter, 382 FeatureFilter secondaryFilter, 383 boolean recurse) 384 { 385 this.context = context; 386 this.filter = filter; 387 this.secondaryFilter = secondaryFilter; 388 this.recurse = recurse; 389 } 390 391 public boolean equals(Object o) 392 { 393 if (! (o instanceof FilteredSubContext)) 394 { 395 return false; 396 } 397 398 FilteredSubContext that = (FilteredSubContext) o; 399 400 return 401 context.equals(that.context) && 402 filter.equals(that.filter) && 403 secondaryFilter.equals(that.secondaryFilter) && 404 (recurse == that.recurse); 405 } 406 407 public int hashCode() 408 { 409 return context.hashCode() ^ filter.hashCode(); 410 } 411 } 412 413 /** 414 * <code>CacheFlusher</code> is a listener for changes to the 415 * original context's feature holders. A change forces removal of 416 * its associated cache key from the cache. 417 */ 418 private class CacheFlusher implements ChangeListener 419 { 420 private FilteredSubContext fsc; 421 422 public CacheFlusher(FilteredSubContext fsc) 423 { 424 this.fsc = fsc; 425 } 426 427 public void preChange(ChangeEvent ce) { } 428 429 public void postChange(ChangeEvent ce) 430 { 431 subContextCache.remove(fsc); 432 cacheFlushers.remove(this); 433 434 if (hasListeners()) 435 { 436 ChangeSupport cs = getChangeSupport(SequenceRenderContext.LAYOUT); 437 synchronized(cs) 438 { 439 ChangeEvent ce2 = 440 new ChangeEvent(PairwiseFilteringRenderer.this, 441 SequenceRenderContext.LAYOUT); 442 cs.firePostChangeEvent(ce2); 443 } 444 } 445 } 446 } 447}