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