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