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