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 if (isMapUsingSIFTs && seq.isProtein())
603 if (progress != null)
605 progress.setProgressBar(
607 .getString("status.obtaining_mapping_with_sifts"),
610 jalview.datamodel.Mapping sqmpping = maxAlignseq
611 .getMappingFromS1(false);
612 if (targetChainId != null && !targetChainId.trim().isEmpty())
614 StructureMapping siftsMapping;
617 siftsMapping = getStructureMapping(seq, pdbFile, targetChainId,
618 pdb, maxChain, sqmpping, maxAlignseq, siftsClient);
619 seqToStrucMapping.add(siftsMapping);
620 maxChain.makeExactMapping(siftsMapping, seq);
621 maxChain.transferRESNUMFeatures(seq, "IEA: SIFTS",
622 pdb.getId().toLowerCase(Locale.ROOT));
623 maxChain.transferResidueAnnotation(siftsMapping, null);
624 ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
626 } catch (SiftsException e)
628 // fall back to NW alignment
629 Console.error(e.getMessage());
630 StructureMapping nwMapping = getNWMappings(seq, pdbFile,
631 targetChainId, maxChain, pdb, maxAlignseq);
632 seqToStrucMapping.add(nwMapping);
633 maxChain.makeExactMapping(maxAlignseq, seq);
634 maxChain.transferRESNUMFeatures(seq, "IEA:Jalview",
635 pdb.getId().toLowerCase(Locale.ROOT)); // FIXME: is
638 maxChain.transferResidueAnnotation(nwMapping, sqmpping);
639 ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
644 List<StructureMapping> foundSiftsMappings = new ArrayList<>();
645 for (PDBChain chain : pdb.getChains())
647 StructureMapping siftsMapping = null;
650 siftsMapping = getStructureMapping(seq, pdbFile, chain.id,
651 pdb, chain, sqmpping, maxAlignseq, siftsClient);
652 foundSiftsMappings.add(siftsMapping);
653 chain.makeExactMapping(siftsMapping, seq);
654 chain.transferRESNUMFeatures(seq, "IEA: SIFTS",
655 pdb.getId().toLowerCase(Locale.ROOT));// FIXME: is this
657 chain.transferResidueAnnotation(siftsMapping, null);
658 } catch (SiftsException e)
660 jalview.bin.Console.errPrintln(e.getMessage());
661 } catch (Exception e)
663 jalview.bin.Console.errPrintln(
664 "Unexpected exception during SIFTS mapping - falling back to NW for this sequence/structure pair");
665 jalview.bin.Console.errPrintln(e.getMessage());
668 if (!foundSiftsMappings.isEmpty())
670 seqToStrucMapping.addAll(foundSiftsMappings);
671 ds.addPDBId(sqmpping.getTo().getAllPDBEntries().get(0));
676 * fallback to NeedlemanWunch.
678 StructureMapping nwMapping = getNWMappings(seq, pdbFile,
679 maxChainId, maxChain, pdb, maxAlignseq);
680 seqToStrucMapping.add(nwMapping);
681 maxChain.transferRESNUMFeatures(seq, null,
682 pdb.getId().toLowerCase(Locale.ROOT)); // FIXME: is this
684 maxChain.transferResidueAnnotation(nwMapping, sqmpping);
685 ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
692 // first check if we should use an identity mapping
693 if (idLengthChain != null && maxAlignseq.getS2Coverage() < 0.5)
696 "Assuming 3Dsi identity mapping between structure and sequence");
697 StructureMapping matchMapping = getIdMappings(seq, pdbFile,
698 idLengthChain.id, idLengthChain, pdb);
699 seqToStrucMapping.add(matchMapping);
700 ds.addPDBId(idLengthChain.sequence.getAllPDBEntries().get(0));
701 Console.info("Mapping added.");
705 // Construct a needleman wunsch mapping instead.
706 if (progress != null)
708 progress.setProgressBar(
709 MessageManager.getString(
710 "status.obtaining_mapping_with_nw_alignment"),
713 StructureMapping nwMapping = getNWMappings(seq, pdbFile,
714 maxChainId, maxChain, pdb, maxAlignseq);
715 seqToStrucMapping.add(nwMapping);
716 ds.addPDBId(maxChain.sequence.getAllPDBEntries().get(0));
719 if (forStructureView)
721 for (StructureMapping sm : seqToStrucMapping)
723 addStructureMapping(sm); // not addAll!
726 if (progress != null)
728 progress.setProgressBar(null, progressSessionId);
735 * check if we need to extract secondary structure from given pdbFile and
736 * transfer to sequences
739 * @param sequenceArray
742 private boolean isStructureFileProcessed(String pdbFile,
743 SequenceI[] sequenceArray)
745 boolean processed = false;
746 if (isPDBFileRegistered(pdbFile))
748 for (SequenceI sq : sequenceArray)
751 while (ds.getDatasetSequence() != null)
753 ds = ds.getDatasetSequence();
756 if (ds.getAnnotation() != null)
758 for (AlignmentAnnotation ala : ds.getAnnotation())
760 // false if any annotation present from this structure
761 // JBPNote this fails for jmol/chimera view because the *file* is
762 // passed, not the structure data ID -
763 if (PDBfile.isCalcIdForFile(ala, findIdForPDBFile(pdbFile)))
774 public void addStructureMapping(StructureMapping sm)
776 if (!mappings.contains(sm))
783 * retrieve a mapping for seq from SIFTs using associated DBRefEntry for
788 * @param targetChainId
794 * client for retrieval of SIFTS mappings for this structure
796 * @throws SiftsException
798 private StructureMapping getStructureMapping(SequenceI seq,
799 String pdbFile, String targetChainId, StructureFile pdb,
800 PDBChain maxChain, jalview.datamodel.Mapping sqmpping,
801 AlignSeq maxAlignseq, SiftsClient siftsClient)
802 throws SiftsException
804 StructureMapping curChainMapping = siftsClient
805 .getSiftsStructureMapping(seq, pdbFile, targetChainId);
808 PDBChain chain = pdb.findChain(targetChainId);
811 chain.transferResidueAnnotation(curChainMapping, null);
813 } catch (Exception e)
817 return curChainMapping;
821 * construct a mapping based on a pairwise alignment of the sequence and chain
831 private StructureMapping getNWMappings(SequenceI seq, String pdbFile,
832 String maxChainId, PDBChain maxChain, StructureFile pdb,
833 AlignSeq maxAlignseq)
835 final StringBuilder mappingDetails = new StringBuilder(128);
836 mappingDetails.append(NEWLINE)
837 .append("Sequence \u27f7 Structure mapping details");
838 mappingDetails.append(NEWLINE);
840 .append("Method: inferred with Needleman & Wunsch alignment");
841 mappingDetails.append(NEWLINE).append("PDB Sequence is :")
842 .append(NEWLINE).append("Sequence = ")
843 .append(maxChain.sequence.getSequenceAsString());
844 mappingDetails.append(NEWLINE).append("No of residues = ")
845 .append(maxChain.residues.size()).append(NEWLINE)
847 PrintStream ps = new PrintStream(System.out)
850 public void print(String x)
852 mappingDetails.append(x);
856 public void println()
858 mappingDetails.append(NEWLINE);
862 maxAlignseq.printAlignment(ps);
864 mappingDetails.append(NEWLINE).append("PDB start/end ");
865 mappingDetails.append(String.valueOf(maxAlignseq.seq2start))
867 mappingDetails.append(String.valueOf(maxAlignseq.seq2end));
868 mappingDetails.append(NEWLINE).append("SEQ start/end ");
871 .valueOf(maxAlignseq.seq1start + (seq.getStart() - 1)))
873 mappingDetails.append(
874 String.valueOf(maxAlignseq.seq1end + (seq.getStart() - 1)));
875 mappingDetails.append(NEWLINE);
876 maxChain.makeExactMapping(maxAlignseq, seq);
877 jalview.datamodel.Mapping sqmpping = maxAlignseq
878 .getMappingFromS1(false);
879 maxChain.transferRESNUMFeatures(seq, null,
880 pdb.getId().toLowerCase(Locale.ROOT));
882 HashMap<Integer, int[]> mapping = new HashMap<>();
889 Atom tmp = maxChain.atoms.elementAt(index);
890 if ((resNum != tmp.resNumber || insCode != tmp.insCode)
891 && tmp.alignmentMapping != -1)
893 resNum = tmp.resNumber;
894 insCode = tmp.insCode;
895 if (tmp.alignmentMapping >= -1)
897 mapping.put(tmp.alignmentMapping + 1,
899 { tmp.resNumber, tmp.atomIndex });
904 } while (index < maxChain.atoms.size());
906 StructureMapping nwMapping = new StructureMapping(seq, pdbFile,
907 pdb.getId(), maxChainId, mapping, mappingDetails.toString());
908 maxChain.transferResidueAnnotation(nwMapping, sqmpping);
913 * construct a 1:1 mapping using given residue and sequence numbering
916 * @param identityChainId
917 * @param identityChain
922 private StructureMapping getIdMappings(SequenceI seq, String pdbFile,
923 String identityChainId, PDBChain identityChain, StructureFile pdb)
925 final StringBuilder mappingDetails = new StringBuilder(128);
926 mappingDetails.append(NEWLINE)
927 .append("Sequence \u27f7 Structure mapping details");
928 mappingDetails.append(NEWLINE);
929 mappingDetails.append("Method: Matching length 1:1");
930 mappingDetails.append(NEWLINE).append("PDB Sequence is :")
931 .append(NEWLINE).append("Sequence = ")
932 .append(identityChain.sequence.getSequenceAsString());
933 mappingDetails.append(NEWLINE).append("No of residues = ")
934 .append(identityChain.residues.size()).append(NEWLINE)
937 mappingDetails.append(NEWLINE)
938 .append("Aligned Sequence is: " + seq.getDisplayId(true));
939 mappingDetails.append(NEWLINE)
940 .append("Sequence = " + seq.getSequenceAsString());
942 int from = Math.max(seq.getStart(),identityChain.sequence.getStart());
943 int to = Math.min(seq.getEnd(), identityChain.sequence.getEnd());
944 jalview.datamodel.Mapping sqmpping = new jalview.datamodel.Mapping(seq,
945 new MapList(new int[]
950 identityChain.mapChainWith(sqmpping, seq);
952 identityChain.transferRESNUMFeatures(seq, null,
953 pdb.getId().toLowerCase(Locale.ROOT));
956 // TODO REFACTOR TO PDBChain as a builder
957 HashMap<Integer, int[]> mapping = new HashMap<>();
964 Atom tmp = identityChain.atoms.elementAt(index);
965 if ((resNum != tmp.resNumber || insCode != tmp.insCode)
966 && tmp.alignmentMapping != -1)
968 resNum = tmp.resNumber;
969 insCode = tmp.insCode;
970 if (tmp.alignmentMapping >= -1)
972 mapping.put(tmp.alignmentMapping + 1,
974 { tmp.resNumber, tmp.atomIndex });
979 } while (index < identityChain.atoms.size());
981 StructureMapping idMapping = new StructureMapping(seq, pdbFile,
982 pdb.getId(), identityChainId, mapping,
983 mappingDetails.toString());
984 identityChain.transferResidueAnnotation(idMapping, sqmpping);
988 public void removeStructureViewerListener(Object svl, String[] pdbfiles)
990 listeners.removeElement(svl);
991 if (svl instanceof SequenceListener)
993 for (int i = 0; i < listeners.size(); i++)
995 if (listeners.elementAt(i) instanceof StructureListener)
997 ((StructureListener) listeners.elementAt(i))
998 .releaseReferences(svl);
1003 if (pdbfiles == null)
1009 * Remove mappings to the closed listener's PDB files, but first check if
1010 * another listener is still interested
1012 List<String> pdbs = new ArrayList<>(Arrays.asList(pdbfiles));
1014 StructureListener sl;
1015 for (int i = 0; i < listeners.size(); i++)
1017 if (listeners.elementAt(i) instanceof StructureListener)
1019 sl = (StructureListener) listeners.elementAt(i);
1020 for (String pdbfile : sl.getStructureFiles())
1022 pdbs.remove(pdbfile);
1028 * Rebuild the mappings set, retaining only those which are for 'other' PDB
1031 if (pdbs.size() > 0)
1033 List<StructureMapping> tmp = new ArrayList<>();
1034 for (StructureMapping sm : mappings)
1036 if (!pdbs.contains(sm.pdbfile))
1047 * hack to highlight a range of positions at once on any structure views
1049 * @param sequenceRef
1051 * - series of int start-end ranges as positions on sequenceRef
1055 public void highlightPositionsOn(SequenceI sequenceRef, int[][] is,
1058 boolean hasSequenceListeners = handlingVamsasMo
1059 || !seqmappings.isEmpty();
1060 SearchResultsI results = null;
1061 ArrayList<Integer> listOfPositions = new ArrayList<Integer>();
1062 for (int[] s_e : is)
1064 for (int p = s_e[0]; p <= s_e[1]; listOfPositions.add(p++))
1067 int seqpos[] = new int[listOfPositions.size()];
1069 for (Integer p : listOfPositions)
1074 for (i = 0; i < listeners.size(); i++)
1076 Object listener = listeners.elementAt(i);
1077 if (listener == source)
1079 // TODO listener (e.g. SeqPanel) is never == source (AlignViewport)
1080 // Temporary fudge with SequenceListener.getVamsasSource()
1083 if (listener instanceof StructureListener)
1085 highlightStructure((StructureListener) listener, sequenceRef,
1093 * Propagate mouseover of a single position in a structure
1100 public String mouseOverStructure(int pdbResNum, String chain,
1103 AtomSpec atomSpec = new AtomSpec(pdbfile, chain, pdbResNum, 0);
1104 List<AtomSpec> atoms = Collections.singletonList(atomSpec);
1105 return mouseOverStructure(atoms);
1109 * Propagate mouseover or selection of multiple positions in a structure
1113 public String mouseOverStructure(List<AtomSpec> atoms)
1115 if (listeners == null)
1117 // old or prematurely sent event
1120 boolean hasSequenceListener = false;
1121 for (int i = 0; i < listeners.size(); i++)
1123 if (listeners.elementAt(i) instanceof SequenceListener)
1125 hasSequenceListener = true;
1128 if (!hasSequenceListener)
1133 SearchResultsI results = findAlignmentPositionsForStructurePositions(
1135 String result = null;
1136 for (Object li : listeners)
1138 if (li instanceof SequenceListener)
1140 String s = ((SequenceListener) li).highlightSequence(results);
1151 * Constructs a SearchResults object holding regions (if any) in the Jalview
1152 * alignment which have a mapping to the structure viewer positions in the
1158 public SearchResultsI findAlignmentPositionsForStructurePositions(
1159 List<AtomSpec> atoms)
1161 SearchResultsI results = new SearchResults();
1162 for (AtomSpec atom : atoms)
1164 SequenceI lastseq = null;
1166 for (StructureMapping sm : mappings)
1168 if (sm.pdbfile.equals(atom.getPdbFile())
1169 && sm.pdbchain.equals(atom.getChain()))
1171 int indexpos = sm.getSeqPos(atom.getPdbResNum());
1172 if (lastipos != indexpos || lastseq != sm.sequence)
1174 results.appendResult(sm.sequence, indexpos, indexpos);
1175 lastipos = indexpos;
1176 lastseq = sm.sequence;
1177 // construct highlighted sequence list
1178 for (AlignedCodonFrame acf : seqmappings)
1180 acf.markMappedRegion(sm.sequence, indexpos, results);
1190 * highlight regions associated with a position (indexpos) in seq
1193 * the sequence that the mouse over occurred on
1195 * the absolute position being mouseovered in seq (0 to seq.length())
1197 * the sequence position (if -1, seq.findPosition is called to
1198 * resolve the residue number)
1200 public void mouseOverSequence(SequenceI seq, int indexpos, int seqPos,
1201 VamsasSource source)
1203 boolean hasSequenceListeners = handlingVamsasMo
1204 || !seqmappings.isEmpty();
1205 SearchResultsI results = null;
1208 seqPos = seq.findPosition(indexpos);
1210 for (int i = 0; i < listeners.size(); i++)
1212 Object listener = listeners.elementAt(i);
1213 if (listener == source)
1215 // TODO listener (e.g. SeqPanel) is never == source (AlignViewport)
1216 // Temporary fudge with SequenceListener.getVamsasSource()
1219 if (listener instanceof StructureListener)
1221 highlightStructure((StructureListener) listener, seq, seqPos);
1225 if (listener instanceof SequenceListener)
1227 final SequenceListener seqListener = (SequenceListener) listener;
1228 if (hasSequenceListeners
1229 && seqListener.getVamsasSource() != source)
1231 if (relaySeqMappings)
1233 if (results == null)
1235 results = MappingUtils.buildSearchResults(seq, seqPos,
1238 if (handlingVamsasMo)
1240 results.addResult(seq, seqPos, seqPos);
1243 if (!results.isEmpty())
1245 seqListener.highlightSequence(results);
1250 else if (listener instanceof VamsasListener && !handlingVamsasMo)
1252 ((VamsasListener) listener).mouseOverSequence(seq, indexpos,
1255 else if (listener instanceof SecondaryStructureListener)
1257 ((SecondaryStructureListener) listener).mouseOverSequence(seq,
1265 * Send suitable messages to a StructureListener to highlight atoms
1266 * corresponding to the given sequence position(s)
1272 public void highlightStructure(StructureListener sl, SequenceI seq,
1275 if (!sl.isListeningFor(seq))
1280 List<AtomSpec> atoms = new ArrayList<>();
1281 for (StructureMapping sm : mappings)
1283 if (sm.sequence == seq || sm.sequence == seq.getDatasetSequence()
1284 || (sm.sequence.getDatasetSequence() != null && sm.sequence
1285 .getDatasetSequence() == seq.getDatasetSequence()))
1287 for (int index : positions)
1289 atomNo = sm.getAtomNum(index);
1293 atoms.add(new AtomSpec(sm.pdbfile, sm.pdbchain,
1294 sm.getPDBResNum(index), atomNo));
1299 sl.highlightAtoms(atoms);
1302 public void highlightStructureRegionsFor(StructureListener sl,
1303 SequenceI[] seqs, int... columns)
1305 List<SequenceI> to_highlight = new ArrayList<SequenceI>();
1306 for (SequenceI seq : seqs)
1308 if (sl.isListeningFor(seq))
1310 to_highlight.add(seq);
1313 if (to_highlight.size() == 0)
1317 List<AtomSpec> atoms = new ArrayList<>();
1318 for (SequenceI seq : to_highlight)
1321 for (StructureMapping sm : mappings)
1323 if (sm.sequence == seq || sm.sequence == seq.getDatasetSequence()
1324 || (sm.sequence.getDatasetSequence() != null && sm.sequence
1325 .getDatasetSequence() == seq.getDatasetSequence()))
1328 for (int i = 0; i < columns.length; i += 2)
1330 ContiguousI positions = seq.findPositions(columns[i] + 1,
1331 columns[i + 1] + 1);
1332 if (positions == null)
1336 for (int index = positions.getBegin(); index <= positions
1340 atomNo = sm.getAtomNum(index);
1344 atoms.add(new AtomSpec(sm.pdbfile, sm.pdbchain,
1345 sm.getPDBResNum(index), atomNo));
1351 if (atoms.size() > 0)
1353 sl.highlightAtoms(atoms);
1359 * true if a mouse over event from an external (ie Vamsas) source is being
1362 boolean handlingVamsasMo = false;
1367 * as mouseOverSequence but only route event to SequenceListeners
1371 * in an alignment sequence
1373 public void mouseOverVamsasSequence(SequenceI sequenceI, int position,
1374 VamsasSource source)
1376 handlingVamsasMo = true;
1377 long msg = sequenceI.hashCode() * (1 + position);
1381 mouseOverSequence(sequenceI, position, -1, source);
1383 handlingVamsasMo = false;
1386 public Annotation[] colourSequenceFromStructure(SequenceI seq,
1390 // THIS WILL NOT BE AVAILABLE IN JALVIEW 2.3,
1391 // UNTIL THE COLOUR BY ANNOTATION IS REWORKED
1393 * Annotation [] annotations = new Annotation[seq.getLength()];
1395 * StructureListener sl; int atomNo = 0; for (int i = 0; i <
1396 * listeners.size(); i++) { if (listeners.elementAt(i) instanceof
1397 * StructureListener) { sl = (StructureListener) listeners.elementAt(i);
1399 * for (int j = 0; j < mappings.length; j++) {
1401 * if (mappings[j].sequence == seq && mappings[j].getPdbId().equals(pdbid)
1402 * && mappings[j].pdbfile.equals(sl.getPdbFile())) {
1403 * jalview.bin.Console.outPrintln(pdbid+" "+mappings[j].getPdbId() +"
1404 * "+mappings[j].pdbfile);
1406 * java.awt.Color col; for(int index=0; index<seq.getLength(); index++) {
1407 * if(jalview.util.Comparison.isGap(seq.getCharAt(index))) continue;
1409 * atomNo = mappings[j].getAtomNum(seq.findPosition(index)); col =
1410 * java.awt.Color.white; if (atomNo > 0) { col = sl.getColour(atomNo,
1411 * mappings[j].getPDBResNum(index), mappings[j].pdbchain,
1412 * mappings[j].pdbfile); }
1414 * annotations[index] = new Annotation("X",null,' ',0,col); } return
1415 * annotations; } } } }
1417 * return annotations;
1421 public void structureSelectionChanged()
1425 public void sequenceSelectionChanged()
1429 public void sequenceColoursChanged(Object source)
1431 StructureListener sl;
1432 for (int i = 0; i < listeners.size(); i++)
1434 if (listeners.elementAt(i) instanceof StructureListener)
1436 sl = (StructureListener) listeners.elementAt(i);
1437 sl.updateColours(source);
1442 public StructureMapping[] getMapping(String pdbfile)
1444 List<StructureMapping> tmp = new ArrayList<>();
1445 for (StructureMapping sm : mappings)
1447 if (sm.pdbfile.equals(pdbfile))
1452 return tmp.toArray(new StructureMapping[tmp.size()]);
1456 * Returns a readable description of all mappings for the given pdbfile to any
1457 * of the given sequences
1463 public String printMappings(String pdbfile, List<SequenceI> seqs)
1465 if (pdbfile == null || seqs == null || seqs.isEmpty())
1470 StringBuilder sb = new StringBuilder(64);
1471 for (StructureMapping sm : mappings)
1473 if (Platform.pathEquals(sm.pdbfile, pdbfile)
1474 && seqs.contains(sm.sequence))
1476 sb.append(sm.mappingDetails);
1478 // separator makes it easier to read multiple mappings
1479 sb.append("=====================");
1485 return sb.toString();
1489 * Remove the given mapping
1493 public void deregisterMapping(AlignedCodonFrame acf)
1497 boolean removed = seqmappings.remove(acf);
1498 if (removed && seqmappings.isEmpty())
1500 jalview.bin.Console.outPrintln("All mappings removed");
1506 * Add each of the given codonFrames to the stored set, if not aready present.
1510 public void registerMappings(List<AlignedCodonFrame> mappings)
1512 if (mappings != null)
1514 for (AlignedCodonFrame acf : mappings)
1516 registerMapping(acf);
1522 * Add the given mapping to the stored set, unless already stored.
1524 public void registerMapping(AlignedCodonFrame acf)
1528 if (!seqmappings.contains(acf))
1530 seqmappings.add(acf);
1536 * Resets this object to its initial state by removing all registered
1537 * listeners, codon mappings, PDB file mappings
1539 public void resetAll()
1541 if (mappings != null)
1545 if (seqmappings != null)
1547 seqmappings.clear();
1549 if (sel_listeners != null)
1551 sel_listeners.clear();
1553 if (listeners != null)
1557 if (commandListeners != null)
1559 commandListeners.clear();
1561 if (view_listeners != null)
1563 view_listeners.clear();
1565 if (pdbFileNameId != null)
1567 pdbFileNameId.clear();
1569 if (pdbIdFileName != null)
1571 pdbIdFileName.clear();
1575 public void addSelectionListener(SelectionListener selecter)
1577 if (!sel_listeners.contains(selecter))
1579 sel_listeners.add(selecter);
1583 public void removeSelectionListener(SelectionListener toremove)
1585 if (sel_listeners.contains(toremove))
1587 sel_listeners.remove(toremove);
1591 public synchronized void sendSelection(
1592 jalview.datamodel.SequenceGroup selection,
1593 jalview.datamodel.ColumnSelection colsel, HiddenColumns hidden,
1594 SelectionSource source)
1596 for (SelectionListener slis : sel_listeners)
1600 slis.selection(selection, colsel, hidden, source);
1605 Vector<AlignmentViewPanelListener> view_listeners = new Vector<>();
1607 public synchronized void sendViewPosition(
1608 jalview.api.AlignmentViewPanel source, int startRes, int endRes,
1609 int startSeq, int endSeq)
1612 if (view_listeners != null && view_listeners.size() > 0)
1614 Enumeration<AlignmentViewPanelListener> listeners = view_listeners
1616 while (listeners.hasMoreElements())
1618 AlignmentViewPanelListener slis = listeners.nextElement();
1621 slis.viewPosition(startRes, endRes, startSeq, endSeq, source);
1629 * release all references associated with this manager provider
1631 * @param jalviewLite
1633 public static void release(StructureSelectionManagerProvider jalviewLite)
1635 // synchronized (instances)
1637 if (instances == null)
1641 StructureSelectionManager mnger = (instances.get(jalviewLite));
1644 instances.remove(jalviewLite);
1647 /* bsoares 2019-03-20 finalize deprecated, no apparent external
1648 * resources to close
1650 // mnger.finalize();
1651 } catch (Throwable x)
1658 public void registerPDBEntry(PDBEntry pdbentry)
1660 if (pdbentry.getFile() != null
1661 && pdbentry.getFile().trim().length() > 0)
1663 registerPDBFile(pdbentry.getId(), pdbentry.getFile());
1667 public void addCommandListener(CommandListener cl)
1669 if (!commandListeners.contains(cl))
1671 commandListeners.add(cl);
1675 public boolean hasCommandListener(CommandListener cl)
1677 return this.commandListeners.contains(cl);
1680 public boolean removeCommandListener(CommandListener l)
1682 return commandListeners.remove(l);
1686 * Forward a command to any command listeners (except for the command's
1690 * the command to be broadcast (in its form after being performed)
1692 * if true, the command was being 'undone'
1695 public void commandPerformed(CommandI command, boolean undo,
1696 VamsasSource source)
1698 for (CommandListener listener : commandListeners)
1700 listener.mirrorCommand(command, undo, this, source);
1705 * Returns a new CommandI representing the given command as mapped to the
1706 * given sequences. If no mapping could be made, or the command is not of a
1707 * mappable kind, returns null.
1715 public CommandI mapCommand(CommandI command, boolean undo,
1716 final AlignmentI mapTo, char gapChar)
1718 if (command instanceof EditCommand)
1720 return MappingUtils.mapEditCommand((EditCommand) command, undo, mapTo,
1721 gapChar, seqmappings);
1723 else if (command instanceof OrderCommand)
1725 return MappingUtils.mapOrderCommand((OrderCommand) command, undo,
1726 mapTo, seqmappings);
1731 public List<AlignedCodonFrame> getSequenceMappings()
1737 * quick and dirty route to just highlight all structure positions for a range
1740 * @param sequencesArray
1742 * start-end columns on sequencesArray
1744 * origin parent AlignmentPanel
1746 public void highlightPositionsOnMany(SequenceI[] sequencesArray, int[] is,
1749 for (int i = 0; i < listeners.size(); i++)
1751 Object listener = listeners.elementAt(i);
1752 if (listener == source)
1754 // TODO listener (e.g. SeqPanel) is never == source (AlignViewport)
1755 // Temporary fudge with SequenceListener.getVamsasSource()
1758 if (listener instanceof StructureListener)
1760 highlightStructureRegionsFor((StructureListener) listener,
1761 sequencesArray, is);
1766 public Map<String, String> getPdbFileNameIdMap()
1768 return pdbFileNameId;
1771 public Map<String, String> getPdbIdFileNameMap()
1773 return pdbIdFileName;