0f82650c2226e3a7acf0e9313fc5a59fbdcf7c8c
[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 java.io.PrintStream;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.Collections;
27 import java.util.Enumeration;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.IdentityHashMap;
31 import java.util.List;
32 import java.util.Locale;
33 import java.util.Map;
34 import java.util.Set;
35 import java.util.Vector;
36
37 import jalview.analysis.AlignSeq;
38 import jalview.api.StructureSelectionManagerProvider;
39 import jalview.bin.Console;
40 import jalview.commands.CommandI;
41 import jalview.commands.EditCommand;
42 import jalview.commands.OrderCommand;
43 import jalview.datamodel.AlignedCodonFrame;
44 import jalview.datamodel.AlignmentAnnotation;
45 import jalview.datamodel.AlignmentI;
46 import jalview.datamodel.Annotation;
47 import jalview.datamodel.ContiguousI;
48 import jalview.datamodel.HiddenColumns;
49 import jalview.datamodel.PDBEntry;
50 import jalview.datamodel.SearchResults;
51 import jalview.datamodel.SearchResultsI;
52 import jalview.datamodel.SequenceI;
53 import jalview.ext.jmol.JmolParser;
54 import jalview.gui.IProgressIndicator;
55 import jalview.io.AppletFormatAdapter;
56 import jalview.io.DataSourceType;
57 import jalview.io.StructureFile;
58 import jalview.structure.StructureImportSettings.TFType;
59 import jalview.util.MapList;
60 import jalview.util.MappingUtils;
61 import jalview.util.MessageManager;
62 import jalview.util.Platform;
63 import jalview.ws.sifts.SiftsClient;
64 import jalview.ws.sifts.SiftsException;
65 import jalview.ws.sifts.SiftsSettings;
66 import mc_view.Atom;
67 import mc_view.PDBChain;
68 import mc_view.PDBfile;
69
70 public class StructureSelectionManager
71 {
72   public final static String NEWLINE = System.lineSeparator();
73
74   static IdentityHashMap<StructureSelectionManagerProvider, StructureSelectionManager> instances;
75
76   private List<StructureMapping> mappings = new ArrayList<>();
77
78   private boolean processSecondaryStructure = false;
79
80   private boolean secStructServices = false;
81
82   private boolean addTempFacAnnot = false;
83
84   /*
85    * Set of any registered mappings between (dataset) sequences.
86    */
87   private List<AlignedCodonFrame> seqmappings = new ArrayList<>();
88
89   private List<CommandListener> commandListeners = new ArrayList<>();
90
91   private List<SelectionListener> sel_listeners = new ArrayList<>();
92
93   /**
94    * @return true if will try to use external services for processing secondary
95    *         structure
96    */
97   public boolean isSecStructServices()
98   {
99     return secStructServices;
100   }
101
102   /**
103    * control use of external services for processing secondary structure
104    * 
105    * @param secStructServices
106    */
107   public void setSecStructServices(boolean secStructServices)
108   {
109     this.secStructServices = secStructServices;
110   }
111
112   /**
113    * flag controlling addition of any kind of structural annotation
114    * 
115    * @return true if temperature factor annotation will be added
116    */
117   public boolean isAddTempFacAnnot()
118   {
119     return addTempFacAnnot;
120   }
121
122   /**
123    * set flag controlling addition of structural annotation
124    * 
125    * @param addTempFacAnnot
126    */
127   public void setAddTempFacAnnot(boolean addTempFacAnnot)
128   {
129     this.addTempFacAnnot = addTempFacAnnot;
130   }
131
132   /**
133    * 
134    * @return if true, the structure manager will attempt to add secondary
135    *         structure lines for unannotated sequences
136    */
137
138   public boolean isProcessSecondaryStructure()
139   {
140     return processSecondaryStructure;
141   }
142
143   /**
144    * Control whether structure manager will try to annotate mapped sequences
145    * with secondary structure from PDB data.
146    * 
147    * @param enable
148    */
149   public void setProcessSecondaryStructure(boolean enable)
150   {
151     processSecondaryStructure = enable;
152   }
153
154   /**
155    * debug function - write all mappings to stdout
156    */
157   public void reportMapping()
158   {
159     if (mappings.isEmpty())
160     {
161       jalview.bin.Console
162               .errPrintln("reportMapping: No PDB/Sequence mappings.");
163     }
164     else
165     {
166       jalview.bin.Console.errPrintln(
167               "reportMapping: There are " + mappings.size() + " mappings.");
168       int i = 0;
169       for (StructureMapping sm : mappings)
170       {
171         jalview.bin.Console
172                 .errPrintln("mapping " + i++ + " : " + sm.pdbfile);
173       }
174     }
175   }
176
177   /**
178    * map between the PDB IDs (or structure identifiers) used by Jalview and the
179    * absolute filenames for PDB data that corresponds to it
180    */
181   Map<String, String> pdbIdFileName = new HashMap<>();
182
183   Map<String, String> pdbFileNameId = new HashMap<>();
184
185   public void registerPDBFile(String idForFile, String absoluteFile)
186   {
187     pdbIdFileName.put(idForFile, absoluteFile);
188     pdbFileNameId.put(absoluteFile, idForFile);
189   }
190
191   public String findIdForPDBFile(String idOrFile)
192   {
193     String id = pdbFileNameId.get(idOrFile);
194     return id;
195   }
196
197   public String findFileForPDBId(String idOrFile)
198   {
199     String id = pdbIdFileName.get(idOrFile);
200     return id;
201   }
202
203   public boolean isPDBFileRegistered(String idOrFile)
204   {
205     return pdbFileNameId.containsKey(idOrFile)
206             || pdbIdFileName.containsKey(idOrFile);
207   }
208
209   private static StructureSelectionManager nullProvider = null;
210
211   public static StructureSelectionManager getStructureSelectionManager(
212           StructureSelectionManagerProvider context)
213   {
214     if (context == null)
215     {
216       if (nullProvider == null)
217       {
218         if (instances != null)
219         {
220           throw new Error(MessageManager.getString(
221                   "error.implementation_error_structure_selection_manager_null"),
222                   new NullPointerException(MessageManager
223                           .getString("exception.ssm_context_is_null")));
224         }
225         else
226         {
227           nullProvider = new StructureSelectionManager();
228         }
229         return nullProvider;
230       }
231     }
232     if (instances == null)
233     {
234       instances = new java.util.IdentityHashMap<>();
235     }
236     StructureSelectionManager instance = instances.get(context);
237     if (instance == null)
238     {
239       if (nullProvider != null)
240       {
241         instance = nullProvider;
242       }
243       else
244       {
245         instance = new StructureSelectionManager();
246       }
247       instances.put(context, instance);
248     }
249     return instance;
250   }
251
252   /**
253    * flag controlling whether SeqMappings are relayed from received sequence
254    * mouse over events to other sequences
255    */
256   boolean relaySeqMappings = true;
257
258   /**
259    * Enable or disable relay of seqMapping events to other sequences. You might
260    * want to do this if there are many sequence mappings and the host computer
261    * is slow
262    * 
263    * @param relay
264    */
265   public void setRelaySeqMappings(boolean relay)
266   {
267     relaySeqMappings = relay;
268   }
269
270   /**
271    * get the state of the relay seqMappings flag.
272    * 
273    * @return true if sequence mouse overs are being relayed to other mapped
274    *         sequences
275    */
276   public boolean isRelaySeqMappingsEnabled()
277   {
278     return relaySeqMappings;
279   }
280
281   Vector listeners = new Vector();
282
283   /**
284    * register a listener for alignment sequence mouseover events
285    * 
286    * @param svl
287    */
288   public void addStructureViewerListener(Object svl)
289   {
290     if (!listeners.contains(svl))
291     {
292       listeners.addElement(svl);
293     }
294   }
295
296   /**
297    * Returns the filename the PDB id is already mapped to if known, or null if
298    * it is not mapped
299    * 
300    * @param pdbid
301    * @return
302    */
303   public String alreadyMappedToFile(String pdbid)
304   {
305     for (StructureMapping sm : mappings)
306     {
307       if (sm.getPdbId().equalsIgnoreCase(pdbid))
308       {
309         return sm.pdbfile;
310       }
311     }
312     return null;
313   }
314
315   /**
316    * Import structure data and register a structure mapping for broadcasting
317    * colouring, mouseovers and selection events (convenience wrapper).
318    * 
319    * @param sequence
320    *          - one or more sequences to be mapped to pdbFile
321    * @param targetChains
322    *          - optional chain specification for mapping each sequence to pdb
323    *          (may be nill, individual elements may be nill)
324    * @param pdbFile
325    *          - structure data resource
326    * @param protocol
327    *          - how to resolve data from resource
328    * @return null or the structure data parsed as a pdb file
329    */
330   synchronized public StructureFile setMapping(SequenceI[] sequence,
331           String[] targetChains, String pdbFile, DataSourceType protocol,
332           IProgressIndicator progress)
333   {
334     return computeMapping(true, sequence, targetChains, pdbFile, protocol,
335             progress, null, null, true);
336   }
337
338   /**
339    * Import a single structure file and register sequence structure mappings for
340    * broadcasting colouring, mouseovers and selection events (convenience
341    * wrapper).
342    * 
343    * @param forStructureView
344    *          when true, record the mapping for use in mouseOvers
345    * @param sequence
346    *          - one or more sequences to be mapped to pdbFile
347    * @param targetChains
348    *          - optional chain specification for mapping each sequence to pdb
349    *          (may be nill, individual elements may be nill)
350    * @param pdbFile
351    *          - structure data resource
352    * @param protocol
353    *          - how to resolve data from resource
354    * @return null or the structure data parsed as a pdb file
355    */
356   synchronized public StructureFile setMapping(boolean forStructureView,
357           SequenceI[] sequenceArray, String[] targetChainIds,
358           String pdbFile, DataSourceType sourceType, TFType tft,
359           String paeFilename)
360   {
361     return setMapping(forStructureView, sequenceArray, targetChainIds,
362             pdbFile, sourceType, tft, paeFilename, true);
363   }
364
365   /**
366    * create sequence structure mappings between each sequence and the given
367    * pdbFile (retrieved via the given protocol). Either constructs a mapping
368    * using NW alignment or derives one from any available SIFTS mapping data.
369    * 
370    * @param forStructureView
371    *          when true, record the mapping for use in mouseOvers
372    * 
373    * @param sequenceArray
374    *          - one or more sequences to be mapped to pdbFile
375    * @param targetChainIds
376    *          - optional chain specification for mapping each sequence to pdb
377    *          (may be nill, individual elements may be nill) - JBPNote: JAL-2693
378    *          - this should be List<List<String>>, empty lists indicate no
379    *          predefined mappings
380    * @param pdbFile
381    *          - structure data resource
382    * @param sourceType
383    *          - how to resolve data from resource
384    * @param tft
385    *          - specify how to interpret the temperature factor column in the
386    *          atom data
387    * @param paeFilename
388    *          - when not null, specifies a filename containing a matrix
389    *          formatted in JSON using one of the known PAE formats
390    * @param doXferSettings
391    *          - when true, transfer annotation to mapped sequences in
392    *          sequenceArray
393    * @return null or the structure data parsed as a pdb file
394    */
395   synchronized public StructureFile setMapping(boolean forStructureView,
396           SequenceI[] sequenceArray, String[] targetChainIds,
397           String pdbFile, DataSourceType sourceType, TFType tft,
398           String paeFilename, boolean doXferSettings)
399   {
400     return computeMapping(forStructureView, sequenceArray, targetChainIds,
401             pdbFile, sourceType, null, tft, paeFilename, doXferSettings);
402   }
403
404   /**
405    * create sequence structure mappings between each sequence and the given
406    * pdbFile (retrieved via the given protocol). Either constructs a mapping
407    * using NW alignment or derives one from any available SIFTS mapping data.
408    * 
409    * @param forStructureView
410    *          when true, record the mapping for use in mouseOvers
411    * 
412    * @param sequenceArray
413    *          - one or more sequences to be mapped to pdbFile
414    * @param targetChainIds
415    *          - optional chain specification for mapping each sequence to pdb
416    *          (may be nill, individual elements may be nill) - JBPNote: JAL-2693
417    *          - this should be List<List<String>>, empty lists indicate no
418    *          predefined mappings
419    * @param pdbFile
420    *          - structure data resource
421    * @param sourceType
422    *          - how to resolve data from resource
423    * @param IProgressIndicator
424    *          reference to UI component that maintains a progress bar for the
425    *          mapping operation
426    * @param tft
427    *          - specify how to interpret the temperature factor column in the
428    *          atom data
429    * @param paeFilename
430    *          - when not null, specifies a filename containing a matrix
431    *          formatted in JSON using one of the known PAE formats
432    * @param doXferSettings
433    *          - when true, transfer annotation to mapped sequences in
434    *          sequenceArray
435    * @return null or the structure data parsed as a pdb file
436    */
437   synchronized public StructureFile computeMapping(boolean forStructureView,
438           SequenceI[] sequenceArray, String[] targetChainIds,
439           String pdbFile, DataSourceType sourceType,
440           IProgressIndicator progress, TFType tft, String paeFilename,
441           boolean doXferSettings)
442   {
443     long progressSessionId = System.currentTimeMillis() * 3;
444
445     /**
446      * do we extract and transfer annotation from 3D data ?
447      */
448     // FIXME: possibly should just delete
449
450     boolean parseSecStr = processSecondaryStructure
451             && !isStructureFileProcessed(pdbFile, sequenceArray);
452
453     StructureFile pdb = null;
454     boolean isMapUsingSIFTs = SiftsSettings.isMapWithSifts();
455     try
456     {
457       // FIXME if sourceType is not null, we've lost data here
458       sourceType = AppletFormatAdapter.checkProtocol(pdbFile);
459       pdb = new JmolParser(false, pdbFile, sourceType);
460       if (paeFilename != null)
461       {
462         pdb.setPAEMatrix(paeFilename);
463       }
464       pdb.setTemperatureFactorType(tft);
465       pdb.addSettings(parseSecStr && processSecondaryStructure,
466               parseSecStr && addTempFacAnnot,
467               parseSecStr && secStructServices);
468       // save doXferSettings and reset after doParse()
469       boolean temp = pdb.getDoXferSettings();
470       pdb.setDoXferSettings(doXferSettings);
471       pdb.doParse();
472       pdb.setDoXferSettings(temp);
473       if (pdb.getId() != null && pdb.getId().trim().length() > 0
474               && DataSourceType.FILE == sourceType)
475       {
476         registerPDBFile(pdb.getId().trim(), pdbFile);
477       }
478       // if PDBId is unavailable then skip SIFTS mapping execution path
479       // TODO: JAL-3868 need to know if structure is actually from
480       // PDB (has valid PDB ID and has provenance suggesting it
481       // actually came from PDB)
482       boolean isProtein = false;
483       for (SequenceI s : sequenceArray)
484       {
485         if (s.isProtein())
486         {
487           isProtein = true;
488           break;
489         }
490       }
491       isMapUsingSIFTs = isMapUsingSIFTs && pdb.isPPDBIdAvailable()
492               && !pdb.getId().startsWith("AF-") && isProtein;
493
494     } catch (Exception ex)
495     {
496       ex.printStackTrace();
497       return null;
498     }
499     /*
500      * sifts client - non null if SIFTS mappings are to be used 
501      */
502     SiftsClient siftsClient = null;
503     try
504     {
505       if (isMapUsingSIFTs)
506       {
507         siftsClient = new SiftsClient(pdb);
508       }
509     } catch (SiftsException e)
510     {
511       isMapUsingSIFTs = false;
512       Console.error("SIFTS mapping failed", e);
513       Console.error("Falling back on Needleman & Wunsch alignment");
514       siftsClient = null;
515     }
516
517     String targetChainId;
518     for (int s = 0; s < sequenceArray.length; s++)
519     {
520       boolean infChain = true;
521       final SequenceI seq = sequenceArray[s];
522       SequenceI ds = seq;
523       while (ds.getDatasetSequence() != null)
524       {
525         ds = ds.getDatasetSequence();
526       }
527       List <PDBEntry> putativePDBe = PDBEntryUtils.selectPutativePDBe(seq,ds, pdb); 
528       
529       if (targetChainIds != null && targetChainIds[s] != null)
530       {
531         infChain = false;
532         targetChainId = targetChainIds[s];
533       }
534       else {
535         targetChainId = PDBEntryUtils.inferChainId(seq);
536       }
537
538       /*
539        * Attempt pairwise alignment of the sequence with each chain in the PDB,
540        * and remember the highest scoring chain
541        */
542       float max = -10;
543       AlignSeq maxAlignseq = null;
544       String maxChainId = " ";
545       PDBChain maxChain = null;
546       boolean first = true;
547       PDBChain idLengthChain = null;
548       for (PDBChain chain : pdb.getChains())
549       {
550         if (targetChainId.length() > 0 && !targetChainId.equals(chain.id)
551                 && !infChain)
552         {
553           continue; // don't try to map chains don't match.
554         }
555         PDBEntry putativeChain = null;
556         if (!putativePDBe.isEmpty() && (putativeChain = PDBEntryUtils
557                 .selectPutativePDBEntry(putativePDBe, chain)) == null)
558         {
559           continue;
560         }
561         // TODO: correctly determine sequence type for mixed na/peptide
562         // structures
563         final String type = chain.isNa ? AlignSeq.DNA : AlignSeq.PEP;
564         AlignSeq as = AlignSeq.doGlobalNWAlignment(seq, chain.sequence,
565                 type);
566         // TODO: JAL-4366 determinine of a crummy alignment but exact match should make this chain the one to be mapped to a 3di sequence
567         if (as.s1str.length() == as.s2str.length())
568         {
569           idLengthChain = chain;
570         }
571
572         if (first || as.maxscore > max
573                 || (as.maxscore == max && chain.id.equals(targetChainId)))
574         {
575           first = false;
576           maxChain = chain;
577           max = as.maxscore;
578           maxAlignseq = as;
579           maxChainId = chain.id;
580         }
581       }
582       if (maxChain == null)
583       {
584         continue;
585       }
586       if (sourceType == DataSourceType.PASTE)
587       {
588         pdbFile = "INLINE" + pdb.getId();
589       }
590       List<StructureMapping> seqToStrucMapping = new ArrayList<>();
591
592       List<StructureMapping> foundSiftsMappings = new ArrayList<>();
593       if (isMapUsingSIFTs && seq.isProtein())
594       {
595         if (progress != null)
596         {
597           progress.setProgressBar(
598                   MessageManager
599                           .getString("status.obtaining_mapping_with_sifts"),
600                   progressSessionId);
601         }
602         jalview.datamodel.Mapping sqmpping = maxAlignseq
603                 .getMappingFromS1(false);
604         if (targetChainId != null && !targetChainId.trim().isEmpty())
605         {
606           StructureMapping siftsMapping;
607           try
608           {
609             siftsMapping = getStructureMapping(seq, pdbFile, targetChainId,
610                     pdb, maxChain, sqmpping, maxAlignseq, siftsClient);
611             seqToStrucMapping.add(siftsMapping);
612             maxChain.makeExactMapping(siftsMapping, seq);
613             maxChain.transferRESNUMFeatures(seq, "IEA: SIFTS",
614                     pdb.getId().toLowerCase(Locale.ROOT));
615             maxChain.transferResidueAnnotation(siftsMapping, null);
616             ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
617             foundSiftsMappings.add(siftsMapping);
618
619           } catch (SiftsException e)
620           {
621             Console.error(e.getMessage());
622           }
623         }
624         else
625         {
626           for (PDBChain chain : pdb.getChains())
627           {
628             StructureMapping siftsMapping = null;
629             try
630             {
631               siftsMapping = getStructureMapping(seq, pdbFile, chain.id,
632                       pdb, chain, sqmpping, maxAlignseq, siftsClient);
633               foundSiftsMappings.add(siftsMapping);
634               chain.makeExactMapping(siftsMapping, seq);
635               chain.transferRESNUMFeatures(seq, "IEA: SIFTS",
636                       pdb.getId().toLowerCase(Locale.ROOT));// FIXME: is this
637               // "IEA:SIFTS" ?
638               chain.transferResidueAnnotation(siftsMapping, null);
639             } catch (SiftsException e)
640             {
641               jalview.bin.Console.errPrintln(e.getMessage());
642             } catch (Exception e)
643             {
644               jalview.bin.Console.errPrintln(
645                       "Unexpected exception during SIFTS mapping - falling back to NW for this sequence/structure pair");
646               jalview.bin.Console.errPrintln(e.getMessage());
647             }
648           }
649           // If sifts was successful, add mappings and return
650           if (!foundSiftsMappings.isEmpty())
651           {
652             ds.addPDBId(sqmpping.getTo().getAllPDBEntries().get(0));
653           }
654         }
655         
656         // If sifts was successful, add mappings and return
657         if (!foundSiftsMappings.isEmpty())
658         {
659           seqToStrucMapping.addAll(foundSiftsMappings);
660         }
661       }
662       if (foundSiftsMappings.isEmpty())
663       {
664         // Not doing SIFTS, or SIFTS failed for some reason.
665         
666         // first check if we should use an identity mapping
667         if (idLengthChain != null && maxAlignseq.getS2Coverage() < 0.75)
668         {
669           Console.info(
670                   "Assuming 3Dsi identity mapping between structure and sequence");
671           StructureMapping matchMapping = getIdMappings(seq, pdbFile,
672                   idLengthChain.id, idLengthChain, pdb);
673           seqToStrucMapping.add(matchMapping);
674           ds.addPDBId(idLengthChain.sequence.getAllPDBEntries().get(0));
675           Console.info("Mapping added.");
676         }
677         else
678         {
679           if (maxAlignseq.getS1Coverage()<0.15 && maxAlignseq.getS2Coverage()<0.15)
680           {
681             // skip this - the NW alignment is spurious
682             continue;
683           }
684           // Construct a needleman wunsch mapping instead.
685           if (progress != null)
686           {
687             progress.setProgressBar(
688                     MessageManager.getString(
689                             "status.obtaining_mapping_with_nw_alignment"),
690                     progressSessionId);
691           }
692           StructureMapping nwMapping = getNWMappings(seq, pdbFile,
693                   maxChainId, maxChain, pdb, maxAlignseq);
694           seqToStrucMapping.add(nwMapping);
695           ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
696         }
697       }
698       if (forStructureView)
699       {
700         for (StructureMapping sm : seqToStrucMapping)
701         {
702           addStructureMapping(sm); // not addAll!
703         }
704       }
705       if (progress != null)
706       {
707         progress.setProgressBar(null, progressSessionId);
708       }
709     }
710     return pdb;
711   }
712
713   /**
714    * check if we need to extract secondary structure from given pdbFile and
715    * transfer to sequences
716    * 
717    * @param pdbFile
718    * @param sequenceArray
719    * @return
720    */
721   private boolean isStructureFileProcessed(String pdbFile,
722           SequenceI[] sequenceArray)
723   {
724     boolean processed = false;
725     if (isPDBFileRegistered(pdbFile))
726     {
727       for (SequenceI sq : sequenceArray)
728       {
729         SequenceI ds = sq;
730         while (ds.getDatasetSequence() != null)
731         {
732           ds = ds.getDatasetSequence();
733         }
734         ;
735         if (ds.getAnnotation() != null)
736         {
737           for (AlignmentAnnotation ala : ds.getAnnotation())
738           {
739             // false if any annotation present from this structure
740             // JBPNote this fails for jmol/chimera view because the *file* is
741             // passed, not the structure data ID -
742             if (PDBfile.isCalcIdForFile(ala, findIdForPDBFile(pdbFile)))
743             {
744               processed = true;
745             }
746           }
747         }
748       }
749     }
750     return processed;
751   }
752
753   public void addStructureMapping(StructureMapping sm)
754   {
755     if (!mappings.contains(sm))
756     {
757       mappings.add(sm);
758     }
759   }
760
761   /**
762    * retrieve a mapping for seq from SIFTs using associated DBRefEntry for
763    * uniprot or PDB
764    * 
765    * @param seq
766    * @param pdbFile
767    * @param targetChainId
768    * @param pdb
769    * @param maxChain
770    * @param sqmpping
771    * @param maxAlignseq
772    * @param siftsClient
773    *          client for retrieval of SIFTS mappings for this structure
774    * @return
775    * @throws SiftsException
776    */
777   private StructureMapping getStructureMapping(SequenceI seq,
778           String pdbFile, String targetChainId, StructureFile pdb,
779           PDBChain maxChain, jalview.datamodel.Mapping sqmpping,
780           AlignSeq maxAlignseq, SiftsClient siftsClient)
781           throws SiftsException
782   {
783     StructureMapping curChainMapping = siftsClient
784             .getSiftsStructureMapping(seq, pdbFile, targetChainId);
785     try
786     {
787       PDBChain chain = pdb.findChain(targetChainId);
788       if (chain != null)
789       {
790         chain.transferResidueAnnotation(curChainMapping, null);
791       }
792     } catch (Exception e)
793     {
794       e.printStackTrace();
795     }
796     return curChainMapping;
797   }
798   
799   /**
800    * construct a mapping based on a pairwise alignment of the sequence and chain
801    * @param seq
802    * @param pdbFile
803    * @param maxChainId
804    * @param maxChain
805    * @param pdb
806    * @param maxAlignseq
807    * @return
808    */
809
810   private StructureMapping getNWMappings(SequenceI seq, String pdbFile,
811           String maxChainId, PDBChain maxChain, StructureFile pdb,
812           AlignSeq maxAlignseq)
813   {
814     final StringBuilder mappingDetails = new StringBuilder(128);
815     mappingDetails.append(NEWLINE)
816             .append("Sequence \u27f7 Structure mapping details");
817     mappingDetails.append(NEWLINE);
818     mappingDetails
819             .append("Method: inferred with Needleman & Wunsch alignment");
820     mappingDetails.append(NEWLINE).append("PDB Sequence is :")
821             .append(NEWLINE).append("Sequence = ")
822             .append(maxChain.sequence.getSequenceAsString());
823     mappingDetails.append(NEWLINE).append("No of residues = ")
824             .append(maxChain.residues.size()).append(NEWLINE)
825             .append(NEWLINE);
826     PrintStream ps = new PrintStream(System.out)
827     {
828       @Override
829       public void print(String x)
830       {
831         mappingDetails.append(x);
832       }
833
834       @Override
835       public void println()
836       {
837         mappingDetails.append(NEWLINE);
838       }
839     };
840
841     maxAlignseq.printAlignment(ps);
842
843     mappingDetails.append(NEWLINE).append("PDB start/end ");
844     mappingDetails.append(String.valueOf(maxAlignseq.seq2start))
845             .append(" ");
846     mappingDetails.append(String.valueOf(maxAlignseq.seq2end));
847     mappingDetails.append(NEWLINE).append("SEQ start/end ");
848     mappingDetails
849             .append(String
850                     .valueOf(maxAlignseq.seq1start + (seq.getStart() - 1)))
851             .append(" ");
852     mappingDetails.append(
853             String.valueOf(maxAlignseq.seq1end + (seq.getStart() - 1)));
854     mappingDetails.append(NEWLINE);
855     maxChain.makeExactMapping(maxAlignseq, seq);
856     jalview.datamodel.Mapping sqmpping = maxAlignseq
857             .getMappingFromS1(false);
858     maxChain.transferRESNUMFeatures(seq, null,
859             pdb.getId().toLowerCase(Locale.ROOT));
860
861     HashMap<Integer, int[]> mapping = new HashMap<>();
862     int resNum = -10000;
863     int index = 0;
864     char insCode = ' ';
865
866     do
867     {
868       Atom tmp = maxChain.atoms.elementAt(index);
869       if ((resNum != tmp.resNumber || insCode != tmp.insCode)
870               && tmp.alignmentMapping != -1)
871       {
872         resNum = tmp.resNumber;
873         insCode = tmp.insCode;
874         if (tmp.alignmentMapping >= -1)
875         {
876           mapping.put(tmp.alignmentMapping + 1,
877                   new int[]
878                   { tmp.resNumber, tmp.atomIndex });
879         }
880       }
881
882       index++;
883     } while (index < maxChain.atoms.size());
884
885     StructureMapping nwMapping = new StructureMapping(seq, pdbFile,
886             pdb.getId(), maxChainId, mapping, mappingDetails.toString());
887     maxChain.transferResidueAnnotation(nwMapping, sqmpping);
888     return nwMapping;
889   }
890   
891   /**
892    * construct a 1:1 mapping using given residue and sequence numbering 
893    * @param seq
894    * @param pdbFile
895    * @param identityChainId
896    * @param identityChain
897    * @param pdb
898    * @return
899    */
900
901   private StructureMapping getIdMappings(SequenceI seq, String pdbFile,
902           String identityChainId, PDBChain identityChain, StructureFile pdb)
903   {
904     final StringBuilder mappingDetails = new StringBuilder(128);
905     mappingDetails.append(NEWLINE)
906             .append("Sequence \u27f7 Structure mapping details");
907     mappingDetails.append(NEWLINE);
908     mappingDetails.append("Method: Matching length 1:1");
909     mappingDetails.append(NEWLINE).append("PDB Sequence is :")
910             .append(NEWLINE).append("Sequence = ")
911             .append(identityChain.sequence.getSequenceAsString());
912     mappingDetails.append(NEWLINE).append("No of residues = ")
913             .append(identityChain.residues.size()).append(NEWLINE)
914             .append(NEWLINE);
915
916     mappingDetails.append(NEWLINE)
917             .append("Aligned Sequence is: " + seq.getDisplayId(true));
918     mappingDetails.append(NEWLINE)
919             .append("Sequence = " + seq.getSequenceAsString());
920
921     int from = Math.max(seq.getStart(),identityChain.sequence.getStart());
922     int to = Math.min(seq.getEnd(), identityChain.sequence.getEnd());
923     jalview.datamodel.Mapping sqmpping = new jalview.datamodel.Mapping(seq,
924             new MapList(new int[]
925             { from,to },
926                     new int[]
927                     { from,to },
928                     1, 1));
929     identityChain.mapChainWith(sqmpping, seq);
930
931     identityChain.transferRESNUMFeatures(seq, null,
932             pdb.getId().toLowerCase(Locale.ROOT));
933
934     // Construct mapping
935     // TODO REFACTOR TO PDBChain as a builder
936     HashMap<Integer, int[]> mapping = new HashMap<>();
937     int resNum = -10000;
938     int index = 0;
939     char insCode = ' ';
940
941     do
942     {
943       Atom tmp = identityChain.atoms.elementAt(index);
944       if ((resNum != tmp.resNumber || insCode != tmp.insCode)
945               && tmp.alignmentMapping != -1)
946       {
947         resNum = tmp.resNumber;
948         insCode = tmp.insCode;
949         if (tmp.alignmentMapping >= -1)
950         {
951           mapping.put(tmp.alignmentMapping + 1,
952                   new int[]
953                   { tmp.resNumber, tmp.atomIndex });
954         }
955       }
956
957       index++;
958     } while (index < identityChain.atoms.size());
959
960     StructureMapping idMapping = new StructureMapping(seq, pdbFile,
961             pdb.getId(), identityChainId, mapping,
962             mappingDetails.toString());
963     identityChain.transferResidueAnnotation(idMapping, sqmpping);
964     return idMapping;
965   }
966
967   public void removeStructureViewerListener(Object svl, String[] pdbfiles)
968   {
969     listeners.removeElement(svl);
970     if (svl instanceof SequenceListener)
971     {
972       for (int i = 0; i < listeners.size(); i++)
973       {
974         if (listeners.elementAt(i) instanceof StructureListener)
975         {
976           ((StructureListener) listeners.elementAt(i))
977                   .releaseReferences(svl);
978         }
979       }
980     }
981
982     if (pdbfiles == null)
983     {
984       return;
985     }
986
987     /*
988      * Remove mappings to the closed listener's PDB files, but first check if
989      * another listener is still interested
990      */
991     List<String> pdbs = new ArrayList<>(Arrays.asList(pdbfiles));
992
993     StructureListener sl;
994     for (int i = 0; i < listeners.size(); i++)
995     {
996       if (listeners.elementAt(i) instanceof StructureListener)
997       {
998         sl = (StructureListener) listeners.elementAt(i);
999         for (String pdbfile : sl.getStructureFiles())
1000         {
1001           pdbs.remove(pdbfile);
1002         }
1003       }
1004     }
1005
1006     /*
1007      * Rebuild the mappings set, retaining only those which are for 'other' PDB
1008      * files
1009      */
1010     if (pdbs.size() > 0)
1011     {
1012       List<StructureMapping> tmp = new ArrayList<>();
1013       for (StructureMapping sm : mappings)
1014       {
1015         if (!pdbs.contains(sm.pdbfile))
1016         {
1017           tmp.add(sm);
1018         }
1019       }
1020
1021       mappings = tmp;
1022     }
1023   }
1024
1025   /**
1026    * hack to highlight a range of positions at once on any structure views
1027    * 
1028    * @param sequenceRef
1029    * @param is
1030    *          - series of int start-end ranges as positions on sequenceRef
1031    * @param i
1032    * @param object
1033    */
1034   public void highlightPositionsOn(SequenceI sequenceRef, int[][] is,
1035           Object source)
1036   {
1037     boolean hasSequenceListeners = handlingVamsasMo
1038             || !seqmappings.isEmpty();
1039     SearchResultsI results = null;
1040     ArrayList<Integer> listOfPositions = new ArrayList<Integer>();
1041     for (int[] s_e : is)
1042     {
1043       for (int p = s_e[0]; p <= s_e[1]; listOfPositions.add(p++))
1044         ;
1045     }
1046     int seqpos[] = new int[listOfPositions.size()];
1047     int i = 0;
1048     for (Integer p : listOfPositions)
1049     {
1050       seqpos[i++] = p;
1051     }
1052
1053     for (i = 0; i < listeners.size(); i++)
1054     {
1055       Object listener = listeners.elementAt(i);
1056       if (listener == source)
1057       {
1058         // TODO listener (e.g. SeqPanel) is never == source (AlignViewport)
1059         // Temporary fudge with SequenceListener.getVamsasSource()
1060         continue;
1061       }
1062       if (listener instanceof StructureListener)
1063       {
1064         highlightStructure((StructureListener) listener, sequenceRef,
1065                 seqpos);
1066       }
1067
1068     }
1069   }
1070
1071   /**
1072    * Propagate mouseover of a single position in a structure
1073    * 
1074    * @param pdbResNum
1075    * @param chain
1076    * @param pdbfile
1077    * @return
1078    */
1079   public String mouseOverStructure(int pdbResNum, String chain,
1080           String pdbfile)
1081   {
1082     AtomSpec atomSpec = new AtomSpec(pdbfile, chain, pdbResNum, 0);
1083     List<AtomSpec> atoms = Collections.singletonList(atomSpec);
1084     return mouseOverStructure(atoms);
1085   }
1086
1087   /**
1088    * Propagate mouseover or selection of multiple positions in a structure
1089    * 
1090    * @param atoms
1091    */
1092   public String mouseOverStructure(List<AtomSpec> atoms)
1093   {
1094     if (listeners == null)
1095     {
1096       // old or prematurely sent event
1097       return null;
1098     }
1099     boolean hasSequenceListener = false;
1100     for (int i = 0; i < listeners.size(); i++)
1101     {
1102       if (listeners.elementAt(i) instanceof SequenceListener)
1103       {
1104         hasSequenceListener = true;
1105       }
1106     }
1107     if (!hasSequenceListener)
1108     {
1109       return null;
1110     }
1111
1112     SearchResultsI results = findAlignmentPositionsForStructurePositions(
1113             atoms);
1114     String result = null;
1115     for (Object li : listeners)
1116     {
1117       if (li instanceof SequenceListener)
1118       {
1119         String s = ((SequenceListener) li).highlightSequence(results);
1120         if (s != null)
1121         {
1122           result = s;
1123         }
1124       }
1125     }
1126     return result;
1127   }
1128
1129   /**
1130    * Constructs a SearchResults object holding regions (if any) in the Jalview
1131    * alignment which have a mapping to the structure viewer positions in the
1132    * supplied list
1133    * 
1134    * @param atoms
1135    * @return
1136    */
1137   public SearchResultsI findAlignmentPositionsForStructurePositions(
1138           List<AtomSpec> atoms)
1139   {
1140     SearchResultsI results = new SearchResults();
1141     for (AtomSpec atom : atoms)
1142     {
1143       SequenceI lastseq = null;
1144       int lastipos = -1;
1145       for (StructureMapping sm : mappings)
1146       {
1147         if (sm.pdbfile.equals(atom.getPdbFile())
1148                 && sm.pdbchain.equals(atom.getChain()))
1149         {
1150           int indexpos = sm.getSeqPos(atom.getPdbResNum());
1151           if (lastipos != indexpos || lastseq != sm.sequence)
1152           {
1153             results.appendResult(sm.sequence, indexpos, indexpos);
1154             lastipos = indexpos;
1155             lastseq = sm.sequence;
1156             // construct highlighted sequence list
1157             for (AlignedCodonFrame acf : seqmappings)
1158             {
1159               acf.markMappedRegion(sm.sequence, indexpos, results);
1160             }
1161           }
1162         }
1163       }
1164     }
1165     return results;
1166   }
1167
1168   /**
1169    * highlight regions associated with a position (indexpos) in seq
1170    * 
1171    * @param seq
1172    *          the sequence that the mouse over occurred on
1173    * @param indexpos
1174    *          the absolute position being mouseovered in seq (0 to seq.length())
1175    * @param seqPos
1176    *          the sequence position (if -1, seq.findPosition is called to
1177    *          resolve the residue number)
1178    */
1179   public void mouseOverSequence(SequenceI seq, int indexpos, int seqPos,
1180           VamsasSource source)
1181   {
1182     boolean hasSequenceListeners = handlingVamsasMo
1183             || !seqmappings.isEmpty();
1184     SearchResultsI results = null;
1185     if (seqPos == -1)
1186     {
1187       seqPos = seq.findPosition(indexpos);
1188     }
1189     for (int i = 0; i < listeners.size(); i++)
1190     {
1191       Object listener = listeners.elementAt(i);
1192       if (listener == source)
1193       {
1194         // TODO listener (e.g. SeqPanel) is never == source (AlignViewport)
1195         // Temporary fudge with SequenceListener.getVamsasSource()
1196         continue;
1197       }
1198       if (listener instanceof StructureListener)
1199       {
1200         highlightStructure((StructureListener) listener, seq, seqPos);
1201       }
1202       else
1203       {
1204         if (listener instanceof SequenceListener)
1205         {
1206           final SequenceListener seqListener = (SequenceListener) listener;
1207           if (hasSequenceListeners
1208                   && seqListener.getVamsasSource() != source)
1209           {
1210             if (relaySeqMappings)
1211             {
1212               if (results == null)
1213               {
1214                 results = MappingUtils.buildSearchResults(seq, seqPos,
1215                         seqmappings);
1216               }
1217               if (handlingVamsasMo)
1218               {
1219                 results.addResult(seq, seqPos, seqPos);
1220
1221               }
1222               if (!results.isEmpty())
1223               {
1224                 seqListener.highlightSequence(results);
1225               }
1226             }
1227           }
1228         }
1229         else if (listener instanceof VamsasListener && !handlingVamsasMo)
1230         {
1231           ((VamsasListener) listener).mouseOverSequence(seq, indexpos,
1232                   source);
1233         }
1234         else if (listener instanceof SecondaryStructureListener)
1235         {
1236           ((SecondaryStructureListener) listener).mouseOverSequence(seq,
1237                   indexpos, seqPos);
1238         }
1239       }
1240     }
1241   }
1242
1243   /**
1244    * Send suitable messages to a StructureListener to highlight atoms
1245    * corresponding to the given sequence position(s)
1246    * 
1247    * @param sl
1248    * @param seq
1249    * @param positions
1250    */
1251   public void highlightStructure(StructureListener sl, SequenceI seq,
1252           int... positions)
1253   {
1254     if (!sl.isListeningFor(seq))
1255     {
1256       return;
1257     }
1258     int atomNo;
1259     List<AtomSpec> atoms = new ArrayList<>();
1260     for (StructureMapping sm : mappings)
1261     {
1262       if (sm.sequence == seq || sm.sequence == seq.getDatasetSequence()
1263               || (sm.sequence.getDatasetSequence() != null && sm.sequence
1264                       .getDatasetSequence() == seq.getDatasetSequence()))
1265       {
1266         for (int index : positions)
1267         {
1268           atomNo = sm.getAtomNum(index);
1269
1270           if (atomNo > 0)
1271           {
1272             atoms.add(new AtomSpec(sm.pdbfile, sm.pdbchain,
1273                     sm.getPDBResNum(index), atomNo));
1274           }
1275         }
1276       }
1277     }
1278     sl.highlightAtoms(atoms);
1279   }
1280
1281   public void highlightStructureRegionsFor(StructureListener sl,
1282           SequenceI[] seqs, int... columns)
1283   {
1284     List<SequenceI> to_highlight = new ArrayList<SequenceI>();
1285     for (SequenceI seq : seqs)
1286     {
1287       if (sl.isListeningFor(seq))
1288       {
1289         to_highlight.add(seq);
1290       }
1291     }
1292     if (to_highlight.size() == 0)
1293     {
1294       return;
1295     }
1296     List<AtomSpec> atoms = new ArrayList<>();
1297     for (SequenceI seq : to_highlight)
1298     {
1299       int atomNo;
1300       for (StructureMapping sm : mappings)
1301       {
1302         if (sm.sequence == seq || sm.sequence == seq.getDatasetSequence()
1303                 || (sm.sequence.getDatasetSequence() != null && sm.sequence
1304                         .getDatasetSequence() == seq.getDatasetSequence()))
1305         {
1306
1307           for (int i = 0; i < columns.length; i += 2)
1308           {
1309             ContiguousI positions = seq.findPositions(columns[i] + 1,
1310                     columns[i + 1] + 1);
1311             if (positions == null)
1312             {
1313               continue;
1314             }
1315             for (int index = positions.getBegin(); index <= positions
1316                     .getEnd(); index++)
1317             {
1318
1319               atomNo = sm.getAtomNum(index);
1320
1321               if (atomNo > 0)
1322               {
1323                 atoms.add(new AtomSpec(sm.pdbfile, sm.pdbchain,
1324                         sm.getPDBResNum(index), atomNo));
1325               }
1326             }
1327           }
1328         }
1329       }
1330       if (atoms.size() > 0)
1331       {
1332         sl.highlightAtoms(atoms);
1333       }
1334     }
1335   }
1336
1337   /**
1338    * true if a mouse over event from an external (ie Vamsas) source is being
1339    * handled
1340    */
1341   boolean handlingVamsasMo = false;
1342
1343   long lastmsg = 0;
1344
1345   /**
1346    * as mouseOverSequence but only route event to SequenceListeners
1347    * 
1348    * @param sequenceI
1349    * @param position
1350    *          in an alignment sequence
1351    */
1352   public void mouseOverVamsasSequence(SequenceI sequenceI, int position,
1353           VamsasSource source)
1354   {
1355     handlingVamsasMo = true;
1356     long msg = sequenceI.hashCode() * (1 + position);
1357     if (lastmsg != msg)
1358     {
1359       lastmsg = msg;
1360       mouseOverSequence(sequenceI, position, -1, source);
1361     }
1362     handlingVamsasMo = false;
1363   }
1364
1365   public Annotation[] colourSequenceFromStructure(SequenceI seq,
1366           String pdbid)
1367   {
1368     return null;
1369     // THIS WILL NOT BE AVAILABLE IN JALVIEW 2.3,
1370     // UNTIL THE COLOUR BY ANNOTATION IS REWORKED
1371     /*
1372      * Annotation [] annotations = new Annotation[seq.getLength()];
1373      * 
1374      * StructureListener sl; int atomNo = 0; for (int i = 0; i <
1375      * listeners.size(); i++) { if (listeners.elementAt(i) instanceof
1376      * StructureListener) { sl = (StructureListener) listeners.elementAt(i);
1377      * 
1378      * for (int j = 0; j < mappings.length; j++) {
1379      * 
1380      * if (mappings[j].sequence == seq && mappings[j].getPdbId().equals(pdbid)
1381      * && mappings[j].pdbfile.equals(sl.getPdbFile())) {
1382      * jalview.bin.Console.outPrintln(pdbid+" "+mappings[j].getPdbId() +"
1383      * "+mappings[j].pdbfile);
1384      * 
1385      * java.awt.Color col; for(int index=0; index<seq.getLength(); index++) {
1386      * if(jalview.util.Comparison.isGap(seq.getCharAt(index))) continue;
1387      * 
1388      * atomNo = mappings[j].getAtomNum(seq.findPosition(index)); col =
1389      * java.awt.Color.white; if (atomNo > 0) { col = sl.getColour(atomNo,
1390      * mappings[j].getPDBResNum(index), mappings[j].pdbchain,
1391      * mappings[j].pdbfile); }
1392      * 
1393      * annotations[index] = new Annotation("X",null,' ',0,col); } return
1394      * annotations; } } } }
1395      * 
1396      * return annotations;
1397      */
1398   }
1399
1400   public void structureSelectionChanged()
1401   {
1402   }
1403
1404   public void sequenceSelectionChanged()
1405   {
1406   }
1407
1408   public void sequenceColoursChanged(Object source)
1409   {
1410     StructureListener sl;
1411     for (int i = 0; i < listeners.size(); i++)
1412     {
1413       if (listeners.elementAt(i) instanceof StructureListener)
1414       {
1415         sl = (StructureListener) listeners.elementAt(i);
1416         sl.updateColours(source);
1417       }
1418     }
1419   }
1420
1421   public StructureMapping[] getMapping(String pdbfile)
1422   {
1423     List<StructureMapping> tmp = new ArrayList<>();
1424     for (StructureMapping sm : mappings)
1425     {
1426       if (sm.pdbfile.equals(pdbfile))
1427       {
1428         tmp.add(sm);
1429       }
1430     }
1431     return tmp.toArray(new StructureMapping[tmp.size()]);
1432   }
1433
1434   /**
1435    * Returns a readable description of all mappings for the given pdbfile to any
1436    * of the given sequences
1437    * 
1438    * @param pdbfile
1439    * @param seqs
1440    * @return
1441    */
1442   public String printMappings(String pdbfile, List<SequenceI> seqs)
1443   {
1444     if (pdbfile == null || seqs == null || seqs.isEmpty())
1445     {
1446       return "";
1447     }
1448
1449     StringBuilder sb = new StringBuilder(64);
1450     for (StructureMapping sm : mappings)
1451     {
1452       if (Platform.pathEquals(sm.pdbfile, pdbfile)
1453               && seqs.contains(sm.sequence))
1454       {
1455         sb.append(sm.mappingDetails);
1456         sb.append(NEWLINE);
1457         // separator makes it easier to read multiple mappings
1458         sb.append("=====================");
1459         sb.append(NEWLINE);
1460       }
1461     }
1462     sb.append(NEWLINE);
1463
1464     return sb.toString();
1465   }
1466
1467   /**
1468    * Remove the given mapping
1469    * 
1470    * @param acf
1471    */
1472   public void deregisterMapping(AlignedCodonFrame acf)
1473   {
1474     if (acf != null)
1475     {
1476       boolean removed = seqmappings.remove(acf);
1477       if (removed && seqmappings.isEmpty())
1478       { // debug
1479         jalview.bin.Console.outPrintln("All mappings removed");
1480       }
1481     }
1482   }
1483
1484   /**
1485    * Add each of the given codonFrames to the stored set, if not aready present.
1486    * 
1487    * @param mappings
1488    */
1489   public void registerMappings(List<AlignedCodonFrame> mappings)
1490   {
1491     if (mappings != null)
1492     {
1493       for (AlignedCodonFrame acf : mappings)
1494       {
1495         registerMapping(acf);
1496       }
1497     }
1498   }
1499
1500   /**
1501    * Add the given mapping to the stored set, unless already stored.
1502    */
1503   public void registerMapping(AlignedCodonFrame acf)
1504   {
1505     if (acf != null)
1506     {
1507       if (!seqmappings.contains(acf))
1508       {
1509         seqmappings.add(acf);
1510       }
1511     }
1512   }
1513
1514   /**
1515    * Resets this object to its initial state by removing all registered
1516    * listeners, codon mappings, PDB file mappings
1517    */
1518   public void resetAll()
1519   {
1520     if (mappings != null)
1521     {
1522       mappings.clear();
1523     }
1524     if (seqmappings != null)
1525     {
1526       seqmappings.clear();
1527     }
1528     if (sel_listeners != null)
1529     {
1530       sel_listeners.clear();
1531     }
1532     if (listeners != null)
1533     {
1534       listeners.clear();
1535     }
1536     if (commandListeners != null)
1537     {
1538       commandListeners.clear();
1539     }
1540     if (view_listeners != null)
1541     {
1542       view_listeners.clear();
1543     }
1544     if (pdbFileNameId != null)
1545     {
1546       pdbFileNameId.clear();
1547     }
1548     if (pdbIdFileName != null)
1549     {
1550       pdbIdFileName.clear();
1551     }
1552   }
1553
1554   public void addSelectionListener(SelectionListener selecter)
1555   {
1556     if (!sel_listeners.contains(selecter))
1557     {
1558       sel_listeners.add(selecter);
1559     }
1560   }
1561
1562   public void removeSelectionListener(SelectionListener toremove)
1563   {
1564     if (sel_listeners.contains(toremove))
1565     {
1566       sel_listeners.remove(toremove);
1567     }
1568   }
1569
1570   public synchronized void sendSelection(
1571           jalview.datamodel.SequenceGroup selection,
1572           jalview.datamodel.ColumnSelection colsel, HiddenColumns hidden,
1573           SelectionSource source)
1574   {
1575     for (SelectionListener slis : sel_listeners)
1576     {
1577       if (slis != source)
1578       {
1579         slis.selection(selection, colsel, hidden, source);
1580       }
1581     }
1582   }
1583
1584   Vector<AlignmentViewPanelListener> view_listeners = new Vector<>();
1585
1586   public synchronized void sendViewPosition(
1587           jalview.api.AlignmentViewPanel source, int startRes, int endRes,
1588           int startSeq, int endSeq)
1589   {
1590
1591     if (view_listeners != null && view_listeners.size() > 0)
1592     {
1593       Enumeration<AlignmentViewPanelListener> listeners = view_listeners
1594               .elements();
1595       while (listeners.hasMoreElements())
1596       {
1597         AlignmentViewPanelListener slis = listeners.nextElement();
1598         if (slis != source)
1599         {
1600           slis.viewPosition(startRes, endRes, startSeq, endSeq, source);
1601         }
1602         ;
1603       }
1604     }
1605   }
1606
1607   /**
1608    * release all references associated with this manager provider
1609    * 
1610    * @param jalviewLite
1611    */
1612   public static void release(StructureSelectionManagerProvider jalviewLite)
1613   {
1614     // synchronized (instances)
1615     {
1616       if (instances == null)
1617       {
1618         return;
1619       }
1620       StructureSelectionManager mnger = (instances.get(jalviewLite));
1621       if (mnger != null)
1622       {
1623         instances.remove(jalviewLite);
1624         try
1625         {
1626           /* bsoares 2019-03-20 finalize deprecated, no apparent external
1627            * resources to close
1628            */
1629           // mnger.finalize();
1630         } catch (Throwable x)
1631         {
1632         }
1633       }
1634     }
1635   }
1636
1637   public void registerPDBEntry(PDBEntry pdbentry)
1638   {
1639     if (pdbentry.getFile() != null
1640             && pdbentry.getFile().trim().length() > 0)
1641     {
1642       registerPDBFile(pdbentry.getId(), pdbentry.getFile());
1643     }
1644   }
1645
1646   public void addCommandListener(CommandListener cl)
1647   {
1648     if (!commandListeners.contains(cl))
1649     {
1650       commandListeners.add(cl);
1651     }
1652   }
1653
1654   public boolean hasCommandListener(CommandListener cl)
1655   {
1656     return this.commandListeners.contains(cl);
1657   }
1658
1659   public boolean removeCommandListener(CommandListener l)
1660   {
1661     return commandListeners.remove(l);
1662   }
1663
1664   /**
1665    * Forward a command to any command listeners (except for the command's
1666    * source).
1667    * 
1668    * @param command
1669    *          the command to be broadcast (in its form after being performed)
1670    * @param undo
1671    *          if true, the command was being 'undone'
1672    * @param source
1673    */
1674   public void commandPerformed(CommandI command, boolean undo,
1675           VamsasSource source)
1676   {
1677     for (CommandListener listener : commandListeners)
1678     {
1679       listener.mirrorCommand(command, undo, this, source);
1680     }
1681   }
1682
1683   /**
1684    * Returns a new CommandI representing the given command as mapped to the
1685    * given sequences. If no mapping could be made, or the command is not of a
1686    * mappable kind, returns null.
1687    * 
1688    * @param command
1689    * @param undo
1690    * @param mapTo
1691    * @param gapChar
1692    * @return
1693    */
1694   public CommandI mapCommand(CommandI command, boolean undo,
1695           final AlignmentI mapTo, char gapChar)
1696   {
1697     if (command instanceof EditCommand)
1698     {
1699       return MappingUtils.mapEditCommand((EditCommand) command, undo, mapTo,
1700               gapChar, seqmappings);
1701     }
1702     else if (command instanceof OrderCommand)
1703     {
1704       return MappingUtils.mapOrderCommand((OrderCommand) command, undo,
1705               mapTo, seqmappings);
1706     }
1707     return null;
1708   }
1709
1710   public List<AlignedCodonFrame> getSequenceMappings()
1711   {
1712     return seqmappings;
1713   }
1714
1715   /**
1716    * quick and dirty route to just highlight all structure positions for a range
1717    * of columns
1718    * 
1719    * @param sequencesArray
1720    * @param is
1721    *          start-end columns on sequencesArray
1722    * @param source
1723    *          origin parent AlignmentPanel
1724    */
1725   public void highlightPositionsOnMany(SequenceI[] sequencesArray, int[] is,
1726           Object source)
1727   {
1728     for (int i = 0; i < listeners.size(); i++)
1729     {
1730       Object listener = listeners.elementAt(i);
1731       if (listener == source)
1732       {
1733         // TODO listener (e.g. SeqPanel) is never == source (AlignViewport)
1734         // Temporary fudge with SequenceListener.getVamsasSource()
1735         continue;
1736       }
1737       if (listener instanceof StructureListener)
1738       {
1739         highlightStructureRegionsFor((StructureListener) listener,
1740                 sequencesArray, is);
1741       }
1742     }
1743   }
1744
1745   public Map<String, String> getPdbFileNameIdMap()
1746   {
1747     return pdbFileNameId;
1748   }
1749
1750   public Map<String, String> getPdbIdFileNameMap()
1751   {
1752     return pdbIdFileName;
1753   }
1754
1755 }