2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
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.
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.
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.
21 package jalview.structure;
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;
33 import java.util.Vector;
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.MapList;
58 import jalview.util.MappingUtils;
59 import jalview.util.MessageManager;
60 import jalview.util.Platform;
61 import jalview.ws.sifts.SiftsClient;
62 import jalview.ws.sifts.SiftsException;
63 import jalview.ws.sifts.SiftsSettings;
65 import mc_view.PDBChain;
66 import mc_view.PDBfile;
68 public class StructureSelectionManager
70 public final static String NEWLINE = System.lineSeparator();
72 static IdentityHashMap<StructureSelectionManagerProvider, StructureSelectionManager> instances;
74 private List<StructureMapping> mappings = new ArrayList<>();
76 private boolean processSecondaryStructure = false;
78 private boolean secStructServices = false;
80 private boolean addTempFacAnnot = false;
83 * Set of any registered mappings between (dataset) sequences.
85 private List<AlignedCodonFrame> seqmappings = new ArrayList<>();
87 private List<CommandListener> commandListeners = new ArrayList<>();
89 private List<SelectionListener> sel_listeners = new ArrayList<>();
92 * @return true if will try to use external services for processing secondary
95 public boolean isSecStructServices()
97 return secStructServices;
101 * control use of external services for processing secondary structure
103 * @param secStructServices
105 public void setSecStructServices(boolean secStructServices)
107 this.secStructServices = secStructServices;
111 * flag controlling addition of any kind of structural annotation
113 * @return true if temperature factor annotation will be added
115 public boolean isAddTempFacAnnot()
117 return addTempFacAnnot;
121 * set flag controlling addition of structural annotation
123 * @param addTempFacAnnot
125 public void setAddTempFacAnnot(boolean addTempFacAnnot)
127 this.addTempFacAnnot = addTempFacAnnot;
132 * @return if true, the structure manager will attempt to add secondary
133 * structure lines for unannotated sequences
136 public boolean isProcessSecondaryStructure()
138 return processSecondaryStructure;
142 * Control whether structure manager will try to annotate mapped sequences
143 * with secondary structure from PDB data.
147 public void setProcessSecondaryStructure(boolean enable)
149 processSecondaryStructure = enable;
153 * debug function - write all mappings to stdout
155 public void reportMapping()
157 if (mappings.isEmpty())
160 .errPrintln("reportMapping: No PDB/Sequence mappings.");
164 jalview.bin.Console.errPrintln(
165 "reportMapping: There are " + mappings.size() + " mappings.");
167 for (StructureMapping sm : mappings)
170 .errPrintln("mapping " + i++ + " : " + sm.pdbfile);
176 * map between the PDB IDs (or structure identifiers) used by Jalview and the
177 * absolute filenames for PDB data that corresponds to it
179 Map<String, String> pdbIdFileName = new HashMap<>();
181 Map<String, String> pdbFileNameId = new HashMap<>();
183 public void registerPDBFile(String idForFile, String absoluteFile)
185 pdbIdFileName.put(idForFile, absoluteFile);
186 pdbFileNameId.put(absoluteFile, idForFile);
189 public String findIdForPDBFile(String idOrFile)
191 String id = pdbFileNameId.get(idOrFile);
195 public String findFileForPDBId(String idOrFile)
197 String id = pdbIdFileName.get(idOrFile);
201 public boolean isPDBFileRegistered(String idOrFile)
203 return pdbFileNameId.containsKey(idOrFile)
204 || pdbIdFileName.containsKey(idOrFile);
207 private static StructureSelectionManager nullProvider = null;
209 public static StructureSelectionManager getStructureSelectionManager(
210 StructureSelectionManagerProvider context)
214 if (nullProvider == null)
216 if (instances != null)
218 throw new Error(MessageManager.getString(
219 "error.implementation_error_structure_selection_manager_null"),
220 new NullPointerException(MessageManager
221 .getString("exception.ssm_context_is_null")));
225 nullProvider = new StructureSelectionManager();
230 if (instances == null)
232 instances = new java.util.IdentityHashMap<>();
234 StructureSelectionManager instance = instances.get(context);
235 if (instance == null)
237 if (nullProvider != null)
239 instance = nullProvider;
243 instance = new StructureSelectionManager();
245 instances.put(context, instance);
251 * flag controlling whether SeqMappings are relayed from received sequence
252 * mouse over events to other sequences
254 boolean relaySeqMappings = true;
257 * Enable or disable relay of seqMapping events to other sequences. You might
258 * want to do this if there are many sequence mappings and the host computer
263 public void setRelaySeqMappings(boolean relay)
265 relaySeqMappings = relay;
269 * get the state of the relay seqMappings flag.
271 * @return true if sequence mouse overs are being relayed to other mapped
274 public boolean isRelaySeqMappingsEnabled()
276 return relaySeqMappings;
279 Vector listeners = new Vector();
282 * register a listener for alignment sequence mouseover events
286 public void addStructureViewerListener(Object svl)
288 if (!listeners.contains(svl))
290 listeners.addElement(svl);
295 * Returns the filename the PDB id is already mapped to if known, or null if
301 public String alreadyMappedToFile(String pdbid)
303 for (StructureMapping sm : mappings)
305 if (sm.getPdbId().equalsIgnoreCase(pdbid))
314 * Import structure data and register a structure mapping for broadcasting
315 * colouring, mouseovers and selection events (convenience wrapper).
318 * - one or more sequences to be mapped to pdbFile
319 * @param targetChains
320 * - optional chain specification for mapping each sequence to pdb
321 * (may be nill, individual elements may be nill)
323 * - structure data resource
325 * - how to resolve data from resource
326 * @return null or the structure data parsed as a pdb file
328 synchronized public StructureFile setMapping(SequenceI[] sequence,
329 String[] targetChains, String pdbFile, DataSourceType protocol,
330 IProgressIndicator progress)
332 return computeMapping(true, sequence, targetChains, pdbFile, protocol,
333 progress, null, null, true);
337 * Import a single structure file and register sequence structure mappings for
338 * broadcasting colouring, mouseovers and selection events (convenience
341 * @param forStructureView
342 * when true, record the mapping for use in mouseOvers
344 * - one or more sequences to be mapped to pdbFile
345 * @param targetChains
346 * - optional chain specification for mapping each sequence to pdb
347 * (may be nill, individual elements may be nill)
349 * - structure data resource
351 * - how to resolve data from resource
352 * @return null or the structure data parsed as a pdb file
354 synchronized public StructureFile setMapping(boolean forStructureView,
355 SequenceI[] sequenceArray, String[] targetChainIds,
356 String pdbFile, DataSourceType sourceType, TFType tft,
359 return setMapping(forStructureView, sequenceArray, targetChainIds,
360 pdbFile, sourceType, tft, paeFilename, true);
364 * create sequence structure mappings between each sequence and the given
365 * pdbFile (retrieved via the given protocol). Either constructs a mapping
366 * using NW alignment or derives one from any available SIFTS mapping data.
368 * @param forStructureView
369 * when true, record the mapping for use in mouseOvers
371 * @param sequenceArray
372 * - one or more sequences to be mapped to pdbFile
373 * @param targetChainIds
374 * - optional chain specification for mapping each sequence to pdb
375 * (may be nill, individual elements may be nill) - JBPNote: JAL-2693
376 * - this should be List<List<String>>, empty lists indicate no
377 * predefined mappings
379 * - structure data resource
381 * - how to resolve data from resource
383 * - specify how to interpret the temperature factor column in the
386 * - when not null, specifies a filename containing a matrix
387 * formatted in JSON using one of the known PAE formats
388 * @param doXferSettings
389 * - when true, transfer annotation to mapped sequences in
391 * @return null or the structure data parsed as a pdb file
393 synchronized public StructureFile setMapping(boolean forStructureView,
394 SequenceI[] sequenceArray, String[] targetChainIds,
395 String pdbFile, DataSourceType sourceType, TFType tft,
396 String paeFilename, boolean doXferSettings)
398 return computeMapping(forStructureView, sequenceArray, targetChainIds,
399 pdbFile, sourceType, null, tft, paeFilename, doXferSettings);
403 * create sequence structure mappings between each sequence and the given
404 * pdbFile (retrieved via the given protocol). Either constructs a mapping
405 * using NW alignment or derives one from any available SIFTS mapping data.
407 * @param forStructureView
408 * when true, record the mapping for use in mouseOvers
410 * @param sequenceArray
411 * - one or more sequences to be mapped to pdbFile
412 * @param targetChainIds
413 * - optional chain specification for mapping each sequence to pdb
414 * (may be nill, individual elements may be nill) - JBPNote: JAL-2693
415 * - this should be List<List<String>>, empty lists indicate no
416 * predefined mappings
418 * - structure data resource
420 * - how to resolve data from resource
421 * @param IProgressIndicator
422 * reference to UI component that maintains a progress bar for the
425 * - specify how to interpret the temperature factor column in the
428 * - when not null, specifies a filename containing a matrix
429 * formatted in JSON using one of the known PAE formats
430 * @param doXferSettings
431 * - when true, transfer annotation to mapped sequences in
433 * @return null or the structure data parsed as a pdb file
435 synchronized public StructureFile computeMapping(boolean forStructureView,
436 SequenceI[] sequenceArray, String[] targetChainIds,
437 String pdbFile, DataSourceType sourceType,
438 IProgressIndicator progress, TFType tft, String paeFilename,
439 boolean doXferSettings)
441 long progressSessionId = System.currentTimeMillis() * 3;
444 * do we extract and transfer annotation from 3D data ?
446 // FIXME: possibly should just delete
448 boolean parseSecStr = processSecondaryStructure
449 && !isStructureFileProcessed(pdbFile, sequenceArray);
451 StructureFile pdb = null;
452 boolean isMapUsingSIFTs = SiftsSettings.isMapWithSifts();
455 // FIXME if sourceType is not null, we've lost data here
456 sourceType = AppletFormatAdapter.checkProtocol(pdbFile);
457 pdb = new JmolParser(false, pdbFile, sourceType);
458 if (paeFilename != null)
460 pdb.setPAEMatrix(paeFilename);
462 pdb.setTemperatureFactorType(tft);
463 pdb.addSettings(parseSecStr && processSecondaryStructure,
464 parseSecStr && addTempFacAnnot,
465 parseSecStr && secStructServices);
466 // save doXferSettings and reset after doParse()
467 boolean temp = pdb.getDoXferSettings();
468 pdb.setDoXferSettings(doXferSettings);
470 pdb.setDoXferSettings(temp);
471 if (pdb.getId() != null && pdb.getId().trim().length() > 0
472 && DataSourceType.FILE == sourceType)
474 registerPDBFile(pdb.getId().trim(), pdbFile);
476 // if PDBId is unavailable then skip SIFTS mapping execution path
477 // TODO: JAL-3868 need to know if structure is actually from
478 // PDB (has valid PDB ID and has provenance suggesting it
479 // actually came from PDB)
480 boolean isProtein = false;
481 for (SequenceI s : sequenceArray)
489 isMapUsingSIFTs = isMapUsingSIFTs && pdb.isPPDBIdAvailable()
490 && !pdb.getId().startsWith("AF-") && isProtein;
492 } catch (Exception ex)
494 ex.printStackTrace();
498 * sifts client - non null if SIFTS mappings are to be used
500 SiftsClient siftsClient = null;
505 siftsClient = new SiftsClient(pdb);
507 } catch (SiftsException e)
509 isMapUsingSIFTs = false;
510 Console.error("SIFTS mapping failed", e);
511 Console.error("Falling back on Needleman & Wunsch alignment");
515 String targetChainId;
516 for (int s = 0; s < sequenceArray.length; s++)
518 boolean infChain = true;
519 final SequenceI seq = sequenceArray[s];
521 while (ds.getDatasetSequence() != null)
523 ds = ds.getDatasetSequence();
526 if (targetChainIds != null && targetChainIds[s] != null)
529 targetChainId = targetChainIds[s];
531 else if (seq.getName().indexOf("|") > -1)
533 targetChainId = seq.getName()
534 .substring(seq.getName().lastIndexOf("|") + 1);
535 if (targetChainId.length() > 1)
537 if (targetChainId.trim().length() == 0)
543 // not a valid chain identifier
554 * Attempt pairwise alignment of the sequence with each chain in the PDB,
555 * and remember the highest scoring chain
558 AlignSeq maxAlignseq = null;
559 String maxChainId = " ";
560 PDBChain maxChain = null;
561 boolean first = true;
562 PDBChain idLengthChain = null;
563 for (PDBChain chain : pdb.getChains())
565 if (targetChainId.length() > 0 && !targetChainId.equals(chain.id)
568 continue; // don't try to map chains don't match.
570 // TODO: correctly determine sequence type for mixed na/peptide
572 final String type = chain.isNa ? AlignSeq.DNA : AlignSeq.PEP;
573 AlignSeq as = AlignSeq.doGlobalNWAlignment(seq, chain.sequence,
575 // TODO: JAL-4366 determinine of a crummy alignment but exact match should make this chain the one to be mapped to a 3di sequence
576 if (as.s1str.length() == as.s2str.length())
578 idLengthChain = chain;
581 if (first || as.maxscore > max
582 || (as.maxscore == max && chain.id.equals(targetChainId)))
588 maxChainId = chain.id;
591 if (maxChain == null)
595 if (sourceType == DataSourceType.PASTE)
597 pdbFile = "INLINE" + pdb.getId();
599 List<StructureMapping> seqToStrucMapping = new ArrayList<>();
601 List<StructureMapping> foundSiftsMappings = new ArrayList<>();
602 if (isMapUsingSIFTs && seq.isProtein())
604 if (progress != null)
606 progress.setProgressBar(
608 .getString("status.obtaining_mapping_with_sifts"),
611 jalview.datamodel.Mapping sqmpping = maxAlignseq
612 .getMappingFromS1(false);
613 if (targetChainId != null && !targetChainId.trim().isEmpty())
615 StructureMapping siftsMapping;
618 siftsMapping = getStructureMapping(seq, pdbFile, targetChainId,
619 pdb, maxChain, sqmpping, maxAlignseq, siftsClient);
620 seqToStrucMapping.add(siftsMapping);
621 maxChain.makeExactMapping(siftsMapping, seq);
622 maxChain.transferRESNUMFeatures(seq, "IEA: SIFTS",
623 pdb.getId().toLowerCase(Locale.ROOT));
624 maxChain.transferResidueAnnotation(siftsMapping, null);
625 ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
626 foundSiftsMappings.add(siftsMapping);
628 } catch (SiftsException e)
630 Console.error(e.getMessage());
635 for (PDBChain chain : pdb.getChains())
637 StructureMapping siftsMapping = null;
640 siftsMapping = getStructureMapping(seq, pdbFile, chain.id,
641 pdb, chain, sqmpping, maxAlignseq, siftsClient);
642 foundSiftsMappings.add(siftsMapping);
643 chain.makeExactMapping(siftsMapping, seq);
644 chain.transferRESNUMFeatures(seq, "IEA: SIFTS",
645 pdb.getId().toLowerCase(Locale.ROOT));// FIXME: is this
647 chain.transferResidueAnnotation(siftsMapping, null);
648 } catch (SiftsException e)
650 jalview.bin.Console.errPrintln(e.getMessage());
651 } catch (Exception e)
653 jalview.bin.Console.errPrintln(
654 "Unexpected exception during SIFTS mapping - falling back to NW for this sequence/structure pair");
655 jalview.bin.Console.errPrintln(e.getMessage());
658 // If sifts was successful, add mappings and return
659 if (!foundSiftsMappings.isEmpty())
661 ds.addPDBId(sqmpping.getTo().getAllPDBEntries().get(0));
665 // If sifts was successful, add mappings and return
666 if (!foundSiftsMappings.isEmpty())
668 seqToStrucMapping.addAll(foundSiftsMappings);
671 if (foundSiftsMappings.isEmpty())
673 // Not doing SIFTS, or SIFTS failed for some reason.
675 // first check if we should use an identity mapping
676 if (idLengthChain != null && maxAlignseq.getS2Coverage() < 0.5)
679 "Assuming 3Dsi identity mapping between structure and sequence");
680 StructureMapping matchMapping = getIdMappings(seq, pdbFile,
681 idLengthChain.id, idLengthChain, pdb);
682 seqToStrucMapping.add(matchMapping);
683 ds.addPDBId(idLengthChain.sequence.getAllPDBEntries().get(0));
684 Console.info("Mapping added.");
688 // Construct a needleman wunsch mapping instead.
689 if (progress != null)
691 progress.setProgressBar(
692 MessageManager.getString(
693 "status.obtaining_mapping_with_nw_alignment"),
696 StructureMapping nwMapping = getNWMappings(seq, pdbFile,
697 maxChainId, maxChain, pdb, maxAlignseq);
698 seqToStrucMapping.add(nwMapping);
699 ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
702 if (forStructureView)
704 for (StructureMapping sm : seqToStrucMapping)
706 addStructureMapping(sm); // not addAll!
709 if (progress != null)
711 progress.setProgressBar(null, progressSessionId);
718 * check if we need to extract secondary structure from given pdbFile and
719 * transfer to sequences
722 * @param sequenceArray
725 private boolean isStructureFileProcessed(String pdbFile,
726 SequenceI[] sequenceArray)
728 boolean processed = false;
729 if (isPDBFileRegistered(pdbFile))
731 for (SequenceI sq : sequenceArray)
734 while (ds.getDatasetSequence() != null)
736 ds = ds.getDatasetSequence();
739 if (ds.getAnnotation() != null)
741 for (AlignmentAnnotation ala : ds.getAnnotation())
743 // false if any annotation present from this structure
744 // JBPNote this fails for jmol/chimera view because the *file* is
745 // passed, not the structure data ID -
746 if (PDBfile.isCalcIdForFile(ala, findIdForPDBFile(pdbFile)))
757 public void addStructureMapping(StructureMapping sm)
759 if (!mappings.contains(sm))
766 * retrieve a mapping for seq from SIFTs using associated DBRefEntry for
771 * @param targetChainId
777 * client for retrieval of SIFTS mappings for this structure
779 * @throws SiftsException
781 private StructureMapping getStructureMapping(SequenceI seq,
782 String pdbFile, String targetChainId, StructureFile pdb,
783 PDBChain maxChain, jalview.datamodel.Mapping sqmpping,
784 AlignSeq maxAlignseq, SiftsClient siftsClient)
785 throws SiftsException
787 StructureMapping curChainMapping = siftsClient
788 .getSiftsStructureMapping(seq, pdbFile, targetChainId);
791 PDBChain chain = pdb.findChain(targetChainId);
794 chain.transferResidueAnnotation(curChainMapping, null);
796 } catch (Exception e)
800 return curChainMapping;
804 * construct a mapping based on a pairwise alignment of the sequence and chain
814 private StructureMapping getNWMappings(SequenceI seq, String pdbFile,
815 String maxChainId, PDBChain maxChain, StructureFile pdb,
816 AlignSeq maxAlignseq)
818 final StringBuilder mappingDetails = new StringBuilder(128);
819 mappingDetails.append(NEWLINE)
820 .append("Sequence \u27f7 Structure mapping details");
821 mappingDetails.append(NEWLINE);
823 .append("Method: inferred with Needleman & Wunsch alignment");
824 mappingDetails.append(NEWLINE).append("PDB Sequence is :")
825 .append(NEWLINE).append("Sequence = ")
826 .append(maxChain.sequence.getSequenceAsString());
827 mappingDetails.append(NEWLINE).append("No of residues = ")
828 .append(maxChain.residues.size()).append(NEWLINE)
830 PrintStream ps = new PrintStream(System.out)
833 public void print(String x)
835 mappingDetails.append(x);
839 public void println()
841 mappingDetails.append(NEWLINE);
845 maxAlignseq.printAlignment(ps);
847 mappingDetails.append(NEWLINE).append("PDB start/end ");
848 mappingDetails.append(String.valueOf(maxAlignseq.seq2start))
850 mappingDetails.append(String.valueOf(maxAlignseq.seq2end));
851 mappingDetails.append(NEWLINE).append("SEQ start/end ");
854 .valueOf(maxAlignseq.seq1start + (seq.getStart() - 1)))
856 mappingDetails.append(
857 String.valueOf(maxAlignseq.seq1end + (seq.getStart() - 1)));
858 mappingDetails.append(NEWLINE);
859 maxChain.makeExactMapping(maxAlignseq, seq);
860 jalview.datamodel.Mapping sqmpping = maxAlignseq
861 .getMappingFromS1(false);
862 maxChain.transferRESNUMFeatures(seq, null,
863 pdb.getId().toLowerCase(Locale.ROOT));
865 HashMap<Integer, int[]> mapping = new HashMap<>();
872 Atom tmp = maxChain.atoms.elementAt(index);
873 if ((resNum != tmp.resNumber || insCode != tmp.insCode)
874 && tmp.alignmentMapping != -1)
876 resNum = tmp.resNumber;
877 insCode = tmp.insCode;
878 if (tmp.alignmentMapping >= -1)
880 mapping.put(tmp.alignmentMapping + 1,
882 { tmp.resNumber, tmp.atomIndex });
887 } while (index < maxChain.atoms.size());
889 StructureMapping nwMapping = new StructureMapping(seq, pdbFile,
890 pdb.getId(), maxChainId, mapping, mappingDetails.toString());
891 maxChain.transferResidueAnnotation(nwMapping, sqmpping);
896 * construct a 1:1 mapping using given residue and sequence numbering
899 * @param identityChainId
900 * @param identityChain
905 private StructureMapping getIdMappings(SequenceI seq, String pdbFile,
906 String identityChainId, PDBChain identityChain, StructureFile pdb)
908 final StringBuilder mappingDetails = new StringBuilder(128);
909 mappingDetails.append(NEWLINE)
910 .append("Sequence \u27f7 Structure mapping details");
911 mappingDetails.append(NEWLINE);
912 mappingDetails.append("Method: Matching length 1:1");
913 mappingDetails.append(NEWLINE).append("PDB Sequence is :")
914 .append(NEWLINE).append("Sequence = ")
915 .append(identityChain.sequence.getSequenceAsString());
916 mappingDetails.append(NEWLINE).append("No of residues = ")
917 .append(identityChain.residues.size()).append(NEWLINE)
920 mappingDetails.append(NEWLINE)
921 .append("Aligned Sequence is: " + seq.getDisplayId(true));
922 mappingDetails.append(NEWLINE)
923 .append("Sequence = " + seq.getSequenceAsString());
925 int from = Math.max(seq.getStart(),identityChain.sequence.getStart());
926 int to = Math.min(seq.getEnd(), identityChain.sequence.getEnd());
927 jalview.datamodel.Mapping sqmpping = new jalview.datamodel.Mapping(seq,
928 new MapList(new int[]
933 identityChain.mapChainWith(sqmpping, seq);
935 identityChain.transferRESNUMFeatures(seq, null,
936 pdb.getId().toLowerCase(Locale.ROOT));
939 // TODO REFACTOR TO PDBChain as a builder
940 HashMap<Integer, int[]> mapping = new HashMap<>();
947 Atom tmp = identityChain.atoms.elementAt(index);
948 if ((resNum != tmp.resNumber || insCode != tmp.insCode)
949 && tmp.alignmentMapping != -1)
951 resNum = tmp.resNumber;
952 insCode = tmp.insCode;
953 if (tmp.alignmentMapping >= -1)
955 mapping.put(tmp.alignmentMapping + 1,
957 { tmp.resNumber, tmp.atomIndex });
962 } while (index < identityChain.atoms.size());
964 StructureMapping idMapping = new StructureMapping(seq, pdbFile,
965 pdb.getId(), identityChainId, mapping,
966 mappingDetails.toString());
967 identityChain.transferResidueAnnotation(idMapping, sqmpping);
971 public void removeStructureViewerListener(Object svl, String[] pdbfiles)
973 listeners.removeElement(svl);
974 if (svl instanceof SequenceListener)
976 for (int i = 0; i < listeners.size(); i++)
978 if (listeners.elementAt(i) instanceof StructureListener)
980 ((StructureListener) listeners.elementAt(i))
981 .releaseReferences(svl);
986 if (pdbfiles == null)
992 * Remove mappings to the closed listener's PDB files, but first check if
993 * another listener is still interested
995 List<String> pdbs = new ArrayList<>(Arrays.asList(pdbfiles));
997 StructureListener sl;
998 for (int i = 0; i < listeners.size(); i++)
1000 if (listeners.elementAt(i) instanceof StructureListener)
1002 sl = (StructureListener) listeners.elementAt(i);
1003 for (String pdbfile : sl.getStructureFiles())
1005 pdbs.remove(pdbfile);
1011 * Rebuild the mappings set, retaining only those which are for 'other' PDB
1014 if (pdbs.size() > 0)
1016 List<StructureMapping> tmp = new ArrayList<>();
1017 for (StructureMapping sm : mappings)
1019 if (!pdbs.contains(sm.pdbfile))
1030 * hack to highlight a range of positions at once on any structure views
1032 * @param sequenceRef
1034 * - series of int start-end ranges as positions on sequenceRef
1038 public void highlightPositionsOn(SequenceI sequenceRef, int[][] is,
1041 boolean hasSequenceListeners = handlingVamsasMo
1042 || !seqmappings.isEmpty();
1043 SearchResultsI results = null;
1044 ArrayList<Integer> listOfPositions = new ArrayList<Integer>();
1045 for (int[] s_e : is)
1047 for (int p = s_e[0]; p <= s_e[1]; listOfPositions.add(p++))
1050 int seqpos[] = new int[listOfPositions.size()];
1052 for (Integer p : listOfPositions)
1057 for (i = 0; i < listeners.size(); i++)
1059 Object listener = listeners.elementAt(i);
1060 if (listener == source)
1062 // TODO listener (e.g. SeqPanel) is never == source (AlignViewport)
1063 // Temporary fudge with SequenceListener.getVamsasSource()
1066 if (listener instanceof StructureListener)
1068 highlightStructure((StructureListener) listener, sequenceRef,
1076 * Propagate mouseover of a single position in a structure
1083 public String mouseOverStructure(int pdbResNum, String chain,
1086 AtomSpec atomSpec = new AtomSpec(pdbfile, chain, pdbResNum, 0);
1087 List<AtomSpec> atoms = Collections.singletonList(atomSpec);
1088 return mouseOverStructure(atoms);
1092 * Propagate mouseover or selection of multiple positions in a structure
1096 public String mouseOverStructure(List<AtomSpec> atoms)
1098 if (listeners == null)
1100 // old or prematurely sent event
1103 boolean hasSequenceListener = false;
1104 for (int i = 0; i < listeners.size(); i++)
1106 if (listeners.elementAt(i) instanceof SequenceListener)
1108 hasSequenceListener = true;
1111 if (!hasSequenceListener)
1116 SearchResultsI results = findAlignmentPositionsForStructurePositions(
1118 String result = null;
1119 for (Object li : listeners)
1121 if (li instanceof SequenceListener)
1123 String s = ((SequenceListener) li).highlightSequence(results);
1134 * Constructs a SearchResults object holding regions (if any) in the Jalview
1135 * alignment which have a mapping to the structure viewer positions in the
1141 public SearchResultsI findAlignmentPositionsForStructurePositions(
1142 List<AtomSpec> atoms)
1144 SearchResultsI results = new SearchResults();
1145 for (AtomSpec atom : atoms)
1147 SequenceI lastseq = null;
1149 for (StructureMapping sm : mappings)
1151 if (sm.pdbfile.equals(atom.getPdbFile())
1152 && sm.pdbchain.equals(atom.getChain()))
1154 int indexpos = sm.getSeqPos(atom.getPdbResNum());
1155 if (lastipos != indexpos || lastseq != sm.sequence)
1157 results.appendResult(sm.sequence, indexpos, indexpos);
1158 lastipos = indexpos;
1159 lastseq = sm.sequence;
1160 // construct highlighted sequence list
1161 for (AlignedCodonFrame acf : seqmappings)
1163 acf.markMappedRegion(sm.sequence, indexpos, results);
1173 * highlight regions associated with a position (indexpos) in seq
1176 * the sequence that the mouse over occurred on
1178 * the absolute position being mouseovered in seq (0 to seq.length())
1180 * the sequence position (if -1, seq.findPosition is called to
1181 * resolve the residue number)
1183 public void mouseOverSequence(SequenceI seq, int indexpos, int seqPos,
1184 VamsasSource source)
1186 boolean hasSequenceListeners = handlingVamsasMo
1187 || !seqmappings.isEmpty();
1188 SearchResultsI results = null;
1191 seqPos = seq.findPosition(indexpos);
1193 for (int i = 0; i < listeners.size(); i++)
1195 Object listener = listeners.elementAt(i);
1196 if (listener == source)
1198 // TODO listener (e.g. SeqPanel) is never == source (AlignViewport)
1199 // Temporary fudge with SequenceListener.getVamsasSource()
1202 if (listener instanceof StructureListener)
1204 highlightStructure((StructureListener) listener, seq, seqPos);
1208 if (listener instanceof SequenceListener)
1210 final SequenceListener seqListener = (SequenceListener) listener;
1211 if (hasSequenceListeners
1212 && seqListener.getVamsasSource() != source)
1214 if (relaySeqMappings)
1216 if (results == null)
1218 results = MappingUtils.buildSearchResults(seq, seqPos,
1221 if (handlingVamsasMo)
1223 results.addResult(seq, seqPos, seqPos);
1226 if (!results.isEmpty())
1228 seqListener.highlightSequence(results);
1233 else if (listener instanceof VamsasListener && !handlingVamsasMo)
1235 ((VamsasListener) listener).mouseOverSequence(seq, indexpos,
1238 else if (listener instanceof SecondaryStructureListener)
1240 ((SecondaryStructureListener) listener).mouseOverSequence(seq,
1248 * Send suitable messages to a StructureListener to highlight atoms
1249 * corresponding to the given sequence position(s)
1255 public void highlightStructure(StructureListener sl, SequenceI seq,
1258 if (!sl.isListeningFor(seq))
1263 List<AtomSpec> atoms = new ArrayList<>();
1264 for (StructureMapping sm : mappings)
1266 if (sm.sequence == seq || sm.sequence == seq.getDatasetSequence()
1267 || (sm.sequence.getDatasetSequence() != null && sm.sequence
1268 .getDatasetSequence() == seq.getDatasetSequence()))
1270 for (int index : positions)
1272 atomNo = sm.getAtomNum(index);
1276 atoms.add(new AtomSpec(sm.pdbfile, sm.pdbchain,
1277 sm.getPDBResNum(index), atomNo));
1282 sl.highlightAtoms(atoms);
1285 public void highlightStructureRegionsFor(StructureListener sl,
1286 SequenceI[] seqs, int... columns)
1288 List<SequenceI> to_highlight = new ArrayList<SequenceI>();
1289 for (SequenceI seq : seqs)
1291 if (sl.isListeningFor(seq))
1293 to_highlight.add(seq);
1296 if (to_highlight.size() == 0)
1300 List<AtomSpec> atoms = new ArrayList<>();
1301 for (SequenceI seq : to_highlight)
1304 for (StructureMapping sm : mappings)
1306 if (sm.sequence == seq || sm.sequence == seq.getDatasetSequence()
1307 || (sm.sequence.getDatasetSequence() != null && sm.sequence
1308 .getDatasetSequence() == seq.getDatasetSequence()))
1311 for (int i = 0; i < columns.length; i += 2)
1313 ContiguousI positions = seq.findPositions(columns[i] + 1,
1314 columns[i + 1] + 1);
1315 if (positions == null)
1319 for (int index = positions.getBegin(); index <= positions
1323 atomNo = sm.getAtomNum(index);
1327 atoms.add(new AtomSpec(sm.pdbfile, sm.pdbchain,
1328 sm.getPDBResNum(index), atomNo));
1334 if (atoms.size() > 0)
1336 sl.highlightAtoms(atoms);
1342 * true if a mouse over event from an external (ie Vamsas) source is being
1345 boolean handlingVamsasMo = false;
1350 * as mouseOverSequence but only route event to SequenceListeners
1354 * in an alignment sequence
1356 public void mouseOverVamsasSequence(SequenceI sequenceI, int position,
1357 VamsasSource source)
1359 handlingVamsasMo = true;
1360 long msg = sequenceI.hashCode() * (1 + position);
1364 mouseOverSequence(sequenceI, position, -1, source);
1366 handlingVamsasMo = false;
1369 public Annotation[] colourSequenceFromStructure(SequenceI seq,
1373 // THIS WILL NOT BE AVAILABLE IN JALVIEW 2.3,
1374 // UNTIL THE COLOUR BY ANNOTATION IS REWORKED
1376 * Annotation [] annotations = new Annotation[seq.getLength()];
1378 * StructureListener sl; int atomNo = 0; for (int i = 0; i <
1379 * listeners.size(); i++) { if (listeners.elementAt(i) instanceof
1380 * StructureListener) { sl = (StructureListener) listeners.elementAt(i);
1382 * for (int j = 0; j < mappings.length; j++) {
1384 * if (mappings[j].sequence == seq && mappings[j].getPdbId().equals(pdbid)
1385 * && mappings[j].pdbfile.equals(sl.getPdbFile())) {
1386 * jalview.bin.Console.outPrintln(pdbid+" "+mappings[j].getPdbId() +"
1387 * "+mappings[j].pdbfile);
1389 * java.awt.Color col; for(int index=0; index<seq.getLength(); index++) {
1390 * if(jalview.util.Comparison.isGap(seq.getCharAt(index))) continue;
1392 * atomNo = mappings[j].getAtomNum(seq.findPosition(index)); col =
1393 * java.awt.Color.white; if (atomNo > 0) { col = sl.getColour(atomNo,
1394 * mappings[j].getPDBResNum(index), mappings[j].pdbchain,
1395 * mappings[j].pdbfile); }
1397 * annotations[index] = new Annotation("X",null,' ',0,col); } return
1398 * annotations; } } } }
1400 * return annotations;
1404 public void structureSelectionChanged()
1408 public void sequenceSelectionChanged()
1412 public void sequenceColoursChanged(Object source)
1414 StructureListener sl;
1415 for (int i = 0; i < listeners.size(); i++)
1417 if (listeners.elementAt(i) instanceof StructureListener)
1419 sl = (StructureListener) listeners.elementAt(i);
1420 sl.updateColours(source);
1425 public StructureMapping[] getMapping(String pdbfile)
1427 List<StructureMapping> tmp = new ArrayList<>();
1428 for (StructureMapping sm : mappings)
1430 if (sm.pdbfile.equals(pdbfile))
1435 return tmp.toArray(new StructureMapping[tmp.size()]);
1439 * Returns a readable description of all mappings for the given pdbfile to any
1440 * of the given sequences
1446 public String printMappings(String pdbfile, List<SequenceI> seqs)
1448 if (pdbfile == null || seqs == null || seqs.isEmpty())
1453 StringBuilder sb = new StringBuilder(64);
1454 for (StructureMapping sm : mappings)
1456 if (Platform.pathEquals(sm.pdbfile, pdbfile)
1457 && seqs.contains(sm.sequence))
1459 sb.append(sm.mappingDetails);
1461 // separator makes it easier to read multiple mappings
1462 sb.append("=====================");
1468 return sb.toString();
1472 * Remove the given mapping
1476 public void deregisterMapping(AlignedCodonFrame acf)
1480 boolean removed = seqmappings.remove(acf);
1481 if (removed && seqmappings.isEmpty())
1483 jalview.bin.Console.outPrintln("All mappings removed");
1489 * Add each of the given codonFrames to the stored set, if not aready present.
1493 public void registerMappings(List<AlignedCodonFrame> mappings)
1495 if (mappings != null)
1497 for (AlignedCodonFrame acf : mappings)
1499 registerMapping(acf);
1505 * Add the given mapping to the stored set, unless already stored.
1507 public void registerMapping(AlignedCodonFrame acf)
1511 if (!seqmappings.contains(acf))
1513 seqmappings.add(acf);
1519 * Resets this object to its initial state by removing all registered
1520 * listeners, codon mappings, PDB file mappings
1522 public void resetAll()
1524 if (mappings != null)
1528 if (seqmappings != null)
1530 seqmappings.clear();
1532 if (sel_listeners != null)
1534 sel_listeners.clear();
1536 if (listeners != null)
1540 if (commandListeners != null)
1542 commandListeners.clear();
1544 if (view_listeners != null)
1546 view_listeners.clear();
1548 if (pdbFileNameId != null)
1550 pdbFileNameId.clear();
1552 if (pdbIdFileName != null)
1554 pdbIdFileName.clear();
1558 public void addSelectionListener(SelectionListener selecter)
1560 if (!sel_listeners.contains(selecter))
1562 sel_listeners.add(selecter);
1566 public void removeSelectionListener(SelectionListener toremove)
1568 if (sel_listeners.contains(toremove))
1570 sel_listeners.remove(toremove);
1574 public synchronized void sendSelection(
1575 jalview.datamodel.SequenceGroup selection,
1576 jalview.datamodel.ColumnSelection colsel, HiddenColumns hidden,
1577 SelectionSource source)
1579 for (SelectionListener slis : sel_listeners)
1583 slis.selection(selection, colsel, hidden, source);
1588 Vector<AlignmentViewPanelListener> view_listeners = new Vector<>();
1590 public synchronized void sendViewPosition(
1591 jalview.api.AlignmentViewPanel source, int startRes, int endRes,
1592 int startSeq, int endSeq)
1595 if (view_listeners != null && view_listeners.size() > 0)
1597 Enumeration<AlignmentViewPanelListener> listeners = view_listeners
1599 while (listeners.hasMoreElements())
1601 AlignmentViewPanelListener slis = listeners.nextElement();
1604 slis.viewPosition(startRes, endRes, startSeq, endSeq, source);
1612 * release all references associated with this manager provider
1614 * @param jalviewLite
1616 public static void release(StructureSelectionManagerProvider jalviewLite)
1618 // synchronized (instances)
1620 if (instances == null)
1624 StructureSelectionManager mnger = (instances.get(jalviewLite));
1627 instances.remove(jalviewLite);
1630 /* bsoares 2019-03-20 finalize deprecated, no apparent external
1631 * resources to close
1633 // mnger.finalize();
1634 } catch (Throwable x)
1641 public void registerPDBEntry(PDBEntry pdbentry)
1643 if (pdbentry.getFile() != null
1644 && pdbentry.getFile().trim().length() > 0)
1646 registerPDBFile(pdbentry.getId(), pdbentry.getFile());
1650 public void addCommandListener(CommandListener cl)
1652 if (!commandListeners.contains(cl))
1654 commandListeners.add(cl);
1658 public boolean hasCommandListener(CommandListener cl)
1660 return this.commandListeners.contains(cl);
1663 public boolean removeCommandListener(CommandListener l)
1665 return commandListeners.remove(l);
1669 * Forward a command to any command listeners (except for the command's
1673 * the command to be broadcast (in its form after being performed)
1675 * if true, the command was being 'undone'
1678 public void commandPerformed(CommandI command, boolean undo,
1679 VamsasSource source)
1681 for (CommandListener listener : commandListeners)
1683 listener.mirrorCommand(command, undo, this, source);
1688 * Returns a new CommandI representing the given command as mapped to the
1689 * given sequences. If no mapping could be made, or the command is not of a
1690 * mappable kind, returns null.
1698 public CommandI mapCommand(CommandI command, boolean undo,
1699 final AlignmentI mapTo, char gapChar)
1701 if (command instanceof EditCommand)
1703 return MappingUtils.mapEditCommand((EditCommand) command, undo, mapTo,
1704 gapChar, seqmappings);
1706 else if (command instanceof OrderCommand)
1708 return MappingUtils.mapOrderCommand((OrderCommand) command, undo,
1709 mapTo, seqmappings);
1714 public List<AlignedCodonFrame> getSequenceMappings()
1720 * quick and dirty route to just highlight all structure positions for a range
1723 * @param sequencesArray
1725 * start-end columns on sequencesArray
1727 * origin parent AlignmentPanel
1729 public void highlightPositionsOnMany(SequenceI[] sequencesArray, int[] is,
1732 for (int i = 0; i < listeners.size(); i++)
1734 Object listener = listeners.elementAt(i);
1735 if (listener == source)
1737 // TODO listener (e.g. SeqPanel) is never == source (AlignViewport)
1738 // Temporary fudge with SequenceListener.getVamsasSource()
1741 if (listener instanceof StructureListener)
1743 highlightStructureRegionsFor((StructureListener) listener,
1744 sequencesArray, is);
1749 public Map<String, String> getPdbFileNameIdMap()
1751 return pdbFileNameId;
1754 public Map<String, String> getPdbIdFileNameMap()
1756 return pdbIdFileName;