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