132826075f265c091a662b3185ae0c7785d86b54
[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.io.StructureFile;
30 import jalview.schemes.ResidueProperties;
31 import jalview.structure.StructureMapping;
32 import jalview.structure.StructureMappingClient;
33 import jalview.structures.models.MappingOutputModel;
34 import jalview.util.Comparison;
35 import jalview.util.DBRefUtils;
36 import jalview.util.Format;
37 import jalview.xml.binding.sifts.Entry;
38 import jalview.xml.binding.sifts.Entry.Entity;
39 import jalview.xml.binding.sifts.Entry.Entity.Segment;
40 import jalview.xml.binding.sifts.Entry.Entity.Segment.ListMapRegion.MapRegion;
41 import jalview.xml.binding.sifts.Entry.Entity.Segment.ListResidue.Residue;
42 import jalview.xml.binding.sifts.Entry.Entity.Segment.ListResidue.Residue.CrossRefDb;
43 import jalview.xml.binding.sifts.Entry.Entity.Segment.ListResidue.Residue.ResidueDetail;
44
45 import java.io.File;
46 import java.io.FileInputStream;
47 import java.io.FileOutputStream;
48 import java.io.IOException;
49 import java.io.InputStream;
50 import java.io.PrintStream;
51 import java.net.URL;
52 import java.net.URLConnection;
53 import java.nio.file.Files;
54 import java.nio.file.Path;
55 import java.nio.file.attribute.BasicFileAttributes;
56 import java.util.ArrayList;
57 import java.util.Arrays;
58 import java.util.Collections;
59 import java.util.Date;
60 import java.util.HashMap;
61 import java.util.HashSet;
62 import java.util.List;
63 import java.util.Map;
64 import java.util.Set;
65 import java.util.TreeMap;
66 import java.util.zip.GZIPInputStream;
67
68 import javax.xml.bind.JAXBContext;
69 import javax.xml.bind.Unmarshaller;
70 import javax.xml.stream.XMLInputFactory;
71 import javax.xml.stream.XMLStreamReader;
72
73 public class SiftsClient extends StructureMappingClient implements
74         SiftsClientI
75 {
76   /*
77    * for use in mocking out file fetch for tests only
78    * - reset to null after testing!
79    */
80   private static File mockSiftsFile;
81
82   private Entry siftsEntry;
83
84   private String pdbId;
85
86   private String structId;
87
88   private CoordinateSys seqCoordSys = CoordinateSys.UNIPROT;
89
90   private static final int BUFFER_SIZE = 4096;
91
92   public static final int UNASSIGNED = -1;
93
94   private static final int PDB_RES_POS = 0;
95
96   private static final String NOT_OBSERVED = "Not_Observed";
97
98   private static final String SIFTS_FTP_BASE_URL = "http://ftp.ebi.ac.uk/pub/databases/msd/sifts/xml/";
99
100   private final static String NEWLINE = System.lineSeparator();
101
102   private String curSourceDBRef;
103
104   private HashSet<String> curDBRefAccessionIdsString;
105
106   private enum CoordinateSys
107   {
108     UNIPROT("UniProt"), PDB("PDBresnum"), PDBe("PDBe");
109     private String name;
110
111     private CoordinateSys(String name)
112     {
113       this.name = name;
114     }
115
116     public String getName()
117     {
118       return name;
119     }
120   };
121
122   private enum ResidueDetailType
123   {
124     NAME_SEC_STRUCTURE("nameSecondaryStructure"), CODE_SEC_STRUCTURE(
125             "codeSecondaryStructure"), ANNOTATION("Annotation");
126     private String code;
127
128     private ResidueDetailType(String code)
129     {
130       this.code = code;
131     }
132
133     public String getCode()
134     {
135       return code;
136     }
137   };
138
139   /**
140    * Fetch SIFTs file for the given PDBfile and construct an instance of
141    * SiftsClient
142    * 
143    * @param pdbId
144    * @throws SiftsException
145    */
146   public SiftsClient(StructureFile structureFile) throws SiftsException
147   {
148     this.structureFile = structureFile;
149     this.pdbId = structureFile.getId();
150     File siftsFile = getSiftsFile(pdbId);
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 (InputStream in = new FileInputStream(siftFile);
166             GZIPInputStream gzis = new GZIPInputStream(in);)
167     {
168       // System.out.println("File : " + siftFile.getAbsolutePath());
169       JAXBContext jc = JAXBContext.newInstance("jalview.xml.binding.sifts");
170       XMLStreamReader streamReader = XMLInputFactory.newInstance()
171               .createXMLStreamReader(gzis);
172       Unmarshaller um = jc.createUnmarshaller();
173       return (Entry) um.unmarshal(streamReader);
174     } catch (Exception e)
175     {
176       e.printStackTrace();
177       throw new SiftsException(e.getMessage());
178     }
179   }
180
181   /**
182    * Get a SIFTs XML file for a given PDB Id from Cache or download from FTP
183    * repository if not found in cache
184    * 
185    * @param pdbId
186    * @return SIFTs XML file
187    * @throws SiftsException
188    */
189   public static File getSiftsFile(String pdbId) throws SiftsException
190   {
191     /*
192      * return mocked file if it has been set
193      */
194     if (mockSiftsFile != null)
195     {
196       return mockSiftsFile;
197     }
198
199     String siftsFileName = SiftsSettings.getSiftDownloadDirectory()
200             + pdbId.toLowerCase() + ".xml.gz";
201     File siftsFile = new File(siftsFileName);
202     if (siftsFile.exists())
203     {
204       // The line below is required for unit testing... don't comment it out!!!
205       System.out.println(">>> SIFTS File already downloaded for " + pdbId);
206
207       if (isFileOlderThanThreshold(siftsFile,
208               SiftsSettings.getCacheThresholdInDays()))
209       {
210         File oldSiftsFile = new File(siftsFileName + "_old");
211         siftsFile.renameTo(oldSiftsFile);
212         try
213         {
214           siftsFile = downloadSiftsFile(pdbId.toLowerCase());
215           oldSiftsFile.delete();
216           return siftsFile;
217         } catch (IOException e)
218         {
219           e.printStackTrace();
220           oldSiftsFile.renameTo(siftsFile);
221           return new File(siftsFileName);
222         }
223       }
224       else
225       {
226         return siftsFile;
227       }
228     }
229     try
230     {
231       siftsFile = downloadSiftsFile(pdbId.toLowerCase());
232     } catch (IOException e)
233     {
234       throw new SiftsException(e.getMessage());
235     }
236     return siftsFile;
237   }
238
239   /**
240    * This method enables checking if a cached file has exceeded a certain
241    * threshold(in days)
242    * 
243    * @param file
244    *          the cached file
245    * @param noOfDays
246    *          the threshold in days
247    * @return
248    */
249   public static boolean isFileOlderThanThreshold(File file, int noOfDays)
250   {
251     Path filePath = file.toPath();
252     BasicFileAttributes attr;
253     int diffInDays = 0;
254     try
255     {
256       attr = Files.readAttributes(filePath, BasicFileAttributes.class);
257       diffInDays = (int) ((new Date().getTime() - attr.lastModifiedTime()
258               .toMillis()) / (1000 * 60 * 60 * 24));
259       // System.out.println("Diff in days : " + diffInDays);
260     } catch (IOException e)
261     {
262       e.printStackTrace();
263     }
264     return noOfDays <= diffInDays;
265   }
266
267   /**
268    * Download a SIFTs XML file for a given PDB Id from an FTP repository
269    * 
270    * @param pdbId
271    * @return downloaded SIFTs XML file
272    * @throws SiftsException
273    * @throws IOException
274    */
275   public static File downloadSiftsFile(String pdbId) throws SiftsException,
276           IOException
277   {
278     if (pdbId.contains(".cif"))
279     {
280       pdbId = pdbId.replace(".cif", "");
281     }
282     String siftFile = pdbId + ".xml.gz";
283     String siftsFileFTPURL = SIFTS_FTP_BASE_URL + siftFile;
284     String downloadedSiftsFile = SiftsSettings.getSiftDownloadDirectory()
285             + siftFile;
286     File siftsDownloadDir = new File(
287             SiftsSettings.getSiftDownloadDirectory());
288     if (!siftsDownloadDir.exists())
289     {
290       siftsDownloadDir.mkdirs();
291     }
292     // System.out.println(">> Download ftp url : " + siftsFileFTPURL);
293     // long now = System.currentTimeMillis();
294     URL url = new URL(siftsFileFTPURL);
295     URLConnection conn = url.openConnection();
296     InputStream inputStream = conn.getInputStream();
297     FileOutputStream outputStream = new FileOutputStream(
298             downloadedSiftsFile);
299     byte[] buffer = new byte[BUFFER_SIZE];
300     int bytesRead = -1;
301     while ((bytesRead = inputStream.read(buffer)) != -1)
302     {
303       outputStream.write(buffer, 0, bytesRead);
304     }
305     outputStream.close();
306     inputStream.close();
307 //    System.out.println(">>> File downloaded : " + downloadedSiftsFile
308 //            + " took " + (System.currentTimeMillis() - now) + "ms");
309     return new File(downloadedSiftsFile);
310   }
311
312   /**
313    * Delete the SIFTs file for the given PDB Id in the local SIFTs download
314    * directory
315    * 
316    * @param pdbId
317    * @return true if the file was deleted or doesn't exist
318    */
319   public static boolean deleteSiftsFileByPDBId(String pdbId)
320   {
321     File siftsFile = new File(SiftsSettings.getSiftDownloadDirectory()
322             + pdbId.toLowerCase() + ".xml.gz");
323     if (siftsFile.exists())
324     {
325       return siftsFile.delete();
326     }
327     return true;
328   }
329
330   /**
331    * Get a valid SIFTs DBRef for the given sequence current SIFTs entry
332    * 
333    * @param seq
334    *          - the target sequence for the operation
335    * @return a valid DBRefEntry that is SIFTs compatible
336    * @throws Exception
337    *           if no valid source DBRefEntry was found for the given sequences
338    */
339   public DBRefEntryI getValidSourceDBRef(SequenceI seq)
340           throws SiftsException
341   {
342     List<DBRefEntry> dbRefs = seq.getPrimaryDBRefs();
343     if (dbRefs == null || dbRefs.size() < 1)
344     {
345       throw new SiftsException(
346               "Source DBRef could not be determined. DBRefs might not have been retrieved.");
347     }
348
349     for (DBRefEntry dbRef : dbRefs)
350     {
351       if (dbRef == null || dbRef.getAccessionId() == null
352               || dbRef.getSource() == null)
353       {
354         continue;
355       }
356       String canonicalSource = DBRefUtils.getCanonicalName(dbRef
357               .getSource());
358       if (isValidDBRefEntry(dbRef)
359               && (canonicalSource.equalsIgnoreCase(DBRefSource.UNIPROT) || canonicalSource
360                       .equalsIgnoreCase(DBRefSource.PDB)))
361       {
362         return dbRef;
363       }
364     }
365     throw new SiftsException("Could not get source DB Ref");
366   }
367
368   /**
369    * Check that the DBRef Entry is properly populated and is available in this
370    * SiftClient instance
371    * 
372    * @param entry
373    *          - DBRefEntry to validate
374    * @return true validation is successful otherwise false is returned.
375    */
376   boolean isValidDBRefEntry(DBRefEntryI entry)
377   {
378     return entry != null && entry.getAccessionId() != null
379             && isFoundInSiftsEntry(entry.getAccessionId());
380   }
381
382   @Override
383   public HashSet<String> getAllMappingAccession()
384   {
385     HashSet<String> accessions = new HashSet<String>();
386     List<Entity> entities = siftsEntry.getEntity();
387     for (Entity entity : entities)
388     {
389       List<Segment> segments = entity.getSegment();
390       for (Segment segment : segments)
391       {
392         List<MapRegion> mapRegions = segment.getListMapRegion()
393                 .getMapRegion();
394         for (MapRegion mapRegion : mapRegions)
395         {
396           accessions
397                   .add(mapRegion.getDb().getDbAccessionId().toLowerCase());
398         }
399       }
400     }
401     return accessions;
402   }
403
404   @Override
405   public StructureMapping getStructureMapping(SequenceI seq,
406           String pdbFile, String chain) throws Exception,
407           StructureMappingException
408   {
409     structId = (chain == null) ? pdbId : pdbId + "|" + chain;
410     System.out.println("Getting SIFTS mapping for " + structId + ": seq "
411             + seq.getName());
412
413     final StringBuilder mappingDetails = new StringBuilder(128);
414     PrintStream ps = new PrintStream(System.out)
415     {
416       @Override
417       public void print(String x)
418       {
419         mappingDetails.append(x);
420       }
421
422       @Override
423       public void println()
424       {
425         mappingDetails.append(NEWLINE);
426       }
427     };
428     HashMap<Integer, int[]> mapping = getGreedyMapping(chain, seq, ps);
429
430     String mappingOutput = mappingDetails.toString();
431     StructureMapping siftsMapping = new StructureMapping(seq, pdbFile,
432             pdbId, chain, mapping, mappingOutput);
433     return siftsMapping;
434   }
435
436   @Override
437   public HashMap<Integer, int[]> getGreedyMapping(String entityId,
438           SequenceI seq, java.io.PrintStream os) throws SiftsException,
439           StructureMappingException
440   {
441     List<Integer> omitNonObserved = new ArrayList<Integer>();
442     int nonObservedShiftIndex = 0;
443     // System.out.println("Generating mappings for : " + entityId);
444     Entity entity = null;
445     entity = getEntityById(entityId);
446     String originalSeq = AlignSeq.extractGaps(
447             jalview.util.Comparison.GapChars, seq.getSequenceAsString());
448     HashMap<Integer, int[]> mapping = new HashMap<Integer, int[]>();
449     DBRefEntryI sourceDBRef;
450     sourceDBRef = getValidSourceDBRef(seq);
451     // TODO ensure sequence start/end is in the same coordinate system and
452     // consistent with the choosen sourceDBRef
453
454     // set sequence coordinate system - default value is UniProt
455     if (sourceDBRef.getSource().equalsIgnoreCase(DBRefSource.PDB))
456     {
457       seqCoordSys = CoordinateSys.PDB;
458     }
459
460     HashSet<String> dbRefAccessionIdsString = new HashSet<String>();
461     for (DBRefEntry dbref : seq.getDBRefs())
462     {
463       dbRefAccessionIdsString.add(dbref.getAccessionId().toLowerCase());
464     }
465     dbRefAccessionIdsString.add(sourceDBRef.getAccessionId().toLowerCase());
466
467     curDBRefAccessionIdsString = dbRefAccessionIdsString;
468     curSourceDBRef = sourceDBRef.getAccessionId();
469
470     TreeMap<Integer, String> resNumMap = new TreeMap<Integer, String>();
471     List<Segment> segments = entity.getSegment();
472     SegmentHelperPojo shp = new SegmentHelperPojo(seq, mapping, resNumMap,
473             omitNonObserved, nonObservedShiftIndex);
474     processSegments(segments, shp);
475     try
476     {
477       populateAtomPositions(entityId, mapping);
478     } catch (Exception e)
479     {
480       e.printStackTrace();
481     }
482     if (seqCoordSys == CoordinateSys.UNIPROT)
483     {
484       padWithGaps(resNumMap, omitNonObserved);
485     }
486     int seqStart = UNASSIGNED;
487     int seqEnd = UNASSIGNED;
488     int pdbStart = UNASSIGNED;
489     int pdbEnd = UNASSIGNED;
490
491     if (mapping.isEmpty())
492     {
493       throw new SiftsException("SIFTS mapping failed");
494     }
495
496     Integer[] keys = mapping.keySet().toArray(new Integer[0]);
497     Arrays.sort(keys);
498     seqStart = keys[0];
499     seqEnd = keys[keys.length - 1];
500
501     String matchedSeq = originalSeq;
502     if (seqStart != UNASSIGNED)
503     {
504       pdbStart = mapping.get(seqStart)[PDB_RES_POS];
505       pdbEnd = mapping.get(seqEnd)[PDB_RES_POS];
506       int orignalSeqStart = seq.getStart();
507       if (orignalSeqStart >= 1)
508       {
509         int subSeqStart = (seqStart >= orignalSeqStart) ? seqStart
510                 - orignalSeqStart : 0;
511         int subSeqEnd = seqEnd - (orignalSeqStart - 1);
512         subSeqEnd = originalSeq.length() < subSeqEnd ? originalSeq.length()
513                 : subSeqEnd;
514         matchedSeq = originalSeq.substring(subSeqStart, subSeqEnd);
515       }
516       else
517       {
518         matchedSeq = originalSeq.substring(1, originalSeq.length());
519       }
520     }
521
522     StringBuilder targetStrucSeqs = new StringBuilder();
523     for (String res : resNumMap.values())
524     {
525       targetStrucSeqs.append(res);
526     }
527
528     if (os != null)
529     {
530       MappingOutputModel mop = new MappingOutputModel();
531       mop.setSeqStart(seqStart);
532       mop.setSeqEnd(seqEnd);
533       mop.setSeqName(seq.getName());
534       mop.setSeqResidue(matchedSeq);
535
536       mop.setStrStart(pdbStart);
537       mop.setStrEnd(pdbEnd);
538       mop.setStrName(structId);
539       mop.setStrResidue(targetStrucSeqs.toString());
540
541       mop.setType("pep");
542       os.print(getMappingOutput(mop).toString());
543       os.println();
544     }
545     return mapping;
546   }
547
548   void processSegments(List<Segment> segments, SegmentHelperPojo shp)
549   {
550     SequenceI seq = shp.getSeq();
551     HashMap<Integer, int[]> mapping = shp.getMapping();
552     TreeMap<Integer, String> resNumMap = shp.getResNumMap();
553     List<Integer> omitNonObserved = shp.getOmitNonObserved();
554     int nonObservedShiftIndex = shp.getNonObservedShiftIndex();
555     for (Segment segment : segments)
556     {
557       // System.out.println("Mapping segments : " + segment.getSegId() + "\\"s
558       // + segStartEnd);
559       List<Residue> residues = segment.getListResidue().getResidue();
560       for (Residue residue : residues)
561       {
562         int currSeqIndex = UNASSIGNED;
563         List<CrossRefDb> cRefDbs = residue.getCrossRefDb();
564         CrossRefDb pdbRefDb = null;
565         for (CrossRefDb cRefDb : cRefDbs)
566         {
567           if (cRefDb.getDbSource().equalsIgnoreCase(DBRefSource.PDB))
568           {
569             pdbRefDb = cRefDb;
570           }
571           if (cRefDb.getDbCoordSys()
572                   .equalsIgnoreCase(seqCoordSys.getName())
573                   && isAccessionMatched(cRefDb.getDbAccessionId()))
574           {
575             String resNumIndexString = cRefDb.getDbResNum()
576                     .equalsIgnoreCase("None") ? String.valueOf(UNASSIGNED)
577                     : cRefDb.getDbResNum();
578             try
579             {
580               currSeqIndex = Integer.valueOf(resNumIndexString);
581             } catch (NumberFormatException nfe)
582             {
583               currSeqIndex = Integer.valueOf(resNumIndexString
584                       .split("[a-zA-Z]")[0]);
585               continue;
586             }
587             if (pdbRefDb != null)
588             {
589               break;// exit loop if pdb and uniprot are already found
590             }
591           }
592         }
593         if (currSeqIndex == UNASSIGNED)
594         {
595           continue;
596         }
597         if (currSeqIndex >= seq.getStart() && currSeqIndex <= seq.getEnd())
598         {
599           int resNum;
600           try
601           {
602             resNum = (pdbRefDb == null) ? Integer.valueOf(residue
603                     .getDbResNum()) : Integer.valueOf(pdbRefDb
604                     .getDbResNum());
605           } catch (NumberFormatException nfe)
606           {
607             resNum = (pdbRefDb == null) ? Integer.valueOf(residue
608                     .getDbResNum()) : Integer.valueOf(pdbRefDb
609                     .getDbResNum().split("[a-zA-Z]")[0]);
610             continue;
611           }
612
613           if (isResidueObserved(residue)
614                   || seqCoordSys == CoordinateSys.UNIPROT)
615           {
616             char resCharCode = ResidueProperties
617                     .getSingleCharacterCode(ResidueProperties
618                             .getCanonicalAminoAcid(residue.getDbResName()));
619             resNumMap.put(currSeqIndex, String.valueOf(resCharCode));
620           }
621           else
622           {
623             omitNonObserved.add(currSeqIndex);
624             ++nonObservedShiftIndex;
625           }
626           mapping.put(currSeqIndex - nonObservedShiftIndex, new int[] {
627               Integer.valueOf(resNum), UNASSIGNED });
628         }
629       }
630     }
631   }
632
633
634   /**
635    * Checks if the residue instance is marked 'Not_observed' or not
636    * 
637    * @param residue
638    * @return
639    */
640   private boolean isResidueObserved(Residue residue)
641   {
642     Set<String> annotations = getResidueAnnotaitons(residue,
643             ResidueDetailType.ANNOTATION);
644     if (annotations == null || annotations.isEmpty())
645     {
646       return true;
647     }
648     for (String annotation : annotations)
649     {
650       if (annotation.equalsIgnoreCase(NOT_OBSERVED))
651       {
652         return false;
653       }
654     }
655     return true;
656   }
657
658   /**
659    * Get annotation String for a given residue and annotation type
660    * 
661    * @param residue
662    * @param type
663    * @return
664    */
665   private Set<String> getResidueAnnotaitons(Residue residue,
666           ResidueDetailType type)
667   {
668     HashSet<String> foundAnnotations = new HashSet<String>();
669     List<ResidueDetail> resDetails = residue.getResidueDetail();
670     for (ResidueDetail resDetail : resDetails)
671     {
672       if (resDetail.getProperty().equalsIgnoreCase(type.getCode()))
673       {
674         foundAnnotations.add(resDetail.getContent());
675       }
676     }
677     return foundAnnotations;
678   }
679
680   @Override
681   public boolean isAccessionMatched(String accession)
682   {
683     boolean isStrictMatch = true;
684     return isStrictMatch ? curSourceDBRef.equalsIgnoreCase(accession)
685             : curDBRefAccessionIdsString.contains(accession.toLowerCase());
686   }
687
688   private boolean isFoundInSiftsEntry(String accessionId)
689   {
690     Set<String> siftsDBRefs = getAllMappingAccession();
691     return accessionId != null
692             && siftsDBRefs.contains(accessionId.toLowerCase());
693   }
694
695   /**
696    * Pad omitted residue positions in PDB sequence with gaps
697    * 
698    * @param resNumMap
699    */
700   void padWithGaps(Map<Integer, String> resNumMap,
701           List<Integer> omitNonObserved)
702   {
703     if (resNumMap == null || resNumMap.isEmpty())
704     {
705       return;
706     }
707     Integer[] keys = resNumMap.keySet().toArray(new Integer[0]);
708     // Arrays.sort(keys);
709     int firstIndex = keys[0];
710     int lastIndex = keys[keys.length - 1];
711     // System.out.println("Min value " + firstIndex);
712     // System.out.println("Max value " + lastIndex);
713     for (int x = firstIndex; x <= lastIndex; x++)
714     {
715       if (!resNumMap.containsKey(x) && !omitNonObserved.contains(x))
716       {
717         resNumMap.put(x, "-");
718       }
719     }
720   }
721
722   @Override
723   public Entity getEntityById(String id) throws SiftsException
724   {
725     // Determines an entity to process by performing a heuristic matching of all
726     // Entities with the given chainId and choosing the best matching Entity
727     Entity entity = getEntityByMostOptimalMatchedId(id);
728     if (entity != null)
729     {
730       return entity;
731     }
732     throw new SiftsException("Entity " + id + " not found");
733   }
734
735   /**
736    * This method was added because EntityId is NOT always equal to ChainId.
737    * Hence, it provides the logic to greedily detect the "true" Entity for a
738    * given chainId where discrepancies exist.
739    * 
740    * @param chainId
741    * @return
742    */
743   public Entity getEntityByMostOptimalMatchedId(String chainId)
744   {
745     // System.out.println("---> advanced greedy entityId matching block entered..");
746     List<Entity> entities = siftsEntry.getEntity();
747     SiftsEntitySortPojo[] sPojo = new SiftsEntitySortPojo[entities.size()];
748     int count = 0;
749     for (Entity entity : entities)
750     {
751       sPojo[count] = new SiftsEntitySortPojo();
752       sPojo[count].entityId = entity.getEntityId();
753
754       List<Segment> segments = entity.getSegment();
755       for (Segment segment : segments)
756       {
757         List<Residue> residues = segment.getListResidue().getResidue();
758         for (Residue residue : residues)
759         {
760           List<CrossRefDb> cRefDbs = residue.getCrossRefDb();
761           for (CrossRefDb cRefDb : cRefDbs)
762           {
763             if (!cRefDb.getDbSource().equalsIgnoreCase("PDB"))
764             {
765               continue;
766             }
767             ++sPojo[count].resCount;
768             if (cRefDb.getDbChainId().equalsIgnoreCase(chainId))
769             {
770               ++sPojo[count].chainIdFreq;
771             }
772           }
773         }
774       }
775       sPojo[count].pid = (100 * sPojo[count].chainIdFreq)
776               / sPojo[count].resCount;
777       ++count;
778     }
779     Arrays.sort(sPojo, Collections.reverseOrder());
780     // System.out.println("highest matched entity : " + sPojo[0].entityId);
781     // System.out.println("highest matched pid : " + sPojo[0].pid);
782
783     if (sPojo[0].entityId != null)
784     {
785       if (sPojo[0].pid < 1)
786       {
787         return null;
788       }
789       for (Entity entity : entities)
790       {
791         if (!entity.getEntityId().equalsIgnoreCase(sPojo[0].entityId))
792         {
793           continue;
794         }
795         return entity;
796       }
797     }
798     return null;
799   }
800
801   private class SiftsEntitySortPojo implements
802           Comparable<SiftsEntitySortPojo>
803   {
804     public String entityId;
805
806     public int chainIdFreq;
807
808     public int pid;
809
810     public int resCount;
811
812     @Override
813     public int compareTo(SiftsEntitySortPojo o)
814     {
815       return this.pid - o.pid;
816     }
817   }
818
819   private class SegmentHelperPojo
820   {
821     private SequenceI seq;
822
823     private HashMap<Integer, int[]> mapping;
824
825     private TreeMap<Integer, String> resNumMap;
826
827     private List<Integer> omitNonObserved;
828
829     private int nonObservedShiftIndex;
830
831     public SegmentHelperPojo(SequenceI seq,
832             HashMap<Integer, int[]> mapping,
833             TreeMap<Integer, String> resNumMap,
834             List<Integer> omitNonObserved, int nonObservedShiftIndex)
835     {
836       setSeq(seq);
837       setMapping(mapping);
838       setResNumMap(resNumMap);
839       setOmitNonObserved(omitNonObserved);
840       setNonObservedShiftIndex(nonObservedShiftIndex);
841     }
842
843     public SequenceI getSeq()
844     {
845       return seq;
846     }
847
848     public void setSeq(SequenceI seq)
849     {
850       this.seq = seq;
851     }
852
853     public HashMap<Integer, int[]> getMapping()
854     {
855       return mapping;
856     }
857
858     public void setMapping(HashMap<Integer, int[]> mapping)
859     {
860       this.mapping = mapping;
861     }
862
863     public TreeMap<Integer, String> getResNumMap()
864     {
865       return resNumMap;
866     }
867
868     public void setResNumMap(TreeMap<Integer, String> resNumMap)
869     {
870       this.resNumMap = resNumMap;
871     }
872
873     public List<Integer> getOmitNonObserved()
874     {
875       return omitNonObserved;
876     }
877
878     public void setOmitNonObserved(List<Integer> omitNonObserved)
879     {
880       this.omitNonObserved = omitNonObserved;
881     }
882
883     public int getNonObservedShiftIndex()
884     {
885       return nonObservedShiftIndex;
886     }
887
888     public void setNonObservedShiftIndex(int nonObservedShiftIndex)
889     {
890       this.nonObservedShiftIndex = nonObservedShiftIndex;
891     }
892   }
893
894   @Override
895   public StringBuffer getMappingOutput(MappingOutputModel mp)
896           throws StructureMappingException
897   {
898     String seqRes = mp.getSeqResidue();
899     String seqName = mp.getSeqName();
900     int sStart = mp.getSeqStart();
901     int sEnd = mp.getSeqEnd();
902
903     String strRes = mp.getStrResidue();
904     String strName = mp.getStrName();
905     int pdbStart = mp.getStrStart();
906     int pdbEnd = mp.getStrEnd();
907
908     String type = mp.getType();
909
910     int maxid = (seqName.length() >= strName.length()) ? seqName.length()
911             : strName.length();
912     int len = 72 - maxid - 1;
913
914     int nochunks = ((seqRes.length()) / len)
915             + ((seqRes.length()) % len > 0 ? 1 : 0);
916     // output mappings
917     StringBuffer output = new StringBuffer();
918     output.append(NEWLINE);
919     output.append("Sequence \u27f7 Structure mapping details").append(
920             NEWLINE);
921     output.append("Method: SIFTS");
922     output.append(NEWLINE).append(NEWLINE);
923
924     output.append(new Format("%" + maxid + "s").form(seqName));
925     output.append(" :  ");
926     output.append(String.valueOf(sStart));
927     output.append(" - ");
928     output.append(String.valueOf(sEnd));
929     output.append(" Maps to ");
930     output.append(NEWLINE);
931     output.append(new Format("%" + maxid + "s").form(structId));
932     output.append(" :  ");
933     output.append(String.valueOf(pdbStart));
934     output.append(" - ");
935     output.append(String.valueOf(pdbEnd));
936     output.append(NEWLINE).append(NEWLINE);
937
938     int matchedSeqCount = 0;
939     for (int j = 0; j < nochunks; j++)
940     {
941       // Print the first aligned sequence
942       output.append(new Format("%" + (maxid) + "s").form(seqName)).append(
943               " ");
944
945       for (int i = 0; i < len; i++)
946       {
947         if ((i + (j * len)) < seqRes.length())
948         {
949           output.append(seqRes.charAt(i + (j * len)));
950         }
951       }
952
953       output.append(NEWLINE);
954       output.append(new Format("%" + (maxid) + "s").form(" ")).append(" ");
955
956       // Print out the matching chars
957       for (int i = 0; i < len; i++)
958       {
959         try
960         {
961           if ((i + (j * len)) < seqRes.length())
962           {
963             boolean sameChar = Comparison.isSameResidue(
964                     seqRes.charAt(i + (j * len)),
965                     strRes.charAt(i + (j * len)), false);
966             if (sameChar
967                     && !jalview.util.Comparison.isGap(seqRes.charAt(i
968                             + (j * len))))
969             {
970               matchedSeqCount++;
971               output.append("|");
972             }
973             else if (type.equals("pep"))
974             {
975               if (ResidueProperties.getPAM250(seqRes.charAt(i + (j * len)),
976                       strRes.charAt(i + (j * len))) > 0)
977               {
978                 output.append(".");
979               }
980               else
981               {
982                 output.append(" ");
983               }
984             }
985             else
986             {
987               output.append(" ");
988             }
989           }
990         } catch (IndexOutOfBoundsException e)
991         {
992           continue;
993         }
994       }
995       // Now print the second aligned sequence
996       output = output.append(NEWLINE);
997       output = output.append(new Format("%" + (maxid) + "s").form(strName))
998               .append(" ");
999       for (int i = 0; i < len; i++)
1000       {
1001         if ((i + (j * len)) < strRes.length())
1002         {
1003           output.append(strRes.charAt(i + (j * len)));
1004         }
1005       }
1006       output.append(NEWLINE).append(NEWLINE);
1007     }
1008     float pid = (float) matchedSeqCount / seqRes.length() * 100;
1009     if (pid < SiftsSettings.getFailSafePIDThreshold())
1010     {
1011       throw new StructureMappingException(
1012               ">>> Low PID detected for SIFTs mapping...");
1013     }
1014     output.append("Length of alignment = " + seqRes.length()).append(
1015             NEWLINE);
1016     output.append(new Format("Percentage ID = %2.2f").form(pid));
1017     return output;
1018   }
1019
1020   @Override
1021   public int getEntityCount()
1022   {
1023     return siftsEntry.getEntity().size();
1024   }
1025
1026   @Override
1027   public String getDbAccessionId()
1028   {
1029     return siftsEntry.getDbAccessionId();
1030   }
1031
1032   @Override
1033   public String getDbCoordSys()
1034   {
1035     return siftsEntry.getDbCoordSys();
1036   }
1037
1038   @Override
1039   public String getDbSource()
1040   {
1041     return siftsEntry.getDbSource();
1042   }
1043
1044   @Override
1045   public String getDbVersion()
1046   {
1047     return siftsEntry.getDbVersion();
1048   }
1049
1050   public static void setMockSiftsFile(File file)
1051   {
1052     mockSiftsFile = file;
1053   }
1054
1055 }