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