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