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