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