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