JAL-1479 refactored DBRef fetching code to StructureChooser to enable applet build
[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         throw new SiftsException("Could not get source DB Ref");
324       }
325
326       for (DBRefEntryI dbRef : dbRefs)
327       {
328         if (dbRef == null || dbRef.getAccessionId() == null
329                 || dbRef.getSource() == null)
330         {
331           continue;
332         }
333         if (isFoundInSiftsEntry(dbRef.getAccessionId())
334                 && (dbRef.getSource().equalsIgnoreCase(DBRefSource.UNIPROT) || dbRef
335                         .getSource().equalsIgnoreCase(DBRefSource.PDB)))
336         {
337           return dbRef;
338         }
339       }
340     }
341     if (sourceDBRef != null && isValidDBRefEntry(sourceDBRef))
342     {
343       return sourceDBRef;
344     }
345     throw new SiftsException("Could not get source DB Ref");
346   }
347
348
349   /**
350    * Check that the DBRef Entry is properly populated and is available in this
351    * SiftClient instance
352    * 
353    * @param entry
354    *          - DBRefEntry to validate
355    * @return true validation is successful otherwise false is returned.
356    */
357   private boolean isValidDBRefEntry(DBRefEntryI entry)
358   {
359     return entry != null && entry.getAccessionId() != null
360             && isFoundInSiftsEntry(entry.getAccessionId());
361   }
362
363   @Override
364   public HashSet<String> getAllMappingAccession()
365   {
366     HashSet<String> accessions = new HashSet<String>();
367     List<Entity> entities = siftsEntry.getEntity();
368     for (Entity entity : entities)
369     {
370       List<Segment> segments = entity.getSegment();
371       for (Segment segment : segments)
372       {
373         List<MapRegion> mapRegions = segment.getListMapRegion()
374                 .getMapRegion();
375         for (MapRegion mapRegion : mapRegions)
376         {
377           accessions.add(mapRegion.getDb().getDbAccessionId());
378         }
379       }
380     }
381     return accessions;
382   }
383
384   @Override
385   public StructureMapping getSiftsStructureMapping(SequenceI seq,
386           String pdbFile, String chain) throws SiftsException
387   {
388     structId = (chain == null) ? pdbId : pdbId + "|" + chain;
389     System.out.println("Getting mapping for: " + pdbId + "|" + chain
390             + " : seq- " + seq.getName());
391
392     final StringBuilder mappingDetails = new StringBuilder(128);
393     PrintStream ps = new PrintStream(System.out)
394     {
395       @Override
396       public void print(String x)
397       {
398         mappingDetails.append(x);
399       }
400
401       @Override
402       public void println()
403       {
404         mappingDetails.append(NEWLINE);
405       }
406     };
407     HashMap<Integer, int[]> mapping = getGreedyMapping(chain, seq, ps);
408
409     String mappingOutput = mappingDetails.toString();
410     StructureMapping siftsMapping = new StructureMapping(seq, pdbFile,
411             pdbId, chain, mapping,
412             mappingOutput);
413     return siftsMapping;
414   }
415
416   @Override
417   public HashMap<Integer, int[]> getGreedyMapping(String entityId, SequenceI seq,
418           java.io.PrintStream os)
419  throws SiftsException
420   {
421     ArrayList<Integer> omitNonObserved = new ArrayList<Integer>();
422     int nonObservedShiftIndex = 0;
423     System.out.println("Generating mappings for : " + entityId);
424     Entity entity = null;
425     entity = getEntityById(entityId);
426     String originalSeq = AlignSeq.extractGaps(
427             jalview.util.Comparison.GapChars,
428             seq.getSequenceAsString());
429     HashMap<Integer, int[]> mapping = new HashMap<Integer, int[]>();
430     DBRefEntryI sourceDBRef = seq.getSourceDBRef();
431     if (sourceDBRef == null)
432     {
433       sourceDBRef = getValidSourceDBRef(seq);
434       // TODO ensure sequence start/end is in the same coordinate system and
435       // consistent with the choosen sourceDBRef
436     }
437
438     // set sequence coordinate system - default value is UniProt
439     if (sourceDBRef.getSource().equalsIgnoreCase(DBRefSource.PDB))
440     {
441       seqCoordSys = CoordinateSys.PDB;
442     }
443
444     HashSet<String> dbRefAccessionIdsString = new HashSet<String>();
445     for (DBRefEntry dbref : seq.getDBRefs())
446     {
447       dbRefAccessionIdsString.add(dbref.getAccessionId().toLowerCase());
448     }
449     dbRefAccessionIdsString.add(sourceDBRef.getAccessionId().toLowerCase());
450
451     curDBRefAccessionIdsString = dbRefAccessionIdsString;
452     curSourceDBRef = sourceDBRef.getAccessionId();
453
454     TreeMap<Integer, String> resNumMap = new TreeMap<Integer, String>();
455     List<Segment> segments = entity.getSegment();
456     for (Segment segment : segments)
457     {
458       segStartEnd = segment.getStart() + " - " + segment.getEnd();
459       System.out.println("Mappging segments : " + segment.getSegId() + "\\"
460               + segStartEnd);
461       List<Residue> residues = segment.getListResidue().getResidue();
462       for (Residue residue : residues)
463       {
464         int currSeqIndex = UNASSIGNED;
465         List<CrossRefDb> cRefDbs = residue.getCrossRefDb();
466         CrossRefDb pdbRefDb = null;
467         for (CrossRefDb cRefDb : cRefDbs)
468         {
469           if (cRefDb.getDbSource().equalsIgnoreCase(DBRefSource.PDB))
470           {
471             pdbRefDb = cRefDb;
472           }
473           if (cRefDb.getDbCoordSys()
474                   .equalsIgnoreCase(seqCoordSys.getName())
475                   && isAccessionMatched(cRefDb.getDbAccessionId()))
476           {
477             String resNumIndexString = cRefDb.getDbResNum()
478                     .equalsIgnoreCase("None") ? String.valueOf(UNASSIGNED)
479                     : cRefDb.getDbResNum();
480             currSeqIndex = Integer.valueOf(resNumIndexString);
481             if (pdbRefDb != null)
482             {
483               break;// exit loop if pdb and uniprot are already found
484             }
485           }
486         }
487         if (currSeqIndex == UNASSIGNED)
488         {
489           continue;
490         }
491         if (currSeqIndex > seq.getStart() && currSeqIndex <= seq.getEnd())
492         {
493           int resNum;
494           try
495           {
496             resNum = (pdbRefDb == null) ? Integer.valueOf(residue
497                   .getDbResNum()) : Integer.valueOf(pdbRefDb.getDbResNum());
498           } catch (NumberFormatException nfe)
499           {
500             resNum = (pdbRefDb == null) ? Integer.valueOf(residue
501                     .getDbResNum()) : Integer.valueOf(pdbRefDb
502                     .getDbResNum().split("[a-zA-Z]")[0]);
503           }
504
505           if (isResidueObserved(residue)
506                   || seqCoordSys == CoordinateSys.UNIPROT)
507           {
508             char resCharCode = ResidueProperties
509                     .getSingleCharacterCode(residue.getDbResName());
510             resNumMap.put(currSeqIndex, String.valueOf(resCharCode));
511           }
512           else
513           {
514             omitNonObserved.add(currSeqIndex);
515             ++nonObservedShiftIndex;
516           }
517           mapping.put(currSeqIndex - nonObservedShiftIndex, new int[] {
518               Integer.valueOf(resNum), UNASSIGNED });
519         }
520       }
521     }
522     try
523     {
524       populateAtomPositions(entityId, mapping);
525     } catch (Exception e)
526     {
527       e.printStackTrace();
528     }
529     padWithGaps(resNumMap, omitNonObserved);
530     int seqStart = UNASSIGNED;
531     int seqEnd = UNASSIGNED;
532     int pdbStart = UNASSIGNED;
533     int pdbEnd = UNASSIGNED;
534
535     Integer[] keys = mapping.keySet().toArray(new Integer[0]);
536     Arrays.sort(keys);
537     seqStart = keys[0];
538     seqEnd = keys[keys.length - 1];
539
540     String matchedSeq = originalSeq;
541     if (seqStart != UNASSIGNED)
542     {
543       pdbStart = mapping.get(seqStart)[PDB_RES_POS];
544       pdbEnd = mapping.get(seqEnd)[PDB_RES_POS];
545       int orignalSeqStart = seq.getStart();
546       if (orignalSeqStart >= 1)
547       {
548         int subSeqStart = seqStart - orignalSeqStart;
549         int subSeqEnd = seqEnd - (orignalSeqStart - 1);
550         subSeqEnd = originalSeq.length() < subSeqEnd ? originalSeq.length()
551                 : subSeqEnd;
552         matchedSeq = originalSeq.substring(subSeqStart, subSeqEnd);
553       }
554     }
555
556     StringBuilder targetStrucSeqs = new StringBuilder();
557     for (String res : resNumMap.values())
558     {
559       targetStrucSeqs.append(res);
560     }
561
562     if (os != null)
563     {
564       MappingOutputPojo mop = new MappingOutputPojo();
565       mop.setSeqStart(seqStart);
566       mop.setSeqEnd(seqEnd);
567       mop.setSeqName(seq.getName());
568       mop.setSeqResidue(matchedSeq);
569
570       mop.setStrStart(pdbStart);
571       mop.setStrEnd(pdbEnd);
572       mop.setStrName(structId);
573       mop.setStrResidue(targetStrucSeqs.toString());
574
575       mop.setType("pep");
576       os.print(getMappingOutput(mop).toString());
577     }
578     return mapping;
579   }
580
581   /**
582    * Checks if the residue instance is marked 'Not_observed' or not
583    * 
584    * @param residue
585    * @return
586    */
587   private boolean isResidueObserved(Residue residue)
588   {
589     String annotation = getResidueAnnotaiton(residue,
590             ResidueDetailType.ANNOTATION);
591     if (annotation == null)
592     {
593       return true;
594     }
595     if (!annotation.equalsIgnoreCase(NOT_FOUND)
596             && annotation.equalsIgnoreCase(NOT_OBSERVED))
597     {
598       return false;
599     }
600     return true;
601   }
602
603   /**
604    * Get annotation String for a given residue and annotation type
605    * 
606    * @param residue
607    * @param type
608    * @return
609    */
610   private String getResidueAnnotaiton(Residue residue,
611           ResidueDetailType type)
612   {
613     List<ResidueDetail> resDetails = residue.getResidueDetail();
614     for (ResidueDetail resDetail : resDetails)
615     {
616       if (resDetail.getProperty().equalsIgnoreCase(type.getCode()))
617       {
618         return resDetail.getContent();
619       }
620     }
621     return NOT_FOUND;
622   }
623
624   @Override
625   public boolean isAccessionMatched(String accession)
626   {
627     boolean isStrictMatch = true;
628     return isStrictMatch ? curSourceDBRef.equalsIgnoreCase(accession)
629             : curDBRefAccessionIdsString.contains(accession.toLowerCase());
630   }
631
632   private boolean isFoundInSiftsEntry(String accessionId)
633   {
634     return accessionId != null
635             && getAllMappingAccession().contains(accessionId);
636   }
637
638   /**
639    * Pad omitted residue positions in PDB sequence with gaps
640    * 
641    * @param resNumMap
642    */
643   void padWithGaps(TreeMap<Integer, String> resNumMap,
644           ArrayList<Integer> omitNonObserved)
645   {
646     if (resNumMap == null || resNumMap.isEmpty())
647     {
648       return;
649     }
650     Integer[] keys = resNumMap.keySet().toArray(new Integer[0]);
651     Arrays.sort(keys);
652     int firstIndex = keys[0];
653     int lastIndex = keys[keys.length - 1];
654     System.out.println("Min value " + firstIndex);
655     System.out.println("Max value " + lastIndex);
656     for (int x = firstIndex; x <= lastIndex; x++)
657     {
658       if (!resNumMap.containsKey(x) && !omitNonObserved.contains(x))
659       {
660         resNumMap.put(x, "-");
661       }
662     }
663   }
664
665
666   /**
667    * 
668    * @param chainId
669    *          Target chain to populate mapping of its atom positions.
670    * @param mapping
671    *          Two dimension array of residue index versus atom position
672    * @throws IllegalArgumentException
673    *           Thrown if chainId or mapping is null
674    */
675   void populateAtomPositions(String chainId, HashMap<Integer, int[]> mapping)
676           throws IllegalArgumentException
677   {
678     PDBChain chain = pdb.findChain(chainId);
679     if (chain == null || mapping == null)
680     {
681       throw new IllegalArgumentException(
682               "Chain id or mapping must not be null.");
683     }
684     for (int[] map : mapping.values())
685     {
686       if (map[PDB_RES_POS] != UNASSIGNED)
687       {
688         map[PDB_ATOM_POS] = getAtomIndex(map[PDB_RES_POS], chain.atoms);
689       }
690     }
691   }
692
693   /**
694    * 
695    * @param residueIndex
696    *          The residue index used for the search
697    * @param atoms
698    *          A collection of Atom to search
699    * @return atom position for the given residue index
700    */
701   int getAtomIndex(int residueIndex, Collection<Atom> atoms)
702   {
703     if (atoms == null)
704     {
705       throw new IllegalArgumentException(
706               "atoms collection must not be null!");
707     }
708     for (Atom atom : atoms)
709     {
710       if (atom.resNumber == residueIndex)
711       {
712         return atom.atomIndex;
713       }
714     }
715     return UNASSIGNED;
716   }
717
718   @Override
719   public Entity getEntityById(String id) throws SiftsException
720   {
721     List<Entity> entities = siftsEntry.getEntity();
722     for (Entity entity : entities)
723     {
724       if (!entity.getEntityId().equalsIgnoreCase(id))
725       {
726         continue;
727       }
728       return entity;
729     }
730     throw new SiftsException("Entity " + id + " not found");
731   }
732
733   @Override
734   public String[] getEntryDBs()
735   {
736     System.out.println("\nListing DB entries...");
737     List<String> availDbs = new ArrayList<String>();
738     List<Db> dbs = siftsEntry.getListDB().getDb();
739     for (Db db : dbs)
740     {
741       availDbs.add(db.getDbSource());
742       System.out.println(db.getDbSource() + " | " + db.getDbCoordSys());
743     }
744     return availDbs.toArray(new String[0]);
745   }
746
747   @Override
748   public StringBuffer getMappingOutput(MappingOutputPojo mp)
749           throws SiftsException
750   {
751     String seqRes = mp.getSeqResidue();
752     String seqName = mp.getSeqName();
753     int sStart = mp.getSeqStart();
754     int sEnd = mp.getSeqEnd();
755
756     String strRes = mp.getStrResidue();
757     String strName = mp.getStrName();
758     int pdbStart = mp.getStrStart();
759     int pdbEnd = mp.getStrEnd();
760     
761     String type = mp.getType();
762     
763     int maxid = (seqName.length() >= strName.length()) ? seqName.length()
764             : strName.length();
765     int len = 72 - maxid - 1;
766
767     int nochunks = ((seqRes.length()) / len)
768             + ((seqRes.length()) % len > 0 ? 1 : 0);
769     // output mappings
770     StringBuffer output = new StringBuffer();
771     output.append(NEWLINE);
772     output.append("Sequence ⟷ Structure mapping details").append(NEWLINE);
773     output.append("Method: SIFTS");
774     output.append(NEWLINE).append(NEWLINE);
775
776     output.append(new Format("%" + maxid + "s").form(seqName));
777     output.append(" :  ");
778     output.append(String.valueOf(sStart));
779     output.append(" - ");
780     output.append(String.valueOf(sEnd));
781     output.append(" Maps to ");
782     output.append(NEWLINE);
783     output.append(new Format("%" + maxid + "s").form(structId));
784     output.append(" :  ");
785     output.append(String.valueOf(pdbStart));
786     output.append(" - ");
787     output.append(String.valueOf(pdbEnd));
788     output.append(NEWLINE).append(NEWLINE);
789     
790     int matchedSeqCount = 0;
791     for (int j = 0; j < nochunks; j++)
792     {
793       // Print the first aligned sequence
794       output.append(new Format("%" + (maxid) + "s").form(seqName)).append(
795               " ");
796
797       for (int i = 0; i < len; i++)
798       {
799         if ((i + (j * len)) < seqRes.length())
800         {
801           output.append(seqRes.charAt(i + (j * len)));
802         }
803       }
804
805       output.append(NEWLINE);
806       output.append(new Format("%" + (maxid) + "s").form(" ")).append(" ");
807
808       // Print out the matching chars
809       for (int i = 0; i < len; i++)
810       {
811         try
812         {
813         if ((i + (j * len)) < seqRes.length())
814         {
815           if (seqRes.charAt(i + (j * len)) == strRes.charAt(i + (j * len))
816                   && !jalview.util.Comparison.isGap(seqRes.charAt(i
817                           + (j * len))))
818           {
819               matchedSeqCount++;
820             output.append("|");
821           }
822           else if (type.equals("pep"))
823           {
824             if (ResidueProperties.getPAM250(seqRes.charAt(i + (j * len)),
825                     strRes.charAt(i + (j * len))) > 0)
826             {
827               output.append(".");
828             }
829             else
830             {
831               output.append(" ");
832             }
833           }
834           else
835           {
836             output.append(" ");
837           }
838         }
839         } catch (IndexOutOfBoundsException e)
840         {
841           continue;
842         }
843       }
844       // Now print the second aligned sequence
845       output = output.append(NEWLINE);
846       output = output.append(new Format("%" + (maxid) + "s").form(strName))
847               .append(" ");
848       for (int i = 0; i < len; i++)
849       {
850         if ((i + (j * len)) < strRes.length())
851         {
852           output.append(strRes.charAt(i + (j * len)));
853         }
854       }
855       output.append(NEWLINE).append(NEWLINE);
856     }
857     float pid = (float) matchedSeqCount / seqRes.length() * 100;
858     if (pid < 2)
859     {
860       throw new SiftsException("Low PID detected for SIFTs mapping...");
861     }
862     output.append("Length of alignment = " + seqRes.length())
863             .append(NEWLINE);
864     output.append(new Format("Percentage ID = %2.2f").form(pid));
865     output.append(NEWLINE);
866     return output;
867   }
868   
869   @Override
870   public int getEntityCount()
871   {
872     return siftsEntry.getEntity().size();
873   }
874
875   @Override
876   public String getDbAccessionId()
877   {
878     return siftsEntry.getDbAccessionId();
879   }
880
881   @Override
882   public String getDbCoordSys()
883   {
884     return siftsEntry.getDbCoordSys();
885   }
886
887   @Override
888   public String getDbEvidence()
889   {
890     return siftsEntry.getDbEvidence();
891   }
892
893   @Override
894   public String getDbSource()
895   {
896     return siftsEntry.getDbSource();
897   }
898
899   @Override
900   public String getDbVersion()
901   {
902     return siftsEntry.getDbVersion();
903   }
904 }