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