JAL-966 SearchResultsI now on viewmodels.AlignmentViewport rather than on SeqCanvas
[jalview.git] / src / jalview / structures / models / AAStructureBindingModel.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.structures.models;
22
23 import jalview.api.StructureSelectionManagerProvider;
24 import jalview.datamodel.AlignmentI;
25 import jalview.datamodel.PDBEntry;
26 import jalview.datamodel.SequenceI;
27 import jalview.structure.AtomSpec;
28 import jalview.structure.StructureListener;
29 import jalview.structure.StructureMapping;
30 import jalview.structure.StructureSelectionManager;
31 import jalview.util.Comparison;
32 import jalview.util.MessageManager;
33
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.List;
37
38 /**
39  * 
40  * A base class to hold common function for protein structure model binding.
41  * Initial version created by refactoring JMol and Chimera binding models, but
42  * other structure viewers could in principle be accommodated in future.
43  * 
44  * @author gmcarstairs
45  *
46  */
47 public abstract class AAStructureBindingModel extends
48         SequenceStructureBindingModel implements StructureListener,
49         StructureSelectionManagerProvider
50 {
51
52   private StructureSelectionManager ssm;
53
54   /*
55    * distinct PDB entries (pdb files) associated
56    * with sequences
57    */
58   private PDBEntry[] pdbEntry;
59
60   /*
61    * sequences mapped to each pdbentry
62    */
63   private SequenceI[][] sequence;
64
65   /*
66    * array of target chains for sequences - tied to pdbentry and sequence[]
67    */
68   private String[][] chains;
69
70   /*
71    * datasource protocol for access to PDBEntrylatest
72    */
73   String protocol = null;
74
75   protected boolean colourBySequence = true;
76
77   private boolean nucleotide;
78
79   private boolean finishedInit = false;
80
81   /**
82    * current set of model filenames loaded in the Jmol instance
83    */
84   protected String[] modelFileNames = null;
85
86   /**
87    * Data bean class to simplify parameterisation in superposeStructures
88    */
89   protected class SuperposeData
90   {
91     /**
92      * Constructor with alignment width argument
93      * 
94      * @param width
95      */
96     public SuperposeData(int width)
97     {
98       pdbResNo = new int[width];
99     }
100
101     public String filename;
102
103     public String pdbId;
104
105     public String chain = "";
106
107     public boolean isRna;
108
109     /*
110      * The pdb residue number (if any) mapped to each column of the alignment
111      */
112     public int[] pdbResNo;
113   }
114
115   /**
116    * Constructor
117    * 
118    * @param ssm
119    * @param seqs
120    */
121   public AAStructureBindingModel(StructureSelectionManager ssm,
122           SequenceI[][] seqs)
123   {
124     this.ssm = ssm;
125     this.sequence = seqs;
126   }
127
128   /**
129    * Constructor
130    * 
131    * @param ssm
132    * @param pdbentry
133    * @param sequenceIs
134    * @param chains
135    * @param protocol
136    */
137   public AAStructureBindingModel(StructureSelectionManager ssm,
138           PDBEntry[] pdbentry, SequenceI[][] sequenceIs, String[][] chains,
139           String protocol)
140   {
141     this.ssm = ssm;
142     this.sequence = sequenceIs;
143     this.nucleotide = Comparison.isNucleotide(sequenceIs);
144     this.chains = chains;
145     this.pdbEntry = pdbentry;
146     this.protocol = protocol;
147     if (chains == null)
148     {
149       this.chains = new String[pdbentry.length][];
150     }
151   }
152
153   public StructureSelectionManager getSsm()
154   {
155     return ssm;
156   }
157
158   /**
159    * Returns the i'th PDBEntry (or null)
160    * 
161    * @param i
162    * @return
163    */
164   public PDBEntry getPdbEntry(int i)
165   {
166     return (pdbEntry != null && pdbEntry.length > i) ? pdbEntry[i] : null;
167   }
168
169   /**
170    * Answers true if this binding includes the given PDB id, else false
171    * 
172    * @param pdbId
173    * @return
174    */
175   public boolean hasPdbId(String pdbId)
176   {
177     if (pdbEntry != null)
178     {
179       for (PDBEntry pdb : pdbEntry)
180       {
181         if (pdb.getId().equals(pdbId))
182         {
183           return true;
184         }
185       }
186     }
187     return false;
188   }
189
190   /**
191    * Returns the number of modelled PDB file entries.
192    * 
193    * @return
194    */
195   public int getPdbCount()
196   {
197     return pdbEntry == null ? 0 : pdbEntry.length;
198   }
199
200   public SequenceI[][] getSequence()
201   {
202     return sequence;
203   }
204
205   public String[][] getChains()
206   {
207     return chains;
208   }
209
210   public String getProtocol()
211   {
212     return protocol;
213   }
214
215   // TODO may remove this if calling methods can be pulled up here
216   protected void setPdbentry(PDBEntry[] pdbentry)
217   {
218     this.pdbEntry = pdbentry;
219   }
220
221   protected void setSequence(SequenceI[][] sequence)
222   {
223     this.sequence = sequence;
224   }
225
226   protected void setChains(String[][] chains)
227   {
228     this.chains = chains;
229   }
230
231   /**
232    * Construct a title string for the viewer window based on the data Jalview
233    * knows about
234    * 
235    * @param viewerName
236    *          TODO
237    * @param verbose
238    * 
239    * @return
240    */
241   public String getViewerTitle(String viewerName, boolean verbose)
242   {
243     if (getSequence() == null || getSequence().length < 1
244             || getPdbCount() < 1 || getSequence()[0].length < 1)
245     {
246       return ("Jalview " + viewerName + " Window");
247     }
248     // TODO: give a more informative title when multiple structures are
249     // displayed.
250     StringBuilder title = new StringBuilder(64);
251     final PDBEntry pdbe = getPdbEntry(0);
252     title.append(viewerName + " view for " + getSequence()[0][0].getName()
253             + ":" + pdbe.getId());
254
255     if (verbose)
256     {
257       String method = (String) pdbe.getProperty("method");
258       if (method != null)
259       {
260         title.append(" Method: ").append(method);
261       }
262       String chain = (String) pdbe.getProperty("chains");
263       if (chain != null)
264       {
265         title.append(" Chain:").append(chain);
266       }
267     }
268     return title.toString();
269   }
270
271   /**
272    * Called by after closeViewer is called, to release any resources and
273    * references so they can be garbage collected. Override if needed.
274    */
275   protected void releaseUIResources()
276   {
277
278   }
279
280   public boolean isColourBySequence()
281   {
282     return colourBySequence;
283   }
284
285   public void setColourBySequence(boolean colourBySequence)
286   {
287     this.colourBySequence = colourBySequence;
288   }
289
290   protected void addSequenceAndChain(int pe, SequenceI[] seq,
291           String[] tchain)
292   {
293     if (pe < 0 || pe >= getPdbCount())
294     {
295       throw new Error(MessageManager.formatMessage(
296               "error.implementation_error_no_pdbentry_from_index",
297               new Object[] { Integer.valueOf(pe).toString() }));
298     }
299     final String nullChain = "TheNullChain";
300     List<SequenceI> s = new ArrayList<SequenceI>();
301     List<String> c = new ArrayList<String>();
302     if (getChains() == null)
303     {
304       setChains(new String[getPdbCount()][]);
305     }
306     if (getSequence()[pe] != null)
307     {
308       for (int i = 0; i < getSequence()[pe].length; i++)
309       {
310         s.add(getSequence()[pe][i]);
311         if (getChains()[pe] != null)
312         {
313           if (i < getChains()[pe].length)
314           {
315             c.add(getChains()[pe][i]);
316           }
317           else
318           {
319             c.add(nullChain);
320           }
321         }
322         else
323         {
324           if (tchain != null && tchain.length > 0)
325           {
326             c.add(nullChain);
327           }
328         }
329       }
330     }
331     for (int i = 0; i < seq.length; i++)
332     {
333       if (!s.contains(seq[i]))
334       {
335         s.add(seq[i]);
336         if (tchain != null && i < tchain.length)
337         {
338           c.add(tchain[i] == null ? nullChain : tchain[i]);
339         }
340       }
341     }
342     SequenceI[] tmp = s.toArray(new SequenceI[s.size()]);
343     getSequence()[pe] = tmp;
344     if (c.size() > 0)
345     {
346       String[] tch = c.toArray(new String[c.size()]);
347       for (int i = 0; i < tch.length; i++)
348       {
349         if (tch[i] == nullChain)
350         {
351           tch[i] = null;
352         }
353       }
354       getChains()[pe] = tch;
355     }
356     else
357     {
358       getChains()[pe] = null;
359     }
360   }
361
362   /**
363    * add structures and any known sequence associations
364    * 
365    * @returns the pdb entries added to the current set.
366    */
367   public synchronized PDBEntry[] addSequenceAndChain(PDBEntry[] pdbe,
368           SequenceI[][] seq, String[][] chns)
369   {
370     List<PDBEntry> v = new ArrayList<PDBEntry>();
371     List<int[]> rtn = new ArrayList<int[]>();
372     for (int i = 0; i < getPdbCount(); i++)
373     {
374       v.add(getPdbEntry(i));
375     }
376     for (int i = 0; i < pdbe.length; i++)
377     {
378       int r = v.indexOf(pdbe[i]);
379       if (r == -1 || r >= getPdbCount())
380       {
381         rtn.add(new int[] { v.size(), i });
382         v.add(pdbe[i]);
383       }
384       else
385       {
386         // just make sure the sequence/chain entries are all up to date
387         addSequenceAndChain(r, seq[i], chns[i]);
388       }
389     }
390     pdbe = v.toArray(new PDBEntry[v.size()]);
391     setPdbentry(pdbe);
392     if (rtn.size() > 0)
393     {
394       // expand the tied sequence[] and string[] arrays
395       SequenceI[][] sqs = new SequenceI[getPdbCount()][];
396       String[][] sch = new String[getPdbCount()][];
397       System.arraycopy(getSequence(), 0, sqs, 0, getSequence().length);
398       System.arraycopy(getChains(), 0, sch, 0, this.getChains().length);
399       setSequence(sqs);
400       setChains(sch);
401       pdbe = new PDBEntry[rtn.size()];
402       for (int r = 0; r < pdbe.length; r++)
403       {
404         int[] stri = (rtn.get(r));
405         // record the pdb file as a new addition
406         pdbe[r] = getPdbEntry(stri[0]);
407         // and add the new sequence/chain entries
408         addSequenceAndChain(stri[0], seq[stri[1]], chns[stri[1]]);
409       }
410     }
411     else
412     {
413       pdbe = null;
414     }
415     return pdbe;
416   }
417
418   /**
419    * Add sequences to the pe'th pdbentry's sequence set.
420    * 
421    * @param pe
422    * @param seq
423    */
424   public void addSequence(int pe, SequenceI[] seq)
425   {
426     addSequenceAndChain(pe, seq, null);
427   }
428
429   /**
430    * add the given sequences to the mapping scope for the given pdb file handle
431    * 
432    * @param pdbFile
433    *          - pdbFile identifier
434    * @param seq
435    *          - set of sequences it can be mapped to
436    */
437   public void addSequenceForStructFile(String pdbFile, SequenceI[] seq)
438   {
439     for (int pe = 0; pe < getPdbCount(); pe++)
440     {
441       if (getPdbEntry(pe).getFile().equals(pdbFile))
442       {
443         addSequence(pe, seq);
444       }
445     }
446   }
447
448   @Override
449   public abstract void highlightAtoms(List<AtomSpec> atoms);
450
451   protected boolean isNucleotide()
452   {
453     return this.nucleotide;
454   }
455
456   /**
457    * Returns a readable description of all mappings for the wrapped pdbfile to
458    * any mapped sequences
459    * 
460    * @param pdbfile
461    * @param seqs
462    * @return
463    */
464   public String printMappings()
465   {
466     if (pdbEntry == null)
467     {
468       return "";
469     }
470     StringBuilder sb = new StringBuilder(128);
471     for (int pdbe = 0; pdbe < getPdbCount(); pdbe++)
472     {
473       String pdbfile = getPdbEntry(pdbe).getFile();
474       List<SequenceI> seqs = Arrays.asList(getSequence()[pdbe]);
475       sb.append(getSsm().printMappings(pdbfile, seqs));
476     }
477     return sb.toString();
478   }
479
480   /**
481    * Returns the mapped structure position for a given aligned column of a given
482    * sequence, or -1 if the column is gapped, beyond the end of the sequence, or
483    * not mapped to structure.
484    * 
485    * @param seq
486    * @param alignedPos
487    * @param mapping
488    * @return
489    */
490   protected int getMappedPosition(SequenceI seq, int alignedPos,
491           StructureMapping mapping)
492   {
493     if (alignedPos >= seq.getLength())
494     {
495       return -1;
496     }
497
498     if (Comparison.isGap(seq.getCharAt(alignedPos)))
499     {
500       return -1;
501     }
502     int seqPos = seq.findPosition(alignedPos);
503     int pos = mapping.getPDBResNum(seqPos);
504     return pos;
505   }
506
507   /**
508    * Helper method to identify residues that can participate in a structure
509    * superposition command. For each structure, identify a sequence in the
510    * alignment which is mapped to the structure. Identify non-gapped columns in
511    * the sequence which have a mapping to a residue in the structure. Returns
512    * the index of the first structure that has a mapping to the alignment.
513    * 
514    * @param alignment
515    *          the sequence alignment which is the basis of structure
516    *          superposition
517    * @param matched
518    *          an array of booleans, indexed by alignment column, where true
519    *          indicates that every structure has a mapped residue present in the
520    *          column (so the column can participate in structure alignment)
521    * @param structures
522    *          an array of data beans corresponding to pdb file index
523    * @return
524    */
525   protected int findSuperposableResidues(AlignmentI alignment,
526           boolean[] matched, SuperposeData[] structures)
527   {
528     int refStructure = -1;
529     String[] files = getPdbFile();
530     if (files == null)
531     {
532       return -1;
533     }
534     for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
535     {
536       StructureMapping[] mappings = getSsm().getMapping(files[pdbfnum]);
537       int lastPos = -1;
538
539       /*
540        * Find the first mapped sequence (if any) for this PDB entry which is in
541        * the alignment
542        */
543       final int seqCountForPdbFile = getSequence()[pdbfnum].length;
544       for (int s = 0; s < seqCountForPdbFile; s++)
545       {
546         for (StructureMapping mapping : mappings)
547         {
548           final SequenceI theSequence = getSequence()[pdbfnum][s];
549           if (mapping.getSequence() == theSequence
550                   && alignment.findIndex(theSequence) > -1)
551           {
552             if (refStructure < 0)
553             {
554               refStructure = pdbfnum;
555             }
556             for (int r = 0; r < matched.length; r++)
557             {
558               if (!matched[r])
559               {
560                 continue;
561               }
562               int pos = getMappedPosition(theSequence, r, mapping);
563               if (pos < 1 || pos == lastPos)
564               {
565                 matched[r] = false;
566                 continue;
567               }
568               lastPos = pos;
569               structures[pdbfnum].pdbResNo[r] = pos;
570             }
571             String chain = mapping.getChain();
572             if (chain != null && chain.trim().length() > 0)
573             {
574               structures[pdbfnum].chain = chain;
575             }
576             structures[pdbfnum].pdbId = mapping.getPdbId();
577             structures[pdbfnum].isRna = theSequence.getRNA() != null;
578
579             /*
580              * move on to next pdb file (ignore sequences for other chains
581              * for the same structure)
582              */
583             s = seqCountForPdbFile;
584             break;
585           }
586         }
587       }
588     }
589     return refStructure;
590   }
591
592   /**
593    * Returns true if the structure viewer has loaded all of the files of
594    * interest (identified by the file mapping having been set up), or false if
595    * any are still not loaded after a timeout interval.
596    * 
597    * @param files
598    */
599   protected boolean waitForFileLoad(String[] files)
600   {
601     /*
602      * give up after 10 secs plus 1 sec per file
603      */
604     long starttime = System.currentTimeMillis();
605     long endTime = 10000 + 1000 * files.length + starttime;
606     String notLoaded = null;
607
608     boolean waiting = true;
609     while (waiting && System.currentTimeMillis() < endTime)
610     {
611       waiting = false;
612       for (String file : files)
613       {
614         notLoaded = file;
615         if (file == null)
616         {
617           continue;
618         }
619         try
620         {
621           StructureMapping[] sm = getSsm().getMapping(file);
622           if (sm == null || sm.length == 0)
623           {
624             waiting = true;
625           }
626         } catch (Throwable x)
627         {
628           waiting = true;
629         }
630       }
631     }
632
633     if (waiting)
634     {
635       System.err
636               .println("Timed out waiting for structure viewer to load file "
637                       + notLoaded);
638       return false;
639     }
640     return true;
641   }
642
643   @Override
644   public boolean isListeningFor(SequenceI seq)
645   {
646     if (sequence != null)
647     {
648       for (SequenceI[] seqs : sequence)
649       {
650         if (seqs != null)
651         {
652           for (SequenceI s : seqs)
653           {
654             if (s == seq
655                     || (s.getDatasetSequence() != null && s
656                             .getDatasetSequence() == seq
657                             .getDatasetSequence()))
658             {
659               return true;
660             }
661           }
662         }
663       }
664     }
665     return false;
666   }
667
668   public boolean isFinishedInit()
669   {
670     return finishedInit;
671   }
672
673   public void setFinishedInit(boolean fi)
674   {
675     this.finishedInit = fi;
676   }
677 }