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