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