/**
   * Delete an exon. Deletes both the transcript -> exon and exon -> transcript relationships.
   *
   * @param exon - Exon to be deleted
   */
  public void deleteExon(Exon exon) {
    Collection<CVTerm> partOfCvterms = conf.getCVTermsForClass("PartOf");
    Collection<CVTerm> exonCvterms = conf.getCVTermsForClass("Exon");
    Collection<CVTerm> transcriptCvterms = conf.getCVTermsForClass("Transcript");

    // delete transcript -> exon child relationship
    for (FeatureRelationship fr : feature.getChildFeatureRelationships()) {
      if (!partOfCvterms.contains(fr.getType())) {
        continue;
      }
      if (!exonCvterms.contains(fr.getSubjectFeature().getType())) {
        continue;
      }
      if (fr.getSubjectFeature().equals(exon.getFeature())) {
        boolean ok = feature.getChildFeatureRelationships().remove(fr);
        break;
      }
    }

    // delete transcript -> exon parent relationship
    for (FeatureRelationship fr : exon.getFeature().getParentFeatureRelationships()) {
      if (!partOfCvterms.contains(fr.getType())) {
        continue;
      }
      if (!transcriptCvterms.contains(fr.getObjectFeature().getType())) {
        continue;
      }
      if (fr.getSubjectFeature().equals(exon.getFeature())) {
        boolean ok = exon.getFeature().getParentFeatureRelationships().remove(fr);
        break;
      }
    }
  }
  /**
   * Delete an non canonical 3' splice site. Deletes both the transcript -> non canonical 3' splice
   * site and non canonical 3' splice site -> transcript relationships.
   *
   * @param nonCanonicalThreePrimeSpliceSite - NonCanonicalThreePrimeSpliceSite to be deleted
   */
  public void deleteNonCanonicalThreePrimeSpliceSite(
      NonCanonicalThreePrimeSpliceSite nonCanonicalThreePrimeSpliceSite) {
    Collection<CVTerm> partOfCvterms = conf.getCVTermsForClass("PartOf");
    Collection<CVTerm> nonCanonicalThreePrimeSpliceSiteCvterms =
        conf.getCVTermsForClass("NonCanonicalThreePrimeSpliceSite");
    Collection<CVTerm> transcriptCvterms = conf.getCVTermsForClass("Transcript");

    // delete transcript -> non canonical 3' splice site child relationship
    for (FeatureRelationship fr : feature.getChildFeatureRelationships()) {
      if (!partOfCvterms.contains(fr.getType())) {
        continue;
      }
      if (!nonCanonicalThreePrimeSpliceSiteCvterms.contains(fr.getSubjectFeature().getType())) {
        continue;
      }
      if (fr.getSubjectFeature().equals(nonCanonicalThreePrimeSpliceSite.getFeature())) {
        boolean ok = feature.getChildFeatureRelationships().remove(fr);
        break;
      }
    }

    // delete transcript -> non canonical 3' splice site parent relationship
    for (FeatureRelationship fr :
        nonCanonicalThreePrimeSpliceSite.getFeature().getParentFeatureRelationships()) {
      if (!partOfCvterms.contains(fr.getType())) {
        continue;
      }
      if (!transcriptCvterms.contains(fr.getObjectFeature().getType())) {
        continue;
      }
      if (fr.getSubjectFeature().equals(nonCanonicalThreePrimeSpliceSite.getFeature())) {
        boolean ok =
            nonCanonicalThreePrimeSpliceSite
                .getFeature()
                .getParentFeatureRelationships()
                .remove(fr);
        break;
      }
    }
  }
 /**
  * Retrieve the gene that this transcript is associated with. Uses the configuration to determine
  * which parent is a gene. The gene object is generated on the fly. Returns <code>null</code> if
  * this transcript is not associated with any gene.
  *
  * @return Gene that this Transcript is associated with
  */
 public Gene getGene() {
   Collection<CVTerm> partOfCvterms = conf.getCVTermsForClass("PartOf");
   Collection<CVTerm> geneCvterms = conf.getDescendantCVTermsForClass("Gene");
   for (FeatureRelationship fr : feature.getParentFeatureRelationships()) {
     if (!partOfCvterms.contains(fr.getType())) {
       continue;
     }
     if (!geneCvterms.contains(fr.getObjectFeature().getType())) {
       continue;
     }
     return ((Gene) BioObjectUtil.createBioObject(fr.getObjectFeature(), conf));
   }
   return null;
 }
  public void deleteCDS() {
    Collection<CVTerm> partOfCvterms = conf.getCVTermsForClass("PartOf");
    Collection<CVTerm> cdsCvterms = conf.getCVTermsForClass("CDS");

    for (FeatureRelationship fr : feature.getChildFeatureRelationships()) {
      if (!partOfCvterms.contains(fr.getType())) {
        continue;
      }
      if (!cdsCvterms.contains(fr.getSubjectFeature().getType())) {
        continue;
      }
      feature.getChildFeatureRelationships().remove(fr);
      break;
    }
  }
  /**
   * Retrieve the CDS associated with this transcript. Uses the configuration to determine which
   * child is a CDS. The CDS object is generated on the fly. Returns <code>null</code> if no CDS is
   * associated.
   *
   * @return CDS associated with this transcript
   */
  public CDS getCDS() {
    Collection<CVTerm> partOfCvterms = conf.getCVTermsForClass("PartOf");
    Collection<CVTerm> cdsCvterms = conf.getCVTermsForClass("CDS");

    for (FeatureRelationship fr : feature.getChildFeatureRelationships()) {
      if (!partOfCvterms.contains(fr.getType())) {
        continue;
      }
      if (!cdsCvterms.contains(fr.getSubjectFeature().getType())) {
        continue;
      }
      return new CDS(fr.getSubjectFeature(), conf);
    }
    return null;
  }
  /**
   * Retrieve all the exons associated with this transcript. Uses the configuration to determine
   * which children are exons. Exon objects are generated on the fly. The collection will be empty
   * if there are no exons associated with the transcript.
   *
   * @return Collection of exons associated with this transcript
   */
  public Collection<Exon> getExons() {
    Collection<Exon> exons = new ArrayList<Exon>();
    Collection<CVTerm> partOfCvterms = conf.getCVTermsForClass("PartOf");
    Collection<CVTerm> exonCvterms = conf.getCVTermsForClass("Exon");

    for (FeatureRelationship fr : feature.getChildFeatureRelationships()) {
      if (!partOfCvterms.contains(fr.getType())) {
        continue;
      }
      if (!exonCvterms.contains(fr.getSubjectFeature().getType())) {
        continue;
      }
      exons.add(new Exon(fr.getSubjectFeature(), conf));
    }
    return exons;
  }
  /**
   * Get the number of exons.
   *
   * @return Number of exons
   */
  public int getNumberOfExons() {
    Collection<CVTerm> partOfCvterms = conf.getCVTermsForClass("PartOf");
    Collection<CVTerm> exonCvterms = conf.getCVTermsForClass("Exon");
    int numExons = 0;

    for (FeatureRelationship fr : feature.getChildFeatureRelationships()) {
      if (!partOfCvterms.contains(fr.getType())) {
        continue;
      }
      if (!exonCvterms.contains(fr.getSubjectFeature().getType())) {
        continue;
      }
      ++numExons;
    }
    return numExons;
  }
  /**
   * Retrieve all the non canonical 3' splice sites associated with this transcript. Uses the
   * configuration to determine which children are non canonical 3' splice sites. Non canonical 3'
   * splice site objects are generated on the fly. The collection will be empty if there are no non
   * canonical 3' splice sites associated with the transcript.
   *
   * @return Collection of non canonical 3' splice sites associated with this transcript
   */
  public Collection<NonCanonicalThreePrimeSpliceSite> getNonCanonicalThreePrimeSpliceSites() {
    Collection<NonCanonicalThreePrimeSpliceSite> nonCanonicalThreePrimeSpliceSites =
        new ArrayList<NonCanonicalThreePrimeSpliceSite>();
    Collection<CVTerm> partOfCvterms = conf.getCVTermsForClass("PartOf");
    Collection<CVTerm> nonCanonicalThreePrimeSpliceSiteCvterms =
        conf.getCVTermsForClass("NonCanonicalThreePrimeSpliceSite");

    for (FeatureRelationship fr : feature.getChildFeatureRelationships()) {
      if (!partOfCvterms.contains(fr.getType())) {
        continue;
      }
      if (!nonCanonicalThreePrimeSpliceSiteCvterms.contains(fr.getSubjectFeature().getType())) {
        continue;
      }
      nonCanonicalThreePrimeSpliceSites.add(
          new NonCanonicalThreePrimeSpliceSite(fr.getSubjectFeature(), conf));
    }
    return nonCanonicalThreePrimeSpliceSites;
  }
  /**
   * Set the gene that this transcript is associated with. Uses the configuration to determine which
   * parent is a gene. If the transcript is already associated with a gene, updates that
   * association. Otherwise, it creates a new association.
   *
   * @param gene - Gene that this transcript will be associated with
   */
  public void setGene(Gene gene) {
    Collection<CVTerm> partOfCvterms = conf.getCVTermsForClass("PartOf");
    Collection<CVTerm> geneCvterms = conf.getDescendantCVTermsForClass("Gene");
    for (FeatureRelationship fr : feature.getParentFeatureRelationships()) {
      if (!partOfCvterms.contains(fr.getType())) {
        continue;
      }
      if (!geneCvterms.contains(fr.getObjectFeature().getType())) {
        continue;
      }
      fr.setObjectFeature(gene.getFeature());
      return;
    }

    FeatureRelationship fr =
        new FeatureRelationship(
            conf.getDefaultCVTermForClass("PartOf"),
            gene.getFeature(),
            this.feature,
            0 // TODO: Do we need to rank the order of any other transcripts?
            );
    feature.getParentFeatureRelationships().add(fr);
    gene.getFeature().getChildFeatureRelationships().add(fr);
  }