JAL-1957 JAL-1479 Further optimisation of SIFTs Client and addition of support for...
[jalview.git] / src / jalview / structure / StructureSelectionManager.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.structure;
22
23 import jalview.analysis.AlignSeq;
24 import jalview.api.StructureSelectionManagerProvider;
25 import jalview.commands.CommandI;
26 import jalview.commands.EditCommand;
27 import jalview.commands.OrderCommand;
28 import jalview.datamodel.AlignedCodonFrame;
29 import jalview.datamodel.AlignmentAnnotation;
30 import jalview.datamodel.AlignmentI;
31 import jalview.datamodel.Annotation;
32 import jalview.datamodel.PDBEntry;
33 import jalview.datamodel.SearchResults;
34 import jalview.datamodel.SequenceI;
35 import jalview.io.AppletFormatAdapter;
36 import jalview.util.MappingUtils;
37 import jalview.util.MessageManager;
38 import jalview.ws.sifts.SiftsClient;
39 import jalview.ws.sifts.SiftsException;
40
41 import java.io.PrintStream;
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.Collections;
45 import java.util.Enumeration;
46 import java.util.HashMap;
47 import java.util.IdentityHashMap;
48 import java.util.LinkedHashSet;
49 import java.util.List;
50 import java.util.Map;
51 import java.util.Set;
52 import java.util.Vector;
53
54 import MCview.Atom;
55 import MCview.PDBChain;
56 import MCview.PDBfile;
57
58 public class StructureSelectionManager
59 {
60   public final static String NEWLINE = System.lineSeparator();
61
62   static IdentityHashMap<StructureSelectionManagerProvider, StructureSelectionManager> instances;
63
64   private List<StructureMapping> mappings = new ArrayList<StructureMapping>();
65
66   private boolean processSecondaryStructure = false;
67
68   private boolean secStructServices = false;
69
70   private boolean addTempFacAnnot = false;
71
72   /*
73    * Set of any registered mappings between (dataset) sequences.
74    */
75   public Set<AlignedCodonFrame> seqmappings = new LinkedHashSet<AlignedCodonFrame>();
76
77   private List<CommandListener> commandListeners = new ArrayList<CommandListener>();
78
79   private List<SelectionListener> sel_listeners = new ArrayList<SelectionListener>();
80
81   /**
82    * @return true if will try to use external services for processing secondary
83    *         structure
84    */
85   public boolean isSecStructServices()
86   {
87     return secStructServices;
88   }
89
90   /**
91    * control use of external services for processing secondary structure
92    * 
93    * @param secStructServices
94    */
95   public void setSecStructServices(boolean secStructServices)
96   {
97     this.secStructServices = secStructServices;
98   }
99
100   /**
101    * flag controlling addition of any kind of structural annotation
102    * 
103    * @return true if temperature factor annotation will be added
104    */
105   public boolean isAddTempFacAnnot()
106   {
107     return addTempFacAnnot;
108   }
109
110   /**
111    * set flag controlling addition of structural annotation
112    * 
113    * @param addTempFacAnnot
114    */
115   public void setAddTempFacAnnot(boolean addTempFacAnnot)
116   {
117     this.addTempFacAnnot = addTempFacAnnot;
118   }
119
120   /**
121    * 
122    * @return if true, the structure manager will attempt to add secondary
123    *         structure lines for unannotated sequences
124    */
125
126   public boolean isProcessSecondaryStructure()
127   {
128     return processSecondaryStructure;
129   }
130
131   /**
132    * Control whether structure manager will try to annotate mapped sequences
133    * with secondary structure from PDB data.
134    * 
135    * @param enable
136    */
137   public void setProcessSecondaryStructure(boolean enable)
138   {
139     processSecondaryStructure = enable;
140   }
141
142   /**
143    * debug function - write all mappings to stdout
144    */
145   public void reportMapping()
146   {
147     if (mappings.isEmpty())
148     {
149       System.err.println("reportMapping: No PDB/Sequence mappings.");
150     }
151     else
152     {
153       System.err.println("reportMapping: There are " + mappings.size()
154               + " mappings.");
155       int i = 0;
156       for (StructureMapping sm : mappings)
157       {
158         System.err.println("mapping " + i++ + " : " + sm.pdbfile);
159       }
160     }
161   }
162
163   /**
164    * map between the PDB IDs (or structure identifiers) used by Jalview and the
165    * absolute filenames for PDB data that corresponds to it
166    */
167   Map<String, String> pdbIdFileName = new HashMap<String, String>();
168
169   Map<String, String> pdbFileNameId = new HashMap<String, String>();
170
171   public void registerPDBFile(String idForFile, String absoluteFile)
172   {
173     pdbIdFileName.put(idForFile, absoluteFile);
174     pdbFileNameId.put(absoluteFile, idForFile);
175   }
176
177   public String findIdForPDBFile(String idOrFile)
178   {
179     String id = pdbFileNameId.get(idOrFile);
180     return id;
181   }
182
183   public String findFileForPDBId(String idOrFile)
184   {
185     String id = pdbIdFileName.get(idOrFile);
186     return id;
187   }
188
189   public boolean isPDBFileRegistered(String idOrFile)
190   {
191     return pdbFileNameId.containsKey(idOrFile)
192             || pdbIdFileName.containsKey(idOrFile);
193   }
194
195   private static StructureSelectionManager nullProvider = null;
196
197   public static StructureSelectionManager getStructureSelectionManager(
198           StructureSelectionManagerProvider context)
199   {
200     if (context == null)
201     {
202       if (nullProvider == null)
203       {
204         if (instances != null)
205         {
206           throw new Error(
207                   MessageManager
208                           .getString("error.implementation_error_structure_selection_manager_null"),
209                   new NullPointerException(MessageManager
210                           .getString("exception.ssm_context_is_null")));
211         }
212         else
213         {
214           nullProvider = new StructureSelectionManager();
215         }
216         return nullProvider;
217       }
218     }
219     if (instances == null)
220     {
221       instances = new java.util.IdentityHashMap<StructureSelectionManagerProvider, StructureSelectionManager>();
222     }
223     StructureSelectionManager instance = instances.get(context);
224     if (instance == null)
225     {
226       if (nullProvider != null)
227       {
228         instance = nullProvider;
229       }
230       else
231       {
232         instance = new StructureSelectionManager();
233       }
234       instances.put(context, instance);
235     }
236     return instance;
237   }
238
239   /**
240    * flag controlling whether SeqMappings are relayed from received sequence
241    * mouse over events to other sequences
242    */
243   boolean relaySeqMappings = true;
244
245   /**
246    * Enable or disable relay of seqMapping events to other sequences. You might
247    * want to do this if there are many sequence mappings and the host computer
248    * is slow
249    * 
250    * @param relay
251    */
252   public void setRelaySeqMappings(boolean relay)
253   {
254     relaySeqMappings = relay;
255   }
256
257   /**
258    * get the state of the relay seqMappings flag.
259    * 
260    * @return true if sequence mouse overs are being relayed to other mapped
261    *         sequences
262    */
263   public boolean isRelaySeqMappingsEnabled()
264   {
265     return relaySeqMappings;
266   }
267
268   Vector listeners = new Vector();
269
270   /**
271    * register a listener for alignment sequence mouseover events
272    * 
273    * @param svl
274    */
275   public void addStructureViewerListener(Object svl)
276   {
277     if (!listeners.contains(svl))
278     {
279       listeners.addElement(svl);
280     }
281   }
282
283   /**
284    * Returns the file name for a mapped PDB id (or null if not mapped).
285    * 
286    * @param pdbid
287    * @return
288    */
289   public String alreadyMappedToFile(String pdbid)
290   {
291     for (StructureMapping sm : mappings)
292     {
293       if (sm.getPdbId().equals(pdbid))
294       {
295         return sm.pdbfile;
296       }
297     }
298     return null;
299   }
300
301   /**
302    * Import structure data and register a structure mapping for broadcasting
303    * colouring, mouseovers and selection events (convenience wrapper).
304    * 
305    * @param sequence
306    *          - one or more sequences to be mapped to pdbFile
307    * @param targetChains
308    *          - optional chain specification for mapping each sequence to pdb
309    *          (may be nill, individual elements may be nill)
310    * @param pdbFile
311    *          - structure data resource
312    * @param protocol
313    *          - how to resolve data from resource
314    * @return null or the structure data parsed as a pdb file
315    */
316   synchronized public PDBfile setMapping(SequenceI[] sequence,
317           String[] targetChains, String pdbFile, String protocol)
318   {
319     return setMapping(true, sequence, targetChains, pdbFile, protocol);
320   }
321
322   /**
323    * create sequence structure mappings between each sequence and the given
324    * pdbFile (retrieved via the given protocol).
325    * 
326    * @param forStructureView
327    *          when true, record the mapping for use in mouseOvers
328    * 
329    * @param sequenceArray
330    *          - one or more sequences to be mapped to pdbFile
331    * @param targetChains
332    *          - optional chain specification for mapping each sequence to pdb
333    *          (may be nill, individual elements may be nill)
334    * @param pdbFile
335    *          - structure data resource
336    * @param protocol
337    *          - how to resolve data from resource
338    * @return null or the structure data parsed as a pdb file
339    */
340   synchronized public PDBfile setMapping(boolean forStructureView,
341           SequenceI[] sequenceArray, String[] targetChains, String pdbFile,
342           String protocol)
343   {
344     /*
345      * There will be better ways of doing this in the future, for now we'll use
346      * the tried and tested MCview pdb mapping
347      */
348     boolean parseSecStr = processSecondaryStructure;
349     if (isPDBFileRegistered(pdbFile))
350     {
351       for (SequenceI sq : sequenceArray)
352       {
353         SequenceI ds = sq;
354         while (ds.getDatasetSequence() != null)
355         {
356           ds = ds.getDatasetSequence();
357         }
358         ;
359         if (ds.getAnnotation() != null)
360         {
361           for (AlignmentAnnotation ala : ds.getAnnotation())
362           {
363             // false if any annotation present from this structure
364             // JBPNote this fails for jmol/chimera view because the *file* is
365             // passed, not the structure data ID -
366             if (PDBfile.isCalcIdForFile(ala, findIdForPDBFile(pdbFile)))
367             {
368               parseSecStr = false;
369             }
370           }
371         }
372       }
373     }
374     PDBfile pdb = null;
375     boolean isMapUsingSIFTs = Boolean.valueOf(jalview.bin.Cache.getDefault(
376             "MAP_WITH_SIFTS", "false"));
377     SiftsClient siftsClient = null;
378     try
379     {
380       pdb = new PDBfile(addTempFacAnnot, parseSecStr, secStructServices,
381               pdbFile, protocol);
382       if (isMapUsingSIFTs)
383       {
384         siftsClient = new SiftsClient(pdb);
385       }
386       if (pdb.id != null && pdb.id.trim().length() > 0
387               && AppletFormatAdapter.FILE.equals(protocol))
388       {
389         registerPDBFile(pdb.id.trim(), pdbFile);
390       }
391     } catch (SiftsException e)
392     {
393       e.printStackTrace();
394     } catch (Exception ex)
395     {
396       ex.printStackTrace();
397       return null;
398     }
399
400     String targetChain;
401     for (int s = 0; s < sequenceArray.length; s++)
402     {
403       boolean infChain = true;
404       final SequenceI seq = sequenceArray[s];
405       if (targetChains != null && targetChains[s] != null)
406       {
407         infChain = false;
408         targetChain = targetChains[s];
409       }
410       else if (seq.getName().indexOf("|") > -1)
411       {
412         targetChain = seq.getName().substring(
413                 seq.getName().lastIndexOf("|") + 1);
414         if (targetChain.length() > 1)
415         {
416           if (targetChain.trim().length() == 0)
417           {
418             targetChain = " ";
419           }
420           else
421           {
422             // not a valid chain identifier
423             targetChain = "";
424           }
425         }
426       }
427       else
428       {
429         targetChain = "";
430       }
431
432       /*
433        * Attempt pairwise alignment of the sequence with each chain in the PDB,
434        * and remember the highest scoring chain
435        */
436       int max = -10;
437       AlignSeq maxAlignseq = null;
438       String maxChainId = " ";
439       PDBChain maxChain = null;
440       boolean first = true;
441       for (PDBChain chain : pdb.chains)
442       {
443         if (targetChain.length() > 0 && !targetChain.equals(chain.id)
444                 && !infChain)
445         {
446           continue; // don't try to map chains don't match.
447         }
448         // TODO: correctly determine sequence type for mixed na/peptide
449         // structures
450         final String type = chain.isNa ? AlignSeq.DNA : AlignSeq.PEP;
451         AlignSeq as = AlignSeq.doGlobalNWAlignment(seq, chain.sequence,
452                 type);
453         // equivalent to:
454         // AlignSeq as = new AlignSeq(sequence[s], chain.sequence, type);
455         // as.calcScoreMatrix();
456         // as.traceAlignment();
457
458         if (first || as.maxscore > max
459                 || (as.maxscore == max && chain.id.equals(targetChain)))
460         {
461           first = false;
462           maxChain = chain;
463           max = as.maxscore;
464           maxAlignseq = as;
465           maxChainId = chain.id;
466         }
467       }
468       if (maxChain == null)
469       {
470         continue;
471       }
472
473       if (protocol.equals(jalview.io.AppletFormatAdapter.PASTE))
474       {
475         pdbFile = "INLINE" + pdb.id;
476       }
477
478       ArrayList<StructureMapping> seqToStrucMapping = null;
479       if (isMapUsingSIFTs)
480       {
481         try
482         {
483           seqToStrucMapping = new ArrayList<StructureMapping>();
484           if (targetChain != null && !targetChain.trim().isEmpty())
485           {
486             maxChainId = targetChain;
487             StructureMapping curChainMapping = siftsClient
488                     .getSiftsStructureMapping(seq, pdbFile, targetChain);
489             seqToStrucMapping.add(curChainMapping);
490           }
491           else
492           {
493             for (PDBChain chain : pdb.chains)
494             {
495               maxChainId = chain.id;
496               StructureMapping curChainMapping = siftsClient
497                       .getSiftsStructureMapping(seq, pdbFile, chain.id);
498               seqToStrucMapping.add(curChainMapping);
499             }
500           }
501         } catch (SiftsException e)
502         {
503           System.err
504                   .println(">>>>>>> SIFTs mapping could not be obtained... Now mapping with NW alignment");
505           seqToStrucMapping = getNWMappings(seq, pdbFile, maxChainId,
506                   maxChain, pdb, maxAlignseq);
507         }
508       }
509       else
510       {
511         seqToStrucMapping = getNWMappings(seq, pdbFile,
512                 maxChainId, maxChain, pdb,
513                 maxAlignseq);
514       }
515
516       if (forStructureView)
517       {
518         // mappings.add(seqToStrucMapping);
519         mappings.addAll(seqToStrucMapping);
520       }
521     }
522     return pdb;
523   }
524
525   private ArrayList<StructureMapping> getNWMappings(SequenceI seq,
526           String pdbFile,
527           String maxChainId, PDBChain maxChain, PDBfile pdb,
528           AlignSeq maxAlignseq)
529   {
530     final StringBuilder mappingDetails = new StringBuilder(128);
531     mappingDetails.append(NEWLINE).append(
532             "Sequence \u27f7 Structure mapping details");
533     mappingDetails.append(NEWLINE);
534     mappingDetails
535             .append("Method: inferred with Needleman & Wunsch alignment");
536     mappingDetails.append(NEWLINE).append("PDB Sequence is :")
537             .append(NEWLINE).append("Sequence = ")
538             .append(maxChain.sequence.getSequenceAsString());
539     mappingDetails.append(NEWLINE).append("No of residues = ")
540             .append(maxChain.residues.size()).append(NEWLINE)
541             .append(NEWLINE);
542     PrintStream ps = new PrintStream(System.out)
543     {
544       @Override
545       public void print(String x)
546       {
547         mappingDetails.append(x);
548       }
549
550       @Override
551       public void println()
552       {
553         mappingDetails.append(NEWLINE);
554       }
555     };
556
557     maxAlignseq.printAlignment(ps);
558
559     mappingDetails.append(NEWLINE).append("PDB start/end ");
560     mappingDetails.append(String.valueOf(maxAlignseq.seq2start))
561             .append(" ");
562     mappingDetails.append(String.valueOf(maxAlignseq.seq2end));
563     mappingDetails.append(NEWLINE).append("SEQ start/end ");
564     mappingDetails.append(
565             String.valueOf(maxAlignseq.seq1start + (seq.getStart() - 1)))
566             .append(" ");
567     mappingDetails.append(String.valueOf(maxAlignseq.seq1end
568             + (seq.getStart() - 1)));
569     mappingDetails.append(NEWLINE);
570     maxChain.makeExactMapping(maxAlignseq, seq);
571     jalview.datamodel.Mapping sqmpping = maxAlignseq
572             .getMappingFromS1(false);
573     maxChain.transferRESNUMFeatures(seq, null);
574
575     // allocate enough slots to store the mapping from positions in
576     // sequence[s] to the associated chain
577     int[][] mapping = new int[seq.findPosition(seq.getLength()) + 2][2];
578     int resNum = -10000;
579     int index = 0;
580
581     do
582     {
583       Atom tmp = maxChain.atoms.elementAt(index);
584       if (resNum != tmp.resNumber && tmp.alignmentMapping != -1)
585       {
586         resNum = tmp.resNumber;
587         if (tmp.alignmentMapping >= -1)
588         {
589           // TODO (JAL-1836) address root cause: negative residue no in PDB
590           // file
591           mapping[tmp.alignmentMapping + 1][0] = tmp.resNumber;
592           mapping[tmp.alignmentMapping + 1][1] = tmp.atomIndex;
593         }
594       }
595
596       index++;
597     } while (index < maxChain.atoms.size());
598
599     StructureMapping nwMapping = new StructureMapping(seq, pdbFile,
600             pdb.id, maxChainId, mapping, mappingDetails.toString());
601     maxChain.transferResidueAnnotation(nwMapping, sqmpping);
602     ArrayList<StructureMapping> mappings = new ArrayList<StructureMapping>();
603     mappings.add(nwMapping);
604     return mappings;
605   }
606
607   public void removeStructureViewerListener(Object svl, String[] pdbfiles)
608   {
609     listeners.removeElement(svl);
610     if (svl instanceof SequenceListener)
611     {
612       for (int i = 0; i < listeners.size(); i++)
613       {
614         if (listeners.elementAt(i) instanceof StructureListener)
615         {
616           ((StructureListener) listeners.elementAt(i))
617                   .releaseReferences(svl);
618         }
619       }
620     }
621
622     if (pdbfiles == null)
623     {
624       return;
625     }
626
627     /*
628      * Remove mappings to the closed listener's PDB files, but first check if
629      * another listener is still interested
630      */
631     List<String> pdbs = new ArrayList<String>(Arrays.asList(pdbfiles));
632
633     StructureListener sl;
634     for (int i = 0; i < listeners.size(); i++)
635     {
636       if (listeners.elementAt(i) instanceof StructureListener)
637       {
638         sl = (StructureListener) listeners.elementAt(i);
639         for (String pdbfile : sl.getPdbFile())
640         {
641           pdbs.remove(pdbfile);
642         }
643       }
644     }
645
646     /*
647      * Rebuild the mappings set, retaining only those which are for 'other' PDB
648      * files
649      */
650     if (pdbs.size() > 0)
651     {
652       List<StructureMapping> tmp = new ArrayList<StructureMapping>();
653       for (StructureMapping sm : mappings)
654       {
655         if (!pdbs.contains(sm.pdbfile))
656         {
657           tmp.add(sm);
658         }
659       }
660
661       mappings = tmp;
662     }
663   }
664
665   /**
666    * Propagate mouseover of a single position in a structure
667    * 
668    * @param pdbResNum
669    * @param chain
670    * @param pdbfile
671    */
672   public void mouseOverStructure(int pdbResNum, String chain, String pdbfile)
673   {
674     AtomSpec atomSpec = new AtomSpec(pdbfile, chain, pdbResNum, 0);
675     List<AtomSpec> atoms = Collections.singletonList(atomSpec);
676     mouseOverStructure(atoms);
677   }
678
679   /**
680    * Propagate mouseover or selection of multiple positions in a structure
681    * 
682    * @param atoms
683    */
684   public void mouseOverStructure(List<AtomSpec> atoms)
685   {
686     if (listeners == null)
687     {
688       // old or prematurely sent event
689       return;
690     }
691     boolean hasSequenceListener = false;
692     for (int i = 0; i < listeners.size(); i++)
693     {
694       if (listeners.elementAt(i) instanceof SequenceListener)
695       {
696         hasSequenceListener = true;
697       }
698     }
699     if (!hasSequenceListener)
700     {
701       return;
702     }
703
704     SearchResults results = new SearchResults();
705     for (AtomSpec atom : atoms)
706     {
707       SequenceI lastseq = null;
708       int lastipos = -1;
709       for (StructureMapping sm : mappings)
710       {
711         if (sm.pdbfile.equals(atom.getPdbFile())
712                 && sm.pdbchain.equals(atom.getChain()))
713         {
714           int indexpos = sm.getSeqPos(atom.getPdbResNum());
715           if (lastipos != indexpos && lastseq != sm.sequence)
716           {
717             results.addResult(sm.sequence, indexpos, indexpos);
718             lastipos = indexpos;
719             lastseq = sm.sequence;
720             // construct highlighted sequence list
721             for (AlignedCodonFrame acf : seqmappings)
722             {
723               acf.markMappedRegion(sm.sequence, indexpos, results);
724             }
725           }
726         }
727       }
728     }
729     for (Object li : listeners)
730     {
731       if (li instanceof SequenceListener)
732       {
733         ((SequenceListener) li).highlightSequence(results);
734       }
735     }
736   }
737
738   /**
739    * highlight regions associated with a position (indexpos) in seq
740    * 
741    * @param seq
742    *          the sequence that the mouse over occurred on
743    * @param indexpos
744    *          the absolute position being mouseovered in seq (0 to seq.length())
745    * @param index
746    *          the sequence position (if -1, seq.findPosition is called to
747    *          resolve the residue number)
748    */
749   public void mouseOverSequence(SequenceI seq, int indexpos, int index,
750           VamsasSource source)
751   {
752     boolean hasSequenceListeners = handlingVamsasMo
753             || !seqmappings.isEmpty();
754     SearchResults results = null;
755     if (index == -1)
756     {
757       index = seq.findPosition(indexpos);
758     }
759     for (int i = 0; i < listeners.size(); i++)
760     {
761       Object listener = listeners.elementAt(i);
762       if (listener == source)
763       {
764         // TODO listener (e.g. SeqPanel) is never == source (AlignViewport)
765         // Temporary fudge with SequenceListener.getVamsasSource()
766         continue;
767       }
768       if (listener instanceof StructureListener)
769       {
770         highlightStructure((StructureListener) listener, seq, index);
771       }
772       else
773       {
774         if (listener instanceof SequenceListener)
775         {
776           final SequenceListener seqListener = (SequenceListener) listener;
777           if (hasSequenceListeners
778                   && seqListener.getVamsasSource() != source)
779           {
780             if (relaySeqMappings)
781             {
782               if (results == null)
783               {
784                 results = MappingUtils.buildSearchResults(seq, index,
785                         seqmappings);
786               }
787               if (handlingVamsasMo)
788               {
789                 results.addResult(seq, index, index);
790
791               }
792               if (!results.isEmpty())
793               {
794                 seqListener.highlightSequence(results);
795               }
796             }
797           }
798         }
799         else if (listener instanceof VamsasListener && !handlingVamsasMo)
800         {
801           ((VamsasListener) listener).mouseOverSequence(seq, indexpos,
802                   source);
803         }
804         else if (listener instanceof SecondaryStructureListener)
805         {
806           ((SecondaryStructureListener) listener).mouseOverSequence(seq,
807                   indexpos, index);
808         }
809       }
810     }
811   }
812
813   /**
814    * Send suitable messages to a StructureListener to highlight atoms
815    * corresponding to the given sequence position.
816    * 
817    * @param sl
818    * @param seq
819    * @param index
820    */
821   protected void highlightStructure(StructureListener sl, SequenceI seq,
822           int index)
823   {
824     if (!sl.isListeningFor(seq))
825     {
826       return;
827     }
828     int atomNo;
829     List<AtomSpec> atoms = new ArrayList<AtomSpec>();
830     for (StructureMapping sm : mappings)
831     {
832       if (sm.sequence == seq || sm.sequence == seq.getDatasetSequence())
833       {
834         atomNo = sm.getAtomNum(index);
835
836         if (atomNo > 0)
837         {
838           atoms.add(new AtomSpec(sm.pdbfile, sm.pdbchain, sm
839                   .getPDBResNum(index), atomNo));
840         }
841       }
842     }
843     sl.highlightAtoms(atoms);
844   }
845
846   /**
847    * true if a mouse over event from an external (ie Vamsas) source is being
848    * handled
849    */
850   boolean handlingVamsasMo = false;
851
852   long lastmsg = 0;
853
854   /**
855    * as mouseOverSequence but only route event to SequenceListeners
856    * 
857    * @param sequenceI
858    * @param position
859    *          in an alignment sequence
860    */
861   public void mouseOverVamsasSequence(SequenceI sequenceI, int position,
862           VamsasSource source)
863   {
864     handlingVamsasMo = true;
865     long msg = sequenceI.hashCode() * (1 + position);
866     if (lastmsg != msg)
867     {
868       lastmsg = msg;
869       mouseOverSequence(sequenceI, position, -1, source);
870     }
871     handlingVamsasMo = false;
872   }
873
874   public Annotation[] colourSequenceFromStructure(SequenceI seq,
875           String pdbid)
876   {
877     return null;
878     // THIS WILL NOT BE AVAILABLE IN JALVIEW 2.3,
879     // UNTIL THE COLOUR BY ANNOTATION IS REWORKED
880     /*
881      * Annotation [] annotations = new Annotation[seq.getLength()];
882      * 
883      * StructureListener sl; int atomNo = 0; for (int i = 0; i <
884      * listeners.size(); i++) { if (listeners.elementAt(i) instanceof
885      * StructureListener) { sl = (StructureListener) listeners.elementAt(i);
886      * 
887      * for (int j = 0; j < mappings.length; j++) {
888      * 
889      * if (mappings[j].sequence == seq && mappings[j].getPdbId().equals(pdbid)
890      * && mappings[j].pdbfile.equals(sl.getPdbFile())) {
891      * System.out.println(pdbid+" "+mappings[j].getPdbId() +"
892      * "+mappings[j].pdbfile);
893      * 
894      * java.awt.Color col; for(int index=0; index<seq.getLength(); index++) {
895      * if(jalview.util.Comparison.isGap(seq.getCharAt(index))) continue;
896      * 
897      * atomNo = mappings[j].getAtomNum(seq.findPosition(index)); col =
898      * java.awt.Color.white; if (atomNo > 0) { col = sl.getColour(atomNo,
899      * mappings[j].getPDBResNum(index), mappings[j].pdbchain,
900      * mappings[j].pdbfile); }
901      * 
902      * annotations[index] = new Annotation("X",null,' ',0,col); } return
903      * annotations; } } } }
904      * 
905      * return annotations;
906      */
907   }
908
909   public void structureSelectionChanged()
910   {
911   }
912
913   public void sequenceSelectionChanged()
914   {
915   }
916
917   public void sequenceColoursChanged(Object source)
918   {
919     StructureListener sl;
920     for (int i = 0; i < listeners.size(); i++)
921     {
922       if (listeners.elementAt(i) instanceof StructureListener)
923       {
924         sl = (StructureListener) listeners.elementAt(i);
925         sl.updateColours(source);
926       }
927     }
928   }
929
930   public StructureMapping[] getMapping(String pdbfile)
931   {
932     List<StructureMapping> tmp = new ArrayList<StructureMapping>();
933     for (StructureMapping sm : mappings)
934     {
935       if (sm.pdbfile.equals(pdbfile))
936       {
937         tmp.add(sm);
938       }
939     }
940     return tmp.toArray(new StructureMapping[tmp.size()]);
941   }
942
943   /**
944    * Returns a readable description of all mappings for the given pdbfile to any
945    * of the given sequences
946    * 
947    * @param pdbfile
948    * @param seqs
949    * @return
950    */
951   public String printMappings(String pdbfile, List<SequenceI> seqs)
952   {
953     if (pdbfile == null || seqs == null || seqs.isEmpty())
954     {
955       return "";
956     }
957
958     StringBuilder sb = new StringBuilder(64);
959     for (StructureMapping sm : mappings)
960     {
961       if (sm.pdbfile.equals(pdbfile) && seqs.contains(sm.sequence))
962       {
963         sb.append(sm.mappingDetails);
964         sb.append(NEWLINE);
965         // separator makes it easier to read multiple mappings
966         sb.append("=====================");
967         sb.append(NEWLINE);
968       }
969     }
970     sb.append(NEWLINE);
971
972     return sb.toString();
973   }
974
975   /**
976    * Remove the given mapping
977    * 
978    * @param acf
979    */
980   public void deregisterMapping(AlignedCodonFrame acf)
981   {
982     if (acf != null)
983     {
984       boolean removed = seqmappings.remove(acf);
985       if (removed && seqmappings.isEmpty())
986       { // debug
987         System.out.println("All mappings removed");
988       }
989     }
990   }
991
992   /**
993    * Add each of the given codonFrames to the stored set, if not aready present.
994    * 
995    * @param set
996    */
997   public void registerMappings(Set<AlignedCodonFrame> set)
998   {
999     if (set != null)
1000     {
1001       for (AlignedCodonFrame acf : set)
1002       {
1003         registerMapping(acf);
1004       }
1005     }
1006   }
1007
1008   /**
1009    * Add the given mapping to the stored set, unless already stored.
1010    */
1011   public void registerMapping(AlignedCodonFrame acf)
1012   {
1013     if (acf != null)
1014     {
1015       if (!seqmappings.contains(acf))
1016       {
1017         seqmappings.add(acf);
1018       }
1019     }
1020   }
1021
1022   /**
1023    * Resets this object to its initial state by removing all registered
1024    * listeners, codon mappings, PDB file mappings
1025    */
1026   public void resetAll()
1027   {
1028     if (mappings != null)
1029     {
1030       mappings.clear();
1031     }
1032     if (seqmappings != null)
1033     {
1034       seqmappings.clear();
1035     }
1036     if (sel_listeners != null)
1037     {
1038       sel_listeners.clear();
1039     }
1040     if (listeners != null)
1041     {
1042       listeners.clear();
1043     }
1044     if (commandListeners != null)
1045     {
1046       commandListeners.clear();
1047     }
1048     if (view_listeners != null)
1049     {
1050       view_listeners.clear();
1051     }
1052     if (pdbFileNameId != null)
1053     {
1054       pdbFileNameId.clear();
1055     }
1056     if (pdbIdFileName != null)
1057     {
1058       pdbIdFileName.clear();
1059     }
1060   }
1061
1062   public void addSelectionListener(SelectionListener selecter)
1063   {
1064     if (!sel_listeners.contains(selecter))
1065     {
1066       sel_listeners.add(selecter);
1067     }
1068   }
1069
1070   public void removeSelectionListener(SelectionListener toremove)
1071   {
1072     if (sel_listeners.contains(toremove))
1073     {
1074       sel_listeners.remove(toremove);
1075     }
1076   }
1077
1078   public synchronized void sendSelection(
1079           jalview.datamodel.SequenceGroup selection,
1080           jalview.datamodel.ColumnSelection colsel, SelectionSource source)
1081   {
1082     for (SelectionListener slis : sel_listeners)
1083     {
1084       if (slis != source)
1085       {
1086         slis.selection(selection, colsel, source);
1087       }
1088     }
1089   }
1090
1091   Vector<AlignmentViewPanelListener> view_listeners = new Vector<AlignmentViewPanelListener>();
1092
1093   public synchronized void sendViewPosition(
1094           jalview.api.AlignmentViewPanel source, int startRes, int endRes,
1095           int startSeq, int endSeq)
1096   {
1097
1098     if (view_listeners != null && view_listeners.size() > 0)
1099     {
1100       Enumeration<AlignmentViewPanelListener> listeners = view_listeners
1101               .elements();
1102       while (listeners.hasMoreElements())
1103       {
1104         AlignmentViewPanelListener slis = listeners.nextElement();
1105         if (slis != source)
1106         {
1107           slis.viewPosition(startRes, endRes, startSeq, endSeq, source);
1108         }
1109         ;
1110       }
1111     }
1112   }
1113
1114   /**
1115    * release all references associated with this manager provider
1116    * 
1117    * @param jalviewLite
1118    */
1119   public static void release(StructureSelectionManagerProvider jalviewLite)
1120   {
1121     // synchronized (instances)
1122     {
1123       if (instances == null)
1124       {
1125         return;
1126       }
1127       StructureSelectionManager mnger = (instances.get(jalviewLite));
1128       if (mnger != null)
1129       {
1130         instances.remove(jalviewLite);
1131         try
1132         {
1133           mnger.finalize();
1134         } catch (Throwable x)
1135         {
1136         }
1137       }
1138     }
1139   }
1140
1141   public void registerPDBEntry(PDBEntry pdbentry)
1142   {
1143     if (pdbentry.getFile() != null
1144             && pdbentry.getFile().trim().length() > 0)
1145     {
1146       registerPDBFile(pdbentry.getId(), pdbentry.getFile());
1147     }
1148   }
1149
1150   public void addCommandListener(CommandListener cl)
1151   {
1152     if (!commandListeners.contains(cl))
1153     {
1154       commandListeners.add(cl);
1155     }
1156   }
1157
1158   public boolean hasCommandListener(CommandListener cl)
1159   {
1160     return this.commandListeners.contains(cl);
1161   }
1162
1163   public boolean removeCommandListener(CommandListener l)
1164   {
1165     return commandListeners.remove(l);
1166   }
1167
1168   /**
1169    * Forward a command to any command listeners (except for the command's
1170    * source).
1171    * 
1172    * @param command
1173    *          the command to be broadcast (in its form after being performed)
1174    * @param undo
1175    *          if true, the command was being 'undone'
1176    * @param source
1177    */
1178   public void commandPerformed(CommandI command, boolean undo,
1179           VamsasSource source)
1180   {
1181     for (CommandListener listener : commandListeners)
1182     {
1183       listener.mirrorCommand(command, undo, this, source);
1184     }
1185   }
1186
1187   /**
1188    * Returns a new CommandI representing the given command as mapped to the
1189    * given sequences. If no mapping could be made, or the command is not of a
1190    * mappable kind, returns null.
1191    * 
1192    * @param command
1193    * @param undo
1194    * @param mapTo
1195    * @param gapChar
1196    * @return
1197    */
1198   public CommandI mapCommand(CommandI command, boolean undo,
1199           final AlignmentI mapTo, char gapChar)
1200   {
1201     if (command instanceof EditCommand)
1202     {
1203       return MappingUtils.mapEditCommand((EditCommand) command, undo,
1204               mapTo, gapChar, seqmappings);
1205     }
1206     else if (command instanceof OrderCommand)
1207     {
1208       return MappingUtils.mapOrderCommand((OrderCommand) command, undo,
1209               mapTo, seqmappings);
1210     }
1211     return null;
1212   }
1213 }