/**
   * Handle UTR joins
   *
   * @param feature
   * @param key
   * @param qualifiers
   * @return
   */
  private Location joinUtrs(Feature feature, Key key, QualifierVector qualifiers) {
    Location location = feature.getLocation();
    if (key.getKeyString().equals("5'UTR") || key.getKeyString().equals("3'UTR")) {
      ChadoCanonicalGene gene = ((GFFStreamFeature) feature).getChadoGene();
      String utrName = GeneUtils.getUniqueName(feature);
      String transcriptName = gene.getTranscriptFromName(utrName);
      List<Feature> utrs;

      if (key.getKeyString().equals("5'UTR")) utrs = gene.get5UtrOfTranscript(transcriptName);
      else utrs = gene.get3UtrOfTranscript(transcriptName);

      if (utrs.size() > 1) {
        int start = Integer.MAX_VALUE;
        RangeVector ranges = new RangeVector();
        for (int i = 0; i < utrs.size(); i++) {
          Feature utr = utrs.get(i);
          Range range = utr.getLocation().getTotalRange();
          if (start > range.getStart()) start = range.getStart();
          ranges.add(range);
        }

        if (start != feature.getLocation().getTotalRange().getStart()) return null;

        location = new Location(ranges, feature.getLocation().isComplement());
      }

      int ntranscripts = gene.getTranscripts().size();
      if (ntranscripts == 1) transcriptName = gene.getGeneUniqueName();
      qualifiers.setQualifier(new Qualifier("locus_tag", transcriptName));
      qualifiers.removeQualifierByName("ID");
    }
    return location;
  }
  /**
   * Maps database (SO) keys to EMBl keys. It will add any extra qualifiers found in the 3rd column
   * of DATABASE_MAP_KEYS.
   *
   * @param key
   * @return
   */
  private Key map(final Key key, final QualifierVector qualifiers) {
    if (DATABASE_MAP_KEYS == null) initDatabaseMappings();
    for (int i = 0; i < DATABASE_MAP_KEYS.length; i++) {
      if (key.getKeyString().equals(DATABASE_MAP_KEYS[i][0])) {
        Key mappedKey = new Key((String) DATABASE_MAP_KEYS[i][1]);
        if (DATABASE_MAP_KEYS[i][2] != null) {

          Qualifier newQualifier = (Qualifier) DATABASE_MAP_KEYS[i][2];
          if (!getEntryInformation().isValidQualifier(mappedKey, newQualifier.getName())) {
            try {
              final int nvalues;
              if (newQualifier.getValues() == null) nvalues = 0;
              else nvalues = newQualifier.getValues().size();

              final int type;
              if (nvalues == 0) type = QualifierInfo.NO_VALUE;
              else type = QualifierInfo.QUOTED_TEXT;
              getEntryInformation()
                  .addQualifierInfo(
                      new QualifierInfo(newQualifier.getName(), type, null, null, false));
            } catch (QualifierInfoException e) {
            }
          }

          qualifiers.addQualifierValues(newQualifier);
        }

        return mappedKey;
      }
    }
    return key;
  }
 /**
  * Maps database (SO) keys to EMBl keys.
  *
  * @param key
  * @return
  */
 protected static Key mapKeys(final Key key) {
   if (DATABASE_MAP_KEYS == null) initDatabaseMappings();
   for (int i = 0; i < DATABASE_MAP_KEYS.length; i++) {
     if (key.getKeyString().equals(DATABASE_MAP_KEYS[i][0]))
       return new Key((String) DATABASE_MAP_KEYS[i][1]);
   }
   return key;
 }
  /**
   * Map GFF features to EMBL/Genbank
   *
   * @param feature
   * @return
   */
  private Object mapGffToNativeFeature(final Feature feature) {
    if (DATABASE_MAP_KEYS == null) initDatabaseMappings();

    Key key = feature.getKey();
    QualifierVector qualifiers = feature.getQualifiers().copy();

    // ignore if obsolete
    if (IGNORE_OBSOLETE_FEATURES) {
      Qualifier isObsoleteQualifier = qualifiers.getQualifierByName("isObsolete");
      if (isObsoleteQualifier != null) {
        String value = (String) isObsoleteQualifier.getValues().get(0);
        if (Boolean.parseBoolean(value)) return null;
      }
    }

    key = map(key, qualifiers);
    if (getEntryInformation().isValidQualifier((String) DATABASE_QUALIFIERS_TO_REMOVE[0])) {
      try {
        if (this instanceof EmblDocumentEntry)
          return new EmblStreamFeature(key, feature.getLocation(), qualifiers);
        else return new GenbankStreamFeature(key, feature.getLocation(), qualifiers);
      } catch (InvalidRelationException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }

    Location location = joinUtrs(feature, key, qualifiers);
    if (location == null) return null;
    // flatten gene model - combining qualifiers
    if (key.getKeyString().equals(DatabaseDocument.EXONMODEL)) {
      ChadoCanonicalGene chadoGene = ((GFFStreamFeature) feature).getChadoGene();

      final String name = GeneUtils.getUniqueName(feature);
      final String transcriptName = chadoGene.getTranscriptFromName(name);

      StringVector sv = new StringVector();
      sv.add(transcriptName);
      final Feature transcript = chadoGene.containsTranscript(sv);

      if (transcript != null && GeneUtils.isNonCodingTranscripts(transcript.getKey())) return null;

      qualifiers.removeQualifierByName("ID");
      int ntranscripts = 0;
      // add transcript & protein qualifiers to CDS
      try {
        final Feature protein = chadoGene.getProteinOfTranscript(transcriptName);
        if (protein != null) combineQualifiers(qualifiers, protein.getQualifiers().copy(), false);

        if (transcript != null)
          ntranscripts = handleTranscripts(qualifiers, transcript, ntranscripts, chadoGene);
      } catch (NullPointerException npe) {
      }

      // add gene qualifiers to CDS
      QualifierVector geneQualifiers = chadoGene.getGene().getQualifiers().copy();

      // multiple transcripts
      if (ntranscripts > 1 && geneQualifiers.getQualifierByName("ID") != null) {
        Qualifier newIDQualifier =
            new Qualifier(
                "shared_id", (String) geneQualifiers.getQualifierByName("ID").getValues().get(0));
        addNewQualifier(qualifiers, newIDQualifier);
        geneQualifiers.removeQualifierByName("ID");
      }
      combineQualifiers(qualifiers, geneQualifiers, true);
    } else if (GeneUtils.isNonCodingTranscripts(key)) {
      // use gene id for non-coding transcripts
      ChadoCanonicalGene chadoGene = ((GFFStreamFeature) feature).getChadoGene();
      if (chadoGene != null) {
        qualifiers.removeQualifierByName("ID");
        QualifierVector geneQualifiers = chadoGene.getGene().getQualifiers().copy();
        combineQualifiers(qualifiers, geneQualifiers, true);
      }
    }

    try {
      for (int i = 0; i < DATABASE_QUALIFIERS_TO_MAP.length; i++) {
        if (!getEntryInformation().isValidQualifier(DATABASE_QUALIFIERS_TO_MAP[i][0])) {
          changeQualifierName(
              qualifiers, DATABASE_QUALIFIERS_TO_MAP[i][0], DATABASE_QUALIFIERS_TO_MAP[i][1]);
        }
      }

      if (qualifiers.getQualifierByName("stop_codon_redefined_as_selenocysteine") != null) {
        handleSelenocysteine(qualifiers, feature);
      }

      for (int i = 0; i < DATABASE_QUALIFIERS_TO_REMOVE.length; i++) {
        if (!getEntryInformation().isValidQualifier((String) DATABASE_QUALIFIERS_TO_REMOVE[i]))
          qualifiers.removeQualifierByName((String) DATABASE_QUALIFIERS_TO_REMOVE[i]);
      }

      if (key.getKeyString().equals("polypeptide")) return null;
      else if (key.getKeyString().equals("gene")) return null;
      else if (key.getKeyString().equals("centromere")) return null;
      else if (key.getKeyString().equals("transcript") || key.getKeyString().equals("mRNA"))
        return null;

      if (this instanceof EmblDocumentEntry)
        return new EmblStreamFeature(key, location, qualifiers);
      else return new GenbankStreamFeature(key, location, qualifiers);
    } catch (InvalidRelationException e) {
      e.printStackTrace();
      if (feature instanceof DatabaseStreamFeature) return new EmblStreamFeature();
      else return new GenbankStreamFeature();
    }
  }