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