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.impl; 023 024import java.util.Collections; 025import java.util.Iterator; 026 027import org.biojava.bio.Annotatable; 028import org.biojava.bio.Annotation; 029import org.biojava.bio.BioError; 030import org.biojava.bio.BioException; 031import org.biojava.bio.SimpleAnnotation; 032import org.biojava.bio.seq.Feature; 033import org.biojava.bio.seq.FeatureFilter; 034import org.biojava.bio.seq.FeatureHolder; 035import org.biojava.bio.seq.FilterUtils; 036import org.biojava.bio.seq.RealizingFeatureHolder; 037import org.biojava.bio.seq.Sequence; 038import org.biojava.bio.seq.SimpleFeatureHolder; 039import org.biojava.bio.symbol.Location; 040import org.biojava.bio.symbol.SymbolList; 041import org.biojava.ontology.OntoTools; 042import org.biojava.ontology.Term; 043import org.biojava.utils.AbstractChangeable; 044import org.biojava.utils.ChangeEvent; 045import org.biojava.utils.ChangeForwarder; 046import org.biojava.utils.ChangeListener; 047import org.biojava.utils.ChangeSupport; 048import org.biojava.utils.ChangeType; 049import org.biojava.utils.ChangeVetoException; 050 051/** 052 * A no-frills implementation of a feature. 053 * 054 * @author Matthew Pocock 055 * @author Thomas Down 056 * @author Kalle N�slund 057 * @author Paul Seed 058 * @author Len Trigg 059 * @see org.biojavax.bio.seq.SimpleRichFeature 060 */ 061 062public class SimpleFeature 063extends 064AbstractChangeable 065implements 066Feature, 067RealizingFeatureHolder, 068java.io.Serializable 069{ 070 private transient ChangeListener annotationForwarder; 071 private transient ChangeListener featureForwarder; 072 073 /** 074 * The FeatureHolder that we will delegate the FeatureHolder interface too. 075 * This is lazily instantiated. 076 */ 077 private SimpleFeatureHolder featureHolder; 078 079 /** 080 * The location of this feature. 081 */ 082 private Location loc; 083 /** 084 * The type of this feature - something like Exon. 085 * This is included for cheap interoperability with GFF. 086 */ 087 private String type; 088 /** 089 * The source of this feature - the program that generated it. 090 * This is included for cheap interoperability with GFF. 091 */ 092 private String source; 093 /** 094 * Our parent FeatureHolder. 095 */ 096 private FeatureHolder parent; 097 /** 098 * The annotation object. 099 * This is lazily instantiated. 100 */ 101 private Annotation annotation; 102 103 private Term typeTerm; 104 private Term sourceTerm; 105 106 /** 107 * A utility function to retrieve the feature holder delegate, creating it if 108 * necessary. 109 * 110 * @return the FeatureHolder delegate 111 */ 112 protected SimpleFeatureHolder getFeatureHolder() { 113 if(featureHolder == null) { 114 featureHolder = new SimpleFeatureHolder(); 115 } 116 return featureHolder; 117 } 118 119 /** 120 * A utility function to find out if the feature holder delegate has been 121 * instantiated yet. If it has not, we may avoid instantiating it by returning 122 * some pre-canned result. 123 * 124 * @return true if the feature holder delegate has been created and false 125 * otherwise 126 */ 127 protected boolean featureHolderAllocated() { 128 return featureHolder != null; 129 } 130 131 protected ChangeSupport getChangeSupport(ChangeType ct) { 132 ChangeSupport cs = super.getChangeSupport(ct); 133 134 if( 135 (annotationForwarder == null) && 136 (ct.isMatchingType(Annotatable.ANNOTATION) || Annotatable.ANNOTATION.isMatchingType(ct)) 137 ) { 138 annotationForwarder = 139 new ChangeForwarder.Retyper(this, cs, Annotation.PROPERTY); 140 getAnnotation().addChangeListener( 141 annotationForwarder, 142 Annotatable.ANNOTATION 143 ); 144 } 145 146 if( 147 (featureForwarder == null) && 148 (ct == null || ct == FeatureHolder.FEATURES) 149 ) { 150 featureForwarder = new ChangeForwarder( 151 this, 152 cs 153 ); 154 getFeatureHolder().addChangeListener( 155 featureForwarder, 156 FeatureHolder.FEATURES 157 ); 158 } 159 160 return cs; 161 } 162 163 public Location getLocation() { 164 return loc; 165 } 166 167 public void setLocation(Location loc) 168 throws ChangeVetoException { 169 if(hasListeners()) { 170 ChangeSupport cs = getChangeSupport(LOCATION); 171 synchronized(cs) { 172 ChangeEvent ce = new ChangeEvent(this, LOCATION, loc, this.loc); 173 synchronized(ce) { 174 cs.firePreChangeEvent(ce); 175 } 176 this.loc = loc; 177 synchronized(ce) { 178 cs.firePostChangeEvent(ce); 179 } 180 } 181 } else { 182 this.loc = loc; 183 } 184 } 185 186 public Term getTypeTerm() { 187 return typeTerm; 188 } 189 190 public String getType() { 191 if(type != null) { 192 return type; 193 } else if (typeTerm != null) { 194 return typeTerm.getName(); 195 } else { 196 return ""; 197 } 198 } 199 200 public void setType(String type) 201 throws ChangeVetoException { 202 if(hasListeners()) { 203 ChangeSupport cs = getChangeSupport(TYPE); 204 synchronized(cs) { 205 ChangeEvent ce = new ChangeEvent(this, TYPE, type, this.type); 206 synchronized(ce) { 207 cs.firePreChangeEvent(ce); 208 } 209 this.type = type; 210 synchronized(ce) { 211 cs.firePostChangeEvent(ce); 212 } 213 } 214 } else { 215 this.type = type; 216 } 217 } 218 219 public void setTypeTerm(Term t) 220 throws ChangeVetoException 221 { 222 if(hasListeners()) { 223 ChangeSupport cs = getChangeSupport(TYPE); 224 synchronized (cs) { 225 ChangeEvent ce_term = new ChangeEvent(this, TYPETERM, t, this.getTypeTerm()); 226 ChangeEvent ce_name = new ChangeEvent(this, TYPE, t.getName(), this.getType()); 227 cs.firePreChangeEvent(ce_term); 228 cs.firePreChangeEvent(ce_name); 229 this.typeTerm = t; 230 cs.firePostChangeEvent(ce_term); 231 cs.firePostChangeEvent(ce_name); 232 } 233 } else { 234 this.typeTerm = t; 235 } 236 } 237 238 public String getSource() { 239 if(source != null) { 240 return source; 241 } else if (sourceTerm != null) { 242 return sourceTerm.getName(); 243 } else { 244 return ""; 245 } 246 } 247 248 public Term getSourceTerm() { 249 return sourceTerm; 250 } 251 252 public FeatureHolder getParent() { 253 return parent; 254 } 255 256 public void setSource(String source) 257 throws ChangeVetoException { 258 if(hasListeners()) { 259 ChangeSupport cs = getChangeSupport(SOURCE); 260 synchronized(cs) { 261 ChangeEvent ce = new ChangeEvent(this, SOURCE, this.source, source); 262 cs.firePreChangeEvent(ce); 263 this.source = source; 264 cs.firePostChangeEvent(ce); 265 } 266 } else { 267 this.source = source; 268 } 269 } 270 271 272 public void setSourceTerm(Term t) 273 throws ChangeVetoException 274 { 275 if(hasListeners()) { 276 ChangeSupport cs = getChangeSupport(TYPE); 277 synchronized (cs) { 278 ChangeEvent ce_term = new ChangeEvent(this, SOURCETERM, t, this.getSourceTerm()); 279 ChangeEvent ce_name = new ChangeEvent(this, SOURCE, t.getName(), this.getSource()); 280 cs.firePreChangeEvent(ce_term); 281 cs.firePreChangeEvent(ce_name); 282 this.sourceTerm = t; 283 cs.firePostChangeEvent(ce_term); 284 cs.firePostChangeEvent(ce_name); 285 } 286 } else { 287 this.sourceTerm = t; 288 } 289 } 290 291 public Sequence getSequence() { 292 FeatureHolder fh = this; 293 while (fh instanceof Feature) { 294 fh = ((Feature) fh).getParent(); 295 } 296 try { 297 return (Sequence) fh; 298 } catch (ClassCastException ex) { 299 throw new BioError("Feature doesn't seem to have a Sequence ancestor: " + fh); 300 } 301 } 302 303 public Annotation getAnnotation() { 304 if(annotation == null) 305 annotation = new SimpleAnnotation(); 306 return annotation; 307 } 308 309 public SymbolList getSymbols() { 310 return getLocation().symbols(getSequence()); 311 } 312 313 public int countFeatures() { 314 if(featureHolderAllocated()) 315 return getFeatureHolder().countFeatures(); 316 return 0; 317 } 318 319 public Iterator features() { 320 if(featureHolderAllocated()) 321 return getFeatureHolder().features(); 322 return Collections.EMPTY_LIST.iterator(); 323 } 324 325 public void removeFeature(Feature f) 326 throws ChangeVetoException { 327 getFeatureHolder().removeFeature(f); 328 } 329 330 public boolean containsFeature(Feature f) { 331 if(featureHolderAllocated()) { 332 return getFeatureHolder().containsFeature(f); 333 } else { 334 return false; 335 } 336 } 337 338 339 public FeatureHolder filter(FeatureFilter ff) { 340 FeatureFilter childFilter = new FeatureFilter.Not(FeatureFilter.top_level); 341 342 if (FilterUtils.areDisjoint(ff, childFilter)) { 343 return FeatureHolder.EMPTY_FEATURE_HOLDER; 344 } else if (featureHolderAllocated()) { 345 return getFeatureHolder().filter(ff); 346 } else { 347 return FeatureHolder.EMPTY_FEATURE_HOLDER; 348 } 349 } 350 351 public FeatureHolder filter(FeatureFilter ff, boolean recurse) { 352 if(featureHolderAllocated()) 353 return getFeatureHolder().filter(ff, recurse); 354 return FeatureHolder.EMPTY_FEATURE_HOLDER; 355 } 356 357 public Feature.Template makeTemplate() { 358 Feature.Template ft = new Feature.Template(); 359 fillTemplate(ft); 360 return ft; 361 } 362 363 protected void fillTemplate(Feature.Template ft) { 364 ft.location = getLocation(); 365 ft.type = getType(); 366 ft.source = getSource(); 367 ft.annotation = getAnnotation(); 368 ft.sourceTerm = getSourceTerm(); 369 ft.typeTerm = getTypeTerm(); 370 } 371 372 /** 373 * Create a <code>SimpleFeature</code> on the given sequence. 374 * The feature is created underneath the parent <code>FeatureHolder</code> 375 * and populated directly from the template fields. However, 376 * if the template annotation is the <code>Annotation.EMPTY_ANNOTATION</code>, 377 * an empty <code>SimpleAnnotation</code> is attached to the feature instead. 378 * @param sourceSeq the source sequence 379 * @param parent the parent sequence or feature 380 * @param template the template for the feature 381 */ 382 public SimpleFeature(Sequence sourceSeq, 383 FeatureHolder parent, 384 Feature.Template template) { 385 if (template.location == null) { 386 throw new IllegalArgumentException( 387 "Location can not be null. Did you mean Location.EMPTY_LOCATION? " + 388 template.toString() 389 ); 390 } 391 if(!(parent instanceof Feature) && !(parent instanceof Sequence)) { 392 throw new IllegalArgumentException("Parent must be sequence or feature, not: " + parent.getClass() + " " + parent); 393 } 394 395 if (template.location.getMin() < 1 || template.location.getMax() > sourceSeq.length()) { 396 //throw new IllegalArgumentException("Location " + template.location.toString() + " is outside 1.." + sourceSeq.length()); 397 } 398 399 this.parent = parent; 400 this.loc = template.location; 401 this.typeTerm = template.typeTerm != null ? template.typeTerm : OntoTools.ANY; 402 this.sourceTerm = template.sourceTerm != null ? template.sourceTerm : OntoTools.ANY; 403 this.type = template.type != null ? template.type : typeTerm.getName(); 404 this.source = template.source != null ? template.source : sourceTerm.getName(); 405 if (this.type == null) { 406 throw new NullPointerException("Either type or typeTerm must have a non-null value"); 407 } 408 if (this.source == null) { 409 throw new NullPointerException("Either source or sourceTerm must have a non-null value"); 410 } 411 this.annotation = template.annotation != null ? new SimpleAnnotation(template.annotation) : null; 412 } 413 414 public String toString() { 415 return "Feature " + getType() + " " + 416 getSource() + " " + getLocation(); 417 } 418 419 public Feature realizeFeature(FeatureHolder fh, Feature.Template templ) 420 throws BioException 421 { 422 try { 423 RealizingFeatureHolder rfh = (RealizingFeatureHolder) getParent(); 424 return rfh.realizeFeature(fh, templ); 425 } catch (ClassCastException ex) { 426 throw new BioException("Couldn't propagate feature creation request."); 427 } 428 } 429 430 public Feature createFeature(Feature.Template temp) 431 throws BioException, ChangeVetoException 432 { 433 Feature f = realizeFeature(this, temp); 434 getFeatureHolder().addFeature(f); 435 return f; 436 } 437 438 public int hashCode() { 439 return makeTemplate().hashCode(); 440 } 441 442 public boolean equals(Object o) { 443 if (! (o instanceof Feature)) { 444 return false; 445 } 446 447 Feature fo = (Feature) o; 448 if (! fo.getSequence().equals(getSequence())) 449 return false; 450 451 return makeTemplate().equals(fo.makeTemplate()); 452 } 453 454 public FeatureFilter getSchema() { 455 return new FeatureFilter.ByParent(new FeatureFilter.ByFeature(this)); 456 } 457}