32bb7b7b97c2e0f037e8c8011ba692c3b54e5f8a
[jalview.git] / src / jalview / ws / sifts / SiftsClient.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.ws.sifts;
22
23 import jalview.analysis.AlignSeq;
24 import jalview.api.DBRefEntryI;
25 import jalview.api.SiftsClientI;
26 import jalview.datamodel.DBRefEntry;
27 import jalview.datamodel.DBRefSource;
28 import jalview.datamodel.SequenceI;
29 import jalview.schemes.ResidueProperties;
30 import jalview.structure.StructureMapping;
31 import jalview.util.Format;
32 import jalview.xml.binding.sifts.Entry;
33 import jalview.xml.binding.sifts.Entry.Entity;
34 import jalview.xml.binding.sifts.Entry.Entity.Segment;
35 import jalview.xml.binding.sifts.Entry.Entity.Segment.ListMapRegion.MapRegion;
36 import jalview.xml.binding.sifts.Entry.Entity.Segment.ListResidue.Residue;
37 import jalview.xml.binding.sifts.Entry.Entity.Segment.ListResidue.Residue.CrossRefDb;
38 import jalview.xml.binding.sifts.Entry.Entity.Segment.ListResidue.Residue.ResidueDetail;
39 import jalview.xml.binding.sifts.Entry.ListDB.Db;
40
41 import java.io.File;
42 import java.io.FileInputStream;
43 import java.io.FileNotFoundException;
44 import java.io.FileOutputStream;
45 import java.io.IOException;
46 import java.io.InputStream;
47 import java.io.PrintStream;
48 import java.net.URL;
49 import java.net.URLConnection;
50 import java.util.ArrayList;
51 import java.util.Arrays;
52 import java.util.Collection;
53 import java.util.HashMap;
54 import java.util.HashSet;
55 import java.util.List;
56 import java.util.TreeMap;
57 import java.util.zip.GZIPInputStream;
58
59 import javax.xml.bind.JAXBContext;
60 import javax.xml.bind.JAXBException;
61 import javax.xml.bind.Unmarshaller;
62 import javax.xml.stream.FactoryConfigurationError;
63 import javax.xml.stream.XMLInputFactory;
64 import javax.xml.stream.XMLStreamException;
65 import javax.xml.stream.XMLStreamReader;
66
67 import MCview.Atom;
68 import MCview.PDBChain;
69 import MCview.PDBfile;
70
71 public class SiftsClient implements SiftsClientI
72 {
73   private Entry siftsEntry;
74
75   private PDBfile pdb;
76
77   private String pdbId;
78
79   private String structId;
80
81   private String segStartEnd;
82
83   private CoordinateSys seqCoordSys = CoordinateSys.UNIPROT;
84
85   private static final int BUFFER_SIZE = 4096;
86
87   public static final int UNASSIGNED = -1;
88
89   private static final int PDB_RES_POS = 0;
90
91   private static final int PDB_ATOM_POS = 1;
92
93   private static final String NOT_FOUND = "Not_Found";
94
95   private static final String NOT_OBSERVED = "Not_Observed";
96
97   private static final String SIFTS_FTP_BASE_URL = "ftp://ftp.ebi.ac.uk/pub/databases/msd/sifts/xml/";
98
99   private final static String NEWLINE = System.lineSeparator();
100
101   private String curSourceDBRef;
102
103   private HashSet<String> curDBRefAccessionIdsString;
104
105   public enum CoordinateSys
106   {
107     UNIPROT("UniProt"), PDB("PDBresnum"), PDBe("PDBe");
108     private String name;
109
110     private CoordinateSys(String name)
111     {
112       this.name = name;
113     }
114
115     public String getName()
116     {
117       return name;
118     }
119   };
120
121   public enum ResidueDetailType
122   {
123     NAME_SEC_STRUCTURE("nameSecondaryStructure"), CODE_SEC_STRUCTURE(
124             "codeSecondaryStructure"), ANNOTATION("Annotation");
125     private String code;
126
127     private ResidueDetailType(String code)
128     {
129       this.code = code;
130     }
131
132     public String getCode()
133     {
134       return code;
135     }
136   };
137
138   /**
139    * Fetch SIFTs file for the given PDBfile and construct an instance of
140    * SiftsClient
141    * 
142    * @param pdbId
143    * @throws SiftsException
144    */
145   public SiftsClient(PDBfile pdb) throws SiftsException
146   {
147     this.pdb = pdb;
148     this.pdbId = pdb.id;
149     File siftsFile = getSiftsFile(pdbId);
150     siftsEntry = parseSIFTs(siftsFile);
151   }
152
153   /**
154    * Construct an instance of SiftsClient using the supplied SIFTs file. Note:
155    * The SIFTs file should correspond to the PDB Id in PDBfile instance
156    * 
157    * @param pdbId
158    * @param siftsFile
159    * @throws SiftsException
160    * @throws Exception
161    */
162   public SiftsClient(PDBfile pdb, File siftsFile) throws SiftsException
163   {
164     this.pdb = pdb;
165     this.pdbId = pdb.id;
166     siftsEntry = parseSIFTs(siftsFile);
167   }
168
169   /**
170    * Parse the given SIFTs File and return a JAXB POJO of parsed data
171    * 
172    * @param siftFile
173    *          - the GZipped SIFTs XML file to parse
174    * @return
175    * @throws Exception
176    *           if a problem occurs while parsing the SIFTs XML
177    */
178   private Entry parseSIFTs(File siftFile) throws SiftsException
179   {
180     try (InputStream in = new FileInputStream(siftFile);
181             GZIPInputStream gzis = new GZIPInputStream(in);)
182     {
183       System.out.println("File : " + siftFile.getAbsolutePath());
184       JAXBContext jc = JAXBContext.newInstance("jalview.xml.binding.sifts");
185       XMLStreamReader streamReader = XMLInputFactory.newInstance()
186               .createXMLStreamReader(gzis);
187       Unmarshaller um = jc.createUnmarshaller();
188       return (Entry) um.unmarshal(streamReader);
189     } catch (JAXBException e)
190     {
191       e.printStackTrace();
192       throw new SiftsException(e.getMessage());
193     } catch (FileNotFoundException e)
194     {
195       e.printStackTrace();
196       throw new SiftsException(e.getMessage());
197     } catch (XMLStreamException e)
198     {
199       e.printStackTrace();
200       throw new SiftsException(e.getMessage());
201     } catch (FactoryConfigurationError e)
202     {
203       e.printStackTrace();
204       throw new SiftsException(e.getMessage());
205     } catch (IOException e)
206     {
207       e.printStackTrace();
208       throw new SiftsException(e.getMessage());
209     }
210   }
211
212   /**
213    * Get a SIFTs XML file for a given PDB Id from Cache or download from FTP
214    * repository if not found in cache
215    * 
216    * @param pdbId
217    * @return SIFTs XML file
218    * @throws SiftsException
219    */
220   public static File getSiftsFile(String pdbId) throws SiftsException
221   {
222     File siftsFile = new File(SiftsSettings.getSiftDownloadDirectory()
223             + pdbId.toLowerCase()
224             + ".xml.gz");
225     if (siftsFile.exists())
226     {
227       // TODO it may be worth performing an age check to determine if a
228       // new SIFTs file should be re-downloaded as SIFTs entries are usually
229       // updated weekly
230       System.out.println(">>> SIFTS File already downloaded for " + pdbId);
231       return siftsFile;
232     }
233     siftsFile = downloadSiftsFile(pdbId.toLowerCase());
234     return siftsFile;
235   }
236
237   /**
238    * Download a SIFTs XML file for a given PDB Id from an FTP repository
239    * 
240    * @param pdbId
241    * @return downloaded SIFTs XML file
242    * @throws SiftsException
243    */
244   public static File downloadSiftsFile(String pdbId) throws SiftsException
245   {
246     String siftFile = pdbId + ".xml.gz";
247     String siftsFileFTPURL = SIFTS_FTP_BASE_URL + siftFile;
248     String downloadedSiftsFile = SiftsSettings.getSiftDownloadDirectory()
249             + siftFile;
250     File siftsDownloadDir = new File(
251             SiftsSettings.getSiftDownloadDirectory());
252     if (!siftsDownloadDir.exists())
253     {
254       siftsDownloadDir.mkdirs();
255     }
256     try
257     {
258       System.out.println(">> Download ftp url : " + siftsFileFTPURL);
259       URL url = new URL(siftsFileFTPURL);
260       URLConnection conn = url.openConnection();
261       InputStream inputStream = conn.getInputStream();
262       FileOutputStream outputStream = new FileOutputStream(
263               downloadedSiftsFile);
264       byte[] buffer = new byte[BUFFER_SIZE];
265       int bytesRead = -1;
266       while ((bytesRead = inputStream.read(buffer)) != -1)
267       {
268         outputStream.write(buffer, 0, bytesRead);
269       }
270       outputStream.close();
271       inputStream.close();
272       System.out.println(">>> File downloaded : " + downloadedSiftsFile);
273     } catch (IOException ex)
274     {
275       throw new SiftsException(ex.getMessage());
276     }
277     return new File(downloadedSiftsFile);
278   }
279
280   /**
281    * Delete the SIFTs file for the given PDB Id in the local SIFTs download
282    * directory
283    * 
284    * @param pdbId
285    * @return true if the file was deleted or doesn't exist
286    */
287   public static boolean deleteSiftsFileByPDBId(String pdbId)
288   {
289     File siftsFile = new File(SiftsSettings.getSiftDownloadDirectory()
290             + pdbId.toLowerCase()
291             + ".xml.gz");
292     if (siftsFile.exists())
293     {
294       return siftsFile.delete();
295     }
296     return true;
297   }
298
299
300   /**
301    * Get a valid SIFTs DBRef for the given sequence current SIFTs entry
302    * 
303    * @param seq
304    *          - the target sequence for the operation
305    * @return a valid DBRefEntry that is SIFTs compatible
306    * @throws Exception
307    *           if no valid source DBRefEntry was found for the given sequences
308    */
309   public DBRefEntryI getValidSourceDBRef(SequenceI seq)
310           throws SiftsException
311   {
312     DBRefEntryI sourceDBRef = null;
313     sourceDBRef = seq.getSourceDBRef();
314     if (sourceDBRef != null && isValidDBRefEntry(sourceDBRef))
315     {
316       return sourceDBRef;
317     }
318     else
319     {
320       DBRefEntry[] dbRefs = seq.getDBRefs();
321       // if (dbRefs == null || dbRefs.length < 1)
322       // {
323       // // final SequenceI[] seqs = new SequenceI[] { seq };
324       // // new jalview.ws.DBRefFetcher(seqs, null, null, null, false)
325       // // .fetchDBRefs(true);
326       // dbRefs = seq.getDBRefs();
327       // }
328
329       if (dbRefs == null || dbRefs.length < 1)
330       {
331         throw new SiftsException("Could not get source DB Ref");
332       }
333
334       for (DBRefEntryI dbRef : dbRefs)
335       {
336         if (dbRef == null || dbRef.getAccessionId() == null
337                 || dbRef.getSource() == null)
338         {
339           continue;
340         }
341         if (isFoundInSiftsEntry(dbRef.getAccessionId())
342                 && (dbRef.getSource().equalsIgnoreCase(DBRefSource.UNIPROT) || dbRef
343                         .getSource().equalsIgnoreCase(DBRefSource.PDB)))
344         {
345           return dbRef;
346         }
347       }
348     }
349     if (sourceDBRef != null && isValidDBRefEntry(sourceDBRef))
350     {
351       return sourceDBRef;
352     }
353     throw new SiftsException("Could not get source DB Ref");
354   }
355
356
357   /**
358    * Check that the DBRef Entry is properly populated and is available in this
359    * SiftClient instance
360    * 
361    * @param entry
362    *          - DBRefEntry to validate
363    * @return true validation is successful otherwise false is returned.
364    */
365   private boolean isValidDBRefEntry(DBRefEntryI entry)
366   {
367     return entry != null && entry.getAccessionId() != null
368             && isFoundInSiftsEntry(entry.getAccessionId());
369   }
370
371   @Override
372   public HashSet<String> getAllMappingAccession()
373   {
374     HashSet<String> accessions = new HashSet<String>();
375     List<Entity> entities = siftsEntry.getEntity();
376     for (Entity entity : entities)
377     {
378       List<Segment> segments = entity.getSegment();
379       for (Segment segment : segments)
380       {
381         List<MapRegion> mapRegions = segment.getListMapRegion()
382                 .getMapRegion();
383         for (MapRegion mapRegion : mapRegions)
384         {
385           accessions.add(mapRegion.getDb().getDbAccessionId());
386         }
387       }
388     }
389     return accessions;
390   }
391
392   @Override
393   public StructureMapping getSiftsStructureMapping(SequenceI seq,
394           String pdbFile, String chain) throws SiftsException
395   {
396     structId = (chain == null) ? pdbId : pdbId + "|" + chain;
397     System.out.println("Getting mapping for: " + pdbId + "|" + chain
398             + " : seq- " + seq.getName());
399
400     final StringBuilder mappingDetails = new StringBuilder(128);
401     PrintStream ps = new PrintStream(System.out)
402     {
403       @Override
404       public void print(String x)
405       {
406         mappingDetails.append(x);
407       }
408
409       @Override
410       public void println()
411       {
412         mappingDetails.append(NEWLINE);
413       }
414     };
415     HashMap<Integer, int[]> mapping = getGreedyMapping(chain, seq, ps);
416
417     String mappingOutput = mappingDetails.toString();
418     StructureMapping siftsMapping = new StructureMapping(seq, pdbFile,
419             pdbId, chain, mapping,
420             mappingOutput);
421     return siftsMapping;
422   }
423
424   @Override
425   public HashMap<Integer, int[]> getGreedyMapping(String entityId, SequenceI seq,
426           java.io.PrintStream os)
427  throws SiftsException
428   {
429     ArrayList<Integer> omitNonObserved = new ArrayList<Integer>();
430     int nonObservedShiftIndex = 0;
431     System.out.println("Generating mappings for : " + entityId);
432     Entity entity = null;
433     entity = getEntityById(entityId);
434     String originalSeq = AlignSeq.extractGaps(
435             jalview.util.Comparison.GapChars,
436             seq.getSequenceAsString());
437     HashMap<Integer, int[]> mapping = new HashMap<Integer, int[]>();
438     DBRefEntryI sourceDBRef = seq.getSourceDBRef();
439     if (sourceDBRef == null)
440     {
441       sourceDBRef = getValidSourceDBRef(seq);
442       // TODO ensure sequence start/end is in the same coordinate system and
443       // consistent with the choosen sourceDBRef
444     }
445
446     // set sequence coordinate system - default value is UniProt
447     if (sourceDBRef.getSource().equalsIgnoreCase(DBRefSource.PDB))
448     {
449       seqCoordSys = CoordinateSys.PDB;
450     }
451
452     HashSet<String> dbRefAccessionIdsString = new HashSet<String>();
453     for (DBRefEntry dbref : seq.getDBRefs())
454     {
455       dbRefAccessionIdsString.add(dbref.getAccessionId().toLowerCase());
456     }
457     dbRefAccessionIdsString.add(sourceDBRef.getAccessionId().toLowerCase());
458
459     curDBRefAccessionIdsString = dbRefAccessionIdsString;
460     curSourceDBRef = sourceDBRef.getAccessionId();
461
462     TreeMap<Integer, String> resNumMap = new TreeMap<Integer, String>();
463     List<Segment> segments = entity.getSegment();
464     for (Segment segment : segments)
465     {
466       segStartEnd = segment.getStart() + " - " + segment.getEnd();
467       System.out.println("Mappging segments : " + segment.getSegId() + "\\"
468               + segStartEnd);
469       List<Residue> residues = segment.getListResidue().getResidue();
470       for (Residue residue : residues)
471       {
472         int currSeqIndex = UNASSIGNED;
473         List<CrossRefDb> cRefDbs = residue.getCrossRefDb();
474         CrossRefDb pdbRefDb = null;
475         for (CrossRefDb cRefDb : cRefDbs)
476         {
477           if (cRefDb.getDbSource().equalsIgnoreCase(DBRefSource.PDB))
478           {
479             pdbRefDb = cRefDb;
480           }
481           if (cRefDb.getDbCoordSys()
482                   .equalsIgnoreCase(seqCoordSys.getName())
483                   && isAccessionMatched(cRefDb.getDbAccessionId()))
484           {
485             String resNumIndexString = cRefDb.getDbResNum()
486                     .equalsIgnoreCase("None") ? String.valueOf(UNASSIGNED)
487                     : cRefDb.getDbResNum();
488             currSeqIndex = Integer.valueOf(resNumIndexString);
489             if (pdbRefDb != null)
490             {
491               break;// exit loop if pdb and uniprot are already found
492             }
493           }
494         }
495         if (currSeqIndex == UNASSIGNED)
496         {
497           continue;
498         }
499         if (currSeqIndex > seq.getStart() && currSeqIndex <= seq.getEnd())
500         {
501           int resNum;
502           try
503           {
504             resNum = (pdbRefDb == null) ? Integer.valueOf(residue
505                   .getDbResNum()) : Integer.valueOf(pdbRefDb.getDbResNum());
506           } catch (NumberFormatException nfe)
507           {
508             resNum = (pdbRefDb == null) ? Integer.valueOf(residue
509                     .getDbResNum()) : Integer.valueOf(pdbRefDb
510                     .getDbResNum().split("[a-zA-Z]")[0]);
511           }
512
513           if (isResidueObserved(residue)
514                   || seqCoordSys == CoordinateSys.UNIPROT)
515           {
516             char resCharCode = ResidueProperties
517                     .getSingleCharacterCode(residue.getDbResName());
518             resNumMap.put(currSeqIndex, String.valueOf(resCharCode));
519           }
520           else
521           {
522             omitNonObserved.add(currSeqIndex);
523             ++nonObservedShiftIndex;
524           }
525           mapping.put(currSeqIndex - nonObservedShiftIndex, new int[] {
526               Integer.valueOf(resNum), UNASSIGNED });
527         }
528       }
529     }
530     try
531     {
532       populateAtomPositions(entityId, mapping);
533     } catch (Exception e)
534     {
535       e.printStackTrace();
536     }
537     padWithGaps(resNumMap, omitNonObserved);
538     int seqStart = UNASSIGNED;
539     int seqEnd = UNASSIGNED;
540     int pdbStart = UNASSIGNED;
541     int pdbEnd = UNASSIGNED;
542
543     Integer[] keys = mapping.keySet().toArray(new Integer[0]);
544     Arrays.sort(keys);
545     seqStart = keys[0];
546     seqEnd = keys[keys.length - 1];
547
548     String matchedSeq = originalSeq;
549     if (seqStart != UNASSIGNED)
550     {
551       pdbStart = mapping.get(seqStart)[PDB_RES_POS];
552       pdbEnd = mapping.get(seqEnd)[PDB_RES_POS];
553       int orignalSeqStart = seq.getStart();
554       if (orignalSeqStart >= 1)
555       {
556         int subSeqStart = seqStart - orignalSeqStart;
557         int subSeqEnd = seqEnd - (orignalSeqStart - 1);
558         subSeqEnd = originalSeq.length() < subSeqEnd ? originalSeq.length()
559                 : subSeqEnd;
560         matchedSeq = originalSeq.substring(subSeqStart, subSeqEnd);
561       }
562     }
563
564     StringBuilder targetStrucSeqs = new StringBuilder();
565     for (String res : resNumMap.values())
566     {
567       targetStrucSeqs.append(res);
568     }
569
570     if (os != null)
571     {
572       MappingOutputPojo mop = new MappingOutputPojo();
573       mop.setSeqStart(seqStart);
574       mop.setSeqEnd(seqEnd);
575       mop.setSeqName(seq.getName());
576       mop.setSeqResidue(matchedSeq);
577
578       mop.setStrStart(pdbStart);
579       mop.setStrEnd(pdbEnd);
580       mop.setStrName(structId);
581       mop.setStrResidue(targetStrucSeqs.toString());
582
583       mop.setType("pep");
584       os.print(getMappingOutput(mop).toString());
585     }
586     return mapping;
587   }
588
589   /**
590    * Checks if the residue instance is marked 'Not_observed' or not
591    * 
592    * @param residue
593    * @return
594    */
595   private boolean isResidueObserved(Residue residue)
596   {
597     String annotation = getResidueAnnotaiton(residue,
598             ResidueDetailType.ANNOTATION);
599     if (annotation == null)
600     {
601       return true;
602     }
603     if (!annotation.equalsIgnoreCase(NOT_FOUND)
604             && annotation.equalsIgnoreCase(NOT_OBSERVED))
605     {
606       return false;
607     }
608     return true;
609   }
610
611   /**
612    * Get annotation String for a given residue and annotation type
613    * 
614    * @param residue
615    * @param type
616    * @return
617    */
618   private String getResidueAnnotaiton(Residue residue,
619           ResidueDetailType type)
620   {
621     List<ResidueDetail> resDetails = residue.getResidueDetail();
622     for (ResidueDetail resDetail : resDetails)
623     {
624       if (resDetail.getProperty().equalsIgnoreCase(type.getCode()))
625       {
626         return resDetail.getContent();
627       }
628     }
629     return NOT_FOUND;
630   }
631
632   @Override
633   public boolean isAccessionMatched(String accession)
634   {
635     boolean isStrictMatch = true;
636     return isStrictMatch ? curSourceDBRef.equalsIgnoreCase(accession)
637             : curDBRefAccessionIdsString.contains(accession.toLowerCase());
638   }
639
640   private boolean isFoundInSiftsEntry(String accessionId)
641   {
642     return accessionId != null
643             && getAllMappingAccession().contains(accessionId);
644   }
645
646   /**
647    * Pad omitted residue positions in PDB sequence with gaps
648    * 
649    * @param resNumMap
650    */
651   void padWithGaps(TreeMap<Integer, String> resNumMap,
652           ArrayList<Integer> omitNonObserved)
653   {
654     if (resNumMap == null || resNumMap.isEmpty())
655     {
656       return;
657     }
658     Integer[] keys = resNumMap.keySet().toArray(new Integer[0]);
659     Arrays.sort(keys);
660     int firstIndex = keys[0];
661     int lastIndex = keys[keys.length - 1];
662     System.out.println("Min value " + firstIndex);
663     System.out.println("Max value " + lastIndex);
664     for (int x = firstIndex; x <= lastIndex; x++)
665     {
666       if (!resNumMap.containsKey(x) && !omitNonObserved.contains(x))
667       {
668         resNumMap.put(x, "-");
669       }
670     }
671   }
672
673
674   /**
675    * 
676    * @param chainId
677    *          Target chain to populate mapping of its atom positions.
678    * @param mapping
679    *          Two dimension array of residue index versus atom position
680    * @throws IllegalArgumentException
681    *           Thrown if chainId or mapping is null
682    */
683   void populateAtomPositions(String chainId, HashMap<Integer, int[]> mapping)
684           throws IllegalArgumentException
685   {
686     PDBChain chain = pdb.findChain(chainId);
687     if (chain == null || mapping == null)
688     {
689       throw new IllegalArgumentException(
690               "Chain id or mapping must not be null.");
691     }
692     for (int[] map : mapping.values())
693     {
694       if (map[PDB_RES_POS] != UNASSIGNED)
695       {
696         map[PDB_ATOM_POS] = getAtomIndex(map[PDB_RES_POS], chain.atoms);
697       }
698     }
699   }
700
701   /**
702    * 
703    * @param residueIndex
704    *          The residue index used for the search
705    * @param atoms
706    *          A collection of Atom to search
707    * @return atom position for the given residue index
708    */
709   int getAtomIndex(int residueIndex, Collection<Atom> atoms)
710   {
711     if (atoms == null)
712     {
713       throw new IllegalArgumentException(
714               "atoms collection must not be null!");
715     }
716     for (Atom atom : atoms)
717     {
718       if (atom.resNumber == residueIndex)
719       {
720         return atom.atomIndex;
721       }
722     }
723     return UNASSIGNED;
724   }
725
726   @Override
727   public Entity getEntityById(String id) throws SiftsException
728   {
729     List<Entity> entities = siftsEntry.getEntity();
730     for (Entity entity : entities)
731     {
732       if (!entity.getEntityId().equalsIgnoreCase(id))
733       {
734         continue;
735       }
736       return entity;
737     }
738     throw new SiftsException("Entity " + id + " not found");
739   }
740
741   @Override
742   public String[] getEntryDBs()
743   {
744     System.out.println("\nListing DB entries...");
745     List<String> availDbs = new ArrayList<String>();
746     List<Db> dbs = siftsEntry.getListDB().getDb();
747     for (Db db : dbs)
748     {
749       availDbs.add(db.getDbSource());
750       System.out.println(db.getDbSource() + " | " + db.getDbCoordSys());
751     }
752     return availDbs.toArray(new String[0]);
753   }
754
755   @Override
756   public StringBuffer getMappingOutput(MappingOutputPojo mp)
757           throws SiftsException
758   {
759     String seqRes = mp.getSeqResidue();
760     String seqName = mp.getSeqName();
761     int sStart = mp.getSeqStart();
762     int sEnd = mp.getSeqEnd();
763
764     String strRes = mp.getStrResidue();
765     String strName = mp.getStrName();
766     int pdbStart = mp.getStrStart();
767     int pdbEnd = mp.getStrEnd();
768     
769     String type = mp.getType();
770     
771     int maxid = (seqName.length() >= strName.length()) ? seqName.length()
772             : strName.length();
773     int len = 72 - maxid - 1;
774
775     int nochunks = ((seqRes.length()) / len)
776             + ((seqRes.length()) % len > 0 ? 1 : 0);
777     // output mappings
778     StringBuffer output = new StringBuffer();
779     output.append(NEWLINE);
780     output.append("Sequence ⟷ Structure mapping details").append(NEWLINE);
781     output.append("Method: SIFTS");
782     output.append(NEWLINE).append(NEWLINE);
783
784     output.append(new Format("%" + maxid + "s").form(seqName));
785     output.append(" :  ");
786     output.append(String.valueOf(sStart));
787     output.append(" - ");
788     output.append(String.valueOf(sEnd));
789     output.append(" Maps to ");
790     output.append(NEWLINE);
791     output.append(new Format("%" + maxid + "s").form(structId));
792     output.append(" :  ");
793     output.append(String.valueOf(pdbStart));
794     output.append(" - ");
795     output.append(String.valueOf(pdbEnd));
796     output.append(NEWLINE).append(NEWLINE);
797     
798     int matchedSeqCount = 0;
799     for (int j = 0; j < nochunks; j++)
800     {
801       // Print the first aligned sequence
802       output.append(new Format("%" + (maxid) + "s").form(seqName)).append(
803               " ");
804
805       for (int i = 0; i < len; i++)
806       {
807         if ((i + (j * len)) < seqRes.length())
808         {
809           output.append(seqRes.charAt(i + (j * len)));
810         }
811       }
812
813       output.append(NEWLINE);
814       output.append(new Format("%" + (maxid) + "s").form(" ")).append(" ");
815
816       // Print out the matching chars
817       for (int i = 0; i < len; i++)
818       {
819         try
820         {
821         if ((i + (j * len)) < seqRes.length())
822         {
823           if (seqRes.charAt(i + (j * len)) == strRes.charAt(i + (j * len))
824                   && !jalview.util.Comparison.isGap(seqRes.charAt(i
825                           + (j * len))))
826           {
827               matchedSeqCount++;
828             output.append("|");
829           }
830           else if (type.equals("pep"))
831           {
832             if (ResidueProperties.getPAM250(seqRes.charAt(i + (j * len)),
833                     strRes.charAt(i + (j * len))) > 0)
834             {
835               output.append(".");
836             }
837             else
838             {
839               output.append(" ");
840             }
841           }
842           else
843           {
844             output.append(" ");
845           }
846         }
847         } catch (IndexOutOfBoundsException e)
848         {
849           continue;
850         }
851       }
852       // Now print the second aligned sequence
853       output = output.append(NEWLINE);
854       output = output.append(new Format("%" + (maxid) + "s").form(strName))
855               .append(" ");
856       for (int i = 0; i < len; i++)
857       {
858         if ((i + (j * len)) < strRes.length())
859         {
860           output.append(strRes.charAt(i + (j * len)));
861         }
862       }
863       output.append(NEWLINE).append(NEWLINE);
864     }
865     float pid = (float) matchedSeqCount / seqRes.length() * 100;
866     if (pid < 2)
867     {
868       throw new SiftsException("Low PID detected for SIFTs mapping...");
869     }
870     output.append("Length of alignment = " + seqRes.length())
871             .append(NEWLINE);
872     output.append(new Format("Percentage ID = %2.2f").form(pid));
873     output.append(NEWLINE);
874     return output;
875   }
876   
877   @Override
878   public int getEntityCount()
879   {
880     return siftsEntry.getEntity().size();
881   }
882
883   @Override
884   public String getDbAccessionId()
885   {
886     return siftsEntry.getDbAccessionId();
887   }
888
889   @Override
890   public String getDbCoordSys()
891   {
892     return siftsEntry.getDbCoordSys();
893   }
894
895   @Override
896   public String getDbEvidence()
897   {
898     return siftsEntry.getDbEvidence();
899   }
900
901   @Override
902   public String getDbSource()
903   {
904     return siftsEntry.getDbSource();
905   }
906
907   @Override
908   public String getDbVersion()
909   {
910     return siftsEntry.getDbVersion();
911   }
912 }