2c93ee52d0f156c7de70fca6871c48163084067b
[jalview.git] / src / jalview / project / Jalview2XML.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.project;
22
23 import static jalview.math.RotatableMatrix.Axis.X;
24 import static jalview.math.RotatableMatrix.Axis.Y;
25 import static jalview.math.RotatableMatrix.Axis.Z;
26
27 import jalview.analysis.Conservation;
28 import jalview.analysis.PCA;
29 import jalview.analysis.scoremodels.ScoreModels;
30 import jalview.analysis.scoremodels.SimilarityParams;
31 import jalview.api.AlignmentViewPanel;
32 import jalview.api.FeatureColourI;
33 import jalview.api.ViewStyleI;
34 import jalview.api.analysis.ScoreModelI;
35 import jalview.api.analysis.SimilarityParamsI;
36 import jalview.api.structures.JalviewStructureDisplayI;
37 import jalview.bin.Cache;
38 import jalview.bin.Jalview;
39 import jalview.datamodel.AlignedCodonFrame;
40 import jalview.datamodel.Alignment;
41 import jalview.datamodel.AlignmentAnnotation;
42 import jalview.datamodel.AlignmentI;
43 import jalview.datamodel.DBRefEntry;
44 import jalview.datamodel.GraphLine;
45 import jalview.datamodel.PDBEntry;
46 import jalview.datamodel.Point;
47 import jalview.datamodel.RnaViewerModel;
48 import jalview.datamodel.SequenceFeature;
49 import jalview.datamodel.SequenceGroup;
50 import jalview.datamodel.SequenceI;
51 import jalview.datamodel.StructureViewerModel;
52 import jalview.datamodel.StructureViewerModel.StructureData;
53 import jalview.datamodel.features.FeatureMatcher;
54 import jalview.datamodel.features.FeatureMatcherI;
55 import jalview.datamodel.features.FeatureMatcherSet;
56 import jalview.datamodel.features.FeatureMatcherSetI;
57 import jalview.ext.varna.RnaModel;
58 import jalview.gui.AlignFrame;
59 import jalview.gui.AlignViewport;
60 import jalview.gui.AlignmentPanel;
61 import jalview.gui.AppVarna;
62 import jalview.gui.ChimeraViewFrame;
63 import jalview.gui.Desktop;
64 import jalview.gui.FeatureRenderer;
65 import jalview.gui.JvOptionPane;
66 import jalview.gui.OOMWarning;
67 import jalview.gui.PCAPanel;
68 import jalview.gui.PaintRefresher;
69 import jalview.gui.SplitFrame;
70 import jalview.gui.StructureViewer;
71 import jalview.gui.StructureViewer.ViewerType;
72 import jalview.gui.StructureViewerBase;
73 import jalview.gui.TreePanel;
74 import jalview.io.BackupFiles;
75 import jalview.io.DataSourceType;
76 import jalview.io.FileFormat;
77 import jalview.io.NewickFile;
78 import jalview.math.Matrix;
79 import jalview.math.MatrixI;
80 import jalview.renderer.ResidueShaderI;
81 import jalview.schemes.AnnotationColourGradient;
82 import jalview.schemes.ColourSchemeI;
83 import jalview.schemes.ColourSchemeProperty;
84 import jalview.schemes.FeatureColour;
85 import jalview.schemes.ResidueProperties;
86 import jalview.schemes.UserColourScheme;
87 import jalview.structures.models.AAStructureBindingModel;
88 import jalview.util.Format;
89 import jalview.util.MessageManager;
90 import jalview.util.Platform;
91 import jalview.util.StringUtils;
92 import jalview.util.jarInputStreamProvider;
93 import jalview.util.matcher.Condition;
94 import jalview.viewmodel.AlignmentViewport;
95 import jalview.viewmodel.PCAModel;
96 import jalview.viewmodel.ViewportRanges;
97 import jalview.viewmodel.seqfeatures.FeatureRendererSettings;
98 import jalview.viewmodel.seqfeatures.FeaturesDisplayed;
99 import jalview.ws.jws2.Jws2Discoverer;
100 import jalview.ws.jws2.dm.AAConSettings;
101 import jalview.ws.jws2.jabaws2.Jws2Instance;
102 import jalview.ws.params.ArgumentI;
103 import jalview.ws.params.AutoCalcSetting;
104 import jalview.ws.params.WsParamSetI;
105 import jalview.xml.binding.jalview.AlcodonFrame;
106 import jalview.xml.binding.jalview.AlcodonFrame.AlcodMap;
107 import jalview.xml.binding.jalview.Annotation;
108 import jalview.xml.binding.jalview.Annotation.ThresholdLine;
109 import jalview.xml.binding.jalview.AnnotationColourScheme;
110 import jalview.xml.binding.jalview.AnnotationElement;
111 import jalview.xml.binding.jalview.DoubleMatrix;
112 import jalview.xml.binding.jalview.DoubleVector;
113 import jalview.xml.binding.jalview.Feature;
114 import jalview.xml.binding.jalview.Feature.OtherData;
115 import jalview.xml.binding.jalview.FeatureMatcherSet.CompoundMatcher;
116 import jalview.xml.binding.jalview.FilterBy;
117 import jalview.xml.binding.jalview.JalviewModel;
118 import jalview.xml.binding.jalview.JalviewModel.FeatureSettings;
119 import jalview.xml.binding.jalview.JalviewModel.FeatureSettings.Group;
120 import jalview.xml.binding.jalview.JalviewModel.FeatureSettings.Setting;
121 import jalview.xml.binding.jalview.JalviewModel.JGroup;
122 import jalview.xml.binding.jalview.JalviewModel.JSeq;
123 import jalview.xml.binding.jalview.JalviewModel.JSeq.Pdbids;
124 import jalview.xml.binding.jalview.JalviewModel.JSeq.Pdbids.StructureState;
125 import jalview.xml.binding.jalview.JalviewModel.JSeq.RnaViewer;
126 import jalview.xml.binding.jalview.JalviewModel.JSeq.RnaViewer.SecondaryStructure;
127 import jalview.xml.binding.jalview.JalviewModel.PcaViewer;
128 import jalview.xml.binding.jalview.JalviewModel.PcaViewer.Axis;
129 import jalview.xml.binding.jalview.JalviewModel.PcaViewer.SeqPointMax;
130 import jalview.xml.binding.jalview.JalviewModel.PcaViewer.SeqPointMin;
131 import jalview.xml.binding.jalview.JalviewModel.PcaViewer.SequencePoint;
132 import jalview.xml.binding.jalview.JalviewModel.Tree;
133 import jalview.xml.binding.jalview.JalviewModel.UserColours;
134 import jalview.xml.binding.jalview.JalviewModel.Viewport;
135 import jalview.xml.binding.jalview.JalviewModel.Viewport.CalcIdParam;
136 import jalview.xml.binding.jalview.JalviewModel.Viewport.HiddenColumns;
137 import jalview.xml.binding.jalview.JalviewUserColours;
138 import jalview.xml.binding.jalview.JalviewUserColours.Colour;
139 import jalview.xml.binding.jalview.MapListType.MapListFrom;
140 import jalview.xml.binding.jalview.MapListType.MapListTo;
141 import jalview.xml.binding.jalview.Mapping;
142 import jalview.xml.binding.jalview.NoValueColour;
143 import jalview.xml.binding.jalview.ObjectFactory;
144 import jalview.xml.binding.jalview.PcaDataType;
145 import jalview.xml.binding.jalview.Pdbentry.Property;
146 import jalview.xml.binding.jalview.Sequence;
147 import jalview.xml.binding.jalview.Sequence.DBRef;
148 import jalview.xml.binding.jalview.SequenceSet;
149 import jalview.xml.binding.jalview.SequenceSet.SequenceSetProperties;
150 import jalview.xml.binding.jalview.ThresholdType;
151 import jalview.xml.binding.jalview.VAMSAS;
152
153 import java.awt.Color;
154 import java.awt.Dimension;
155 import java.awt.Font;
156 import java.awt.Rectangle;
157 import java.io.BufferedReader;
158 import java.io.ByteArrayInputStream;
159 import java.io.DataInputStream;
160 import java.io.DataOutputStream;
161 import java.io.File;
162 import java.io.FileInputStream;
163 import java.io.FileOutputStream;
164 import java.io.IOException;
165 import java.io.InputStream;
166 import java.io.InputStreamReader;
167 import java.io.OutputStreamWriter;
168 import java.io.PrintWriter;
169 import java.math.BigInteger;
170 import java.net.MalformedURLException;
171 import java.net.URL;
172 import java.util.ArrayList;
173 import java.util.Arrays;
174 import java.util.Collections;
175 import java.util.Enumeration;
176 import java.util.GregorianCalendar;
177 import java.util.HashMap;
178 import java.util.HashSet;
179 import java.util.Hashtable;
180 import java.util.IdentityHashMap;
181 import java.util.Iterator;
182 import java.util.LinkedHashMap;
183 import java.util.List;
184 import java.util.Map;
185 import java.util.Map.Entry;
186 import java.util.Set;
187 import java.util.Vector;
188 import java.util.jar.JarEntry;
189 import java.util.jar.JarInputStream;
190 import java.util.jar.JarOutputStream;
191
192 import javax.swing.JInternalFrame;
193 import javax.xml.bind.JAXBContext;
194 import javax.xml.bind.JAXBElement;
195 import javax.xml.bind.Marshaller;
196 import javax.xml.datatype.DatatypeConfigurationException;
197 import javax.xml.datatype.DatatypeFactory;
198 import javax.xml.datatype.XMLGregorianCalendar;
199 import javax.xml.stream.XMLInputFactory;
200 import javax.xml.stream.XMLStreamReader;
201
202 /**
203  * Write out the current jalview desktop state as a Jalview XML stream.
204  * 
205  * Note: the vamsas objects referred to here are primitive versions of the
206  * VAMSAS project schema elements - they are not the same and most likely never
207  * will be :)
208  * 
209  * @author $author$
210  * @version $Revision: 1.134 $
211  */
212 public class Jalview2XML
213 {
214
215   // BH 2018 we add the .jvp binary extension to J2S so that
216   // it will declare that binary when we do the file save from the browser
217
218   static
219   {
220     Platform.addJ2SBinaryType(".jvp?");
221   }
222
223   private static final String VIEWER_PREFIX = "viewer_";
224
225   private static final String RNA_PREFIX = "rna_";
226
227   private static final String UTF_8 = "UTF-8";
228
229   /**
230    * prefix for recovering datasets for alignments with multiple views where
231    * non-existent dataset IDs were written for some views
232    */
233   private static final String UNIQSEQSETID = "uniqueSeqSetId.";
234
235   // use this with nextCounter() to make unique names for entities
236   private int counter = 0;
237
238   /*
239    * SequenceI reference -> XML ID string in jalview XML. Populated as XML reps
240    * of sequence objects are created.
241    */
242   IdentityHashMap<SequenceI, String> seqsToIds = null;
243
244   /**
245    * jalview XML Sequence ID to jalview sequence object reference (both dataset
246    * and alignment sequences. Populated as XML reps of sequence objects are
247    * created.)
248    */
249   Map<String, SequenceI> seqRefIds = null;
250
251   Map<String, SequenceI> incompleteSeqs = null;
252
253   List<SeqFref> frefedSequence = null;
254
255   boolean raiseGUI = true; // whether errors are raised in dialog boxes or not
256
257   /*
258    * Map of reconstructed AlignFrame objects that appear to have come from
259    * SplitFrame objects (have a dna/protein complement view).
260    */
261   private Map<Viewport, AlignFrame> splitFrameCandidates = new HashMap<>();
262
263   /*
264    * Map from displayed rna structure models to their saved session state jar
265    * entry names
266    */
267   private Map<RnaModel, String> rnaSessions = new HashMap<>();
268
269   /**
270    * contains last error message (if any) encountered by XML loader.
271    */
272   String errorMessage = null;
273
274   /**
275    * flag to control whether the Jalview2XML_V1 parser should be deferred to if
276    * exceptions are raised during project XML parsing
277    */
278   public boolean attemptversion1parse = false;
279
280   /*
281    * JalviewJS only -- to allow read file bytes to be saved in the
282    * created AlignFrame, allowing File | Reload of a project file to work
283    * 
284    * BH 2019 JAL-3436
285    */
286   private File jarFile;
287
288   /**
289    * A helper method for safely using the value of an optional attribute that
290    * may be null if not present in the XML. Answers the boolean value, or false
291    * if null.
292    * 
293    * @param b
294    * @return
295    */
296   public static boolean safeBoolean(Boolean b)
297   {
298     return b == null ? false : b.booleanValue();
299   }
300
301   /**
302    * A helper method for safely using the value of an optional attribute that
303    * may be null if not present in the XML. Answers the integer value, or zero
304    * if null.
305    * 
306    * @param i
307    * @return
308    */
309   public static int safeInt(Integer i)
310   {
311     return i == null ? 0 : i.intValue();
312   }
313
314   /**
315    * A helper method for safely using the value of an optional attribute that
316    * may be null if not present in the XML. Answers the float value, or zero if
317    * null.
318    * 
319    * @param f
320    * @return
321    */
322   public static float safeFloat(Float f)
323   {
324     return f == null ? 0f : f.floatValue();
325   }
326
327   /**
328    * create/return unique hash string for sq
329    * 
330    * @param sq
331    * @return new or existing unique string for sq
332    */
333   String seqHash(SequenceI sq)
334   {
335     if (seqsToIds == null)
336     {
337       initSeqRefs();
338     }
339     if (seqsToIds.containsKey(sq))
340     {
341       return seqsToIds.get(sq);
342     }
343     else
344     {
345       // create sequential key
346       String key = "sq" + (seqsToIds.size() + 1);
347       key = makeHashCode(sq, key); // check we don't have an external reference
348       // for it already.
349       seqsToIds.put(sq, key);
350       return key;
351     }
352   }
353
354   void initSeqRefs()
355   {
356     if (seqsToIds == null)
357     {
358       seqsToIds = new IdentityHashMap<>();
359     }
360     if (seqRefIds == null)
361     {
362       seqRefIds = new HashMap<>();
363     }
364     if (incompleteSeqs == null)
365     {
366       incompleteSeqs = new HashMap<>();
367     }
368     if (frefedSequence == null)
369     {
370       frefedSequence = new ArrayList<>();
371     }
372   }
373
374   public Jalview2XML()
375   {
376   }
377
378   public Jalview2XML(boolean raiseGUI)
379   {
380     this.raiseGUI = raiseGUI;
381   }
382
383   /**
384    * base class for resolving forward references to sequences by their ID
385    * 
386    * @author jprocter
387    *
388    */
389   abstract class SeqFref
390   {
391     String sref;
392
393     String type;
394
395     public SeqFref(String _sref, String type)
396     {
397       sref = _sref;
398       this.type = type;
399     }
400
401     public String getSref()
402     {
403       return sref;
404     }
405
406     public SequenceI getSrefSeq()
407     {
408       return seqRefIds.get(sref);
409     }
410
411     public boolean isResolvable()
412     {
413       return seqRefIds.get(sref) != null;
414     }
415
416     public SequenceI getSrefDatasetSeq()
417     {
418       SequenceI sq = seqRefIds.get(sref);
419       if (sq != null)
420       {
421         while (sq.getDatasetSequence() != null)
422         {
423           sq = sq.getDatasetSequence();
424         }
425       }
426       return sq;
427     }
428
429     /**
430      * @return true if the forward reference was fully resolved
431      */
432     abstract boolean resolve();
433
434     @Override
435     public String toString()
436     {
437       return type + " reference to " + sref;
438     }
439   }
440
441   /**
442    * create forward reference for a mapping
443    * 
444    * @param sref
445    * @param _jmap
446    * @return
447    */
448   protected SeqFref newMappingRef(final String sref,
449           final jalview.datamodel.Mapping _jmap)
450   {
451     SeqFref fref = new SeqFref(sref, "Mapping")
452     {
453       public jalview.datamodel.Mapping jmap = _jmap;
454
455       @Override
456       boolean resolve()
457       {
458         SequenceI seq = getSrefDatasetSeq();
459         if (seq == null)
460         {
461           return false;
462         }
463         jmap.setTo(seq);
464         return true;
465       }
466     };
467     return fref;
468   }
469
470   protected SeqFref newAlcodMapRef(final String sref,
471           final AlignedCodonFrame _cf,
472           final jalview.datamodel.Mapping _jmap)
473   {
474
475     SeqFref fref = new SeqFref(sref, "Codon Frame")
476     {
477       AlignedCodonFrame cf = _cf;
478
479       public jalview.datamodel.Mapping mp = _jmap;
480
481       @Override
482       public boolean isResolvable()
483       {
484         return super.isResolvable() && mp.getTo() != null;
485       }
486
487       @Override
488       boolean resolve()
489       {
490         SequenceI seq = getSrefDatasetSeq();
491         if (seq == null)
492         {
493           return false;
494         }
495         cf.addMap(seq, mp.getTo(), mp.getMap());
496         return true;
497       }
498     };
499     return fref;
500   }
501
502   protected void resolveFrefedSequences()
503   {
504     Iterator<SeqFref> nextFref = frefedSequence.iterator();
505     int toresolve = frefedSequence.size();
506     int unresolved = 0, failedtoresolve = 0;
507     while (nextFref.hasNext())
508     {
509       SeqFref ref = nextFref.next();
510       if (ref.isResolvable())
511       {
512         try
513         {
514           if (ref.resolve())
515           {
516             nextFref.remove();
517           }
518           else
519           {
520             failedtoresolve++;
521           }
522         } catch (Exception x)
523         {
524           System.err.println(
525                   "IMPLEMENTATION ERROR: Failed to resolve forward reference for sequence "
526                           + ref.getSref());
527           x.printStackTrace();
528           failedtoresolve++;
529         }
530       }
531       else
532       {
533         unresolved++;
534       }
535     }
536     if (unresolved > 0)
537     {
538       System.err.println("Jalview Project Import: There were " + unresolved
539               + " forward references left unresolved on the stack.");
540     }
541     if (failedtoresolve > 0)
542     {
543       System.err.println("SERIOUS! " + failedtoresolve
544               + " resolvable forward references failed to resolve.");
545     }
546     if (incompleteSeqs != null && incompleteSeqs.size() > 0)
547     {
548       System.err.println(
549               "Jalview Project Import: There are " + incompleteSeqs.size()
550                       + " sequences which may have incomplete metadata.");
551       if (incompleteSeqs.size() < 10)
552       {
553         for (SequenceI s : incompleteSeqs.values())
554         {
555           System.err.println(s.toString());
556         }
557       }
558       else
559       {
560         System.err.println(
561                 "Too many to report. Skipping output of incomplete sequences.");
562       }
563     }
564   }
565
566   /**
567    * This maintains a map of viewports, the key being the seqSetId. Important to
568    * set historyItem and redoList for multiple views
569    */
570   Map<String, AlignViewport> viewportsAdded = new HashMap<>();
571
572   Map<String, AlignmentAnnotation> annotationIds = new HashMap<>();
573
574   String uniqueSetSuffix = "";
575
576   /**
577    * List of pdbfiles added to Jar
578    */
579   List<String> pdbfiles = null;
580
581   // SAVES SEVERAL ALIGNMENT WINDOWS TO SAME JARFILE
582   public void saveState(File statefile)
583   {
584     FileOutputStream fos = null;
585
586     try
587     {
588
589       fos = new FileOutputStream(statefile);
590
591       JarOutputStream jout = new JarOutputStream(fos);
592       saveState(jout);
593       fos.close();
594
595     } catch (Exception e)
596     {
597       Cache.log.error("Couln't write Jalview state to " + statefile, e);
598       // TODO: inform user of the problem - they need to know if their data was
599       // not saved !
600       if (errorMessage == null)
601       {
602         errorMessage = "Did't write Jalview Archive to output file '"
603                 + statefile + "' - See console error log for details";
604       }
605       else
606       {
607         errorMessage += "(Didn't write Jalview Archive to output file '"
608                 + statefile + ")";
609       }
610       e.printStackTrace();
611     } finally
612     {
613       if (fos != null)
614       {
615         try
616         {
617           fos.close();
618         } catch (IOException e)
619         {
620           // ignore
621         }
622       }
623     }
624     reportErrors();
625   }
626
627   /**
628    * Writes a jalview project archive to the given Jar output stream.
629    * 
630    * @param jout
631    */
632   public void saveState(JarOutputStream jout)
633   {
634     AlignFrame[] frames = Desktop.getAlignFrames();
635
636     if (frames == null)
637     {
638       return;
639     }
640     saveAllFrames(Arrays.asList(frames), jout);
641   }
642
643   /**
644    * core method for storing state for a set of AlignFrames.
645    * 
646    * @param frames
647    *          - frames involving all data to be exported (including those
648    *          contained in splitframes, though not the split frames themselves)
649    * @param jout
650    *          - project output stream
651    */
652   private void saveAllFrames(List<AlignFrame> frames, JarOutputStream jout)
653   {
654
655     Hashtable<String, AlignFrame> dsses = new Hashtable<>();
656
657     /*
658      * ensure cached data is clear before starting
659      */
660     // todo tidy up seqRefIds, seqsToIds initialisation / reset
661     rnaSessions.clear();
662     splitFrameCandidates.clear();
663
664     try
665     {
666
667       // NOTE UTF-8 MUST BE USED FOR WRITING UNICODE CHARS
668       // //////////////////////////////////////////////////
669
670       List<String> shortNames = new ArrayList<>();
671       List<String> viewIds = new ArrayList<>();
672
673       // REVERSE ORDER
674       for (int i = frames.size() - 1; i > -1; i--)
675       {
676         AlignFrame af = frames.get(i);
677         AlignViewport vp = af.getViewport();
678         // skip ?
679         if (skipList != null && skipList
680                 .containsKey(vp.getSequenceSetId()))
681         {
682           continue;
683         }
684
685         String shortName = makeFilename(af, shortNames);
686
687         AlignmentI alignment = vp.getAlignment();
688         List<? extends AlignmentViewPanel> panels = af.getAlignPanels();
689         int apSize = panels.size();
690         for (int ap = 0; ap < apSize; ap++)
691           {
692           AlignmentPanel apanel = (AlignmentPanel) panels.get(ap);
693           String fileName = apSize == 1 ? shortName : ap + shortName;
694           if (!fileName.endsWith(".xml"))
695           {
696             fileName = fileName + ".xml";
697           }
698
699           saveState(apanel, fileName, jout, viewIds);
700         }
701         if (apSize > 0)
702         {
703           // BH moved next bit out of inner loop, not that it really matters.
704           // so we are testing to make sure we actually have an alignment,
705           // apparently.
706           String dssid = getDatasetIdRef(alignment.getDataset());
707           if (!dsses.containsKey(dssid))
708           {
709             // We have not already covered this data by reference from another
710             // frame.
711             dsses.put(dssid, af);
712           }
713         }
714       }
715
716       writeDatasetFor(dsses, "" + jout.hashCode() + " " + uniqueSetSuffix,
717               jout);
718
719       try
720       {
721         jout.flush();
722       } catch (Exception foo)
723       {
724       }
725       jout.close();
726     } catch (Exception ex)
727     {
728       // TODO: inform user of the problem - they need to know if their data was
729       // not saved !
730       if (errorMessage == null)
731       {
732         errorMessage = "Couldn't write Jalview Archive - see error output for details";
733       }
734       ex.printStackTrace();
735     }
736   }
737
738   /**
739    * Generates a distinct file name, based on the title of the AlignFrame, by
740    * appending _n for increasing n until an unused name is generated. The new
741    * name (without its extension) is added to the list.
742    * 
743    * @param af
744    * @param namesUsed
745    * @return the generated name, with .xml extension
746    */
747   protected String makeFilename(AlignFrame af, List<String> namesUsed)
748   {
749     String shortName = af.getTitle();
750
751     if (shortName.indexOf(File.separatorChar) > -1)
752     {
753       shortName = shortName
754               .substring(shortName.lastIndexOf(File.separatorChar) + 1);
755     }
756
757     int count = 1;
758
759     while (namesUsed.contains(shortName))
760     {
761       if (shortName.endsWith("_" + (count - 1)))
762       {
763         shortName = shortName.substring(0, shortName.lastIndexOf("_"));
764       }
765
766       shortName = shortName.concat("_" + count);
767       count++;
768     }
769
770     namesUsed.add(shortName);
771
772     if (!shortName.endsWith(".xml"))
773     {
774       shortName = shortName + ".xml";
775     }
776     return shortName;
777   }
778
779   // USE THIS METHOD TO SAVE A SINGLE ALIGNMENT WINDOW
780   public boolean saveAlignment(AlignFrame af, String jarFile,
781           String fileName)
782   {
783     try
784     {
785       // create backupfiles object and get new temp filename destination
786       boolean doBackup = BackupFiles.getEnabled();
787       BackupFiles backupfiles = doBackup ? new BackupFiles(jarFile) : null;
788       FileOutputStream fos = new FileOutputStream(doBackup ? 
789               backupfiles.getTempFilePath() : jarFile);
790
791       JarOutputStream jout = new JarOutputStream(fos);
792       List<AlignFrame> frames = new ArrayList<>();
793
794       // resolve splitframes
795       if (af.getViewport().getCodingComplement() != null)
796       {
797         frames = ((SplitFrame) af.getSplitViewContainer()).getAlignFrames();
798       }
799       else
800       {
801         frames.add(af);
802       }
803       saveAllFrames(frames, jout);
804       try
805       {
806         jout.flush();
807       } catch (Exception foo)
808       {
809       }
810       jout.close();
811       boolean success = true;
812
813       if (doBackup)
814       {
815         backupfiles.setWriteSuccess(success);
816         success = backupfiles.rollBackupsAndRenameTempFile();
817       }
818
819       return success;
820     } catch (Exception ex)
821     {
822       errorMessage = "Couldn't Write alignment view to Jalview Archive - see error output for details";
823       ex.printStackTrace();
824       return false;
825     }
826   }
827
828   /**
829    * Each AlignFrame has a single data set associated with it. Note that none of
830    * these frames are split frames, because Desktop.getAlignFrames() collects
831    * top and bottom separately here.
832    * 
833    * @param dsses
834    * @param fileName
835    * @param jout
836    */
837   private void writeDatasetFor(Hashtable<String, AlignFrame> dsses,
838           String fileName, JarOutputStream jout)
839   {
840
841     // Note that in saveAllFrames we have associated each specific dataset to
842     // ONE of its associated frames.
843
844     for (String dssids : dsses.keySet())
845     {
846       AlignFrame _af = dsses.get(dssids);
847       String jfileName = fileName + " Dataset for " + _af.getTitle();
848       if (!jfileName.endsWith(".xml"))
849       {
850         jfileName = jfileName + ".xml";
851       }
852       saveState(_af.alignPanel, jfileName, true, jout, null);
853     }
854   }
855
856   /**
857    * create a JalviewModel from an alignment view and marshall it to a
858    * JarOutputStream
859    * 
860    * @param ap
861    *          panel to create jalview model for
862    * @param fileName
863    *          name of alignment panel written to output stream
864    * @param jout
865    *          jar output stream
866    * @param viewIds
867    * @param out
868    *          jar entry name
869    */
870   protected JalviewModel saveState(AlignmentPanel ap, String fileName,
871           JarOutputStream jout, List<String> viewIds)
872   {
873     return saveState(ap, fileName, false, jout, viewIds);
874   }
875
876   /**
877    * create a JalviewModel from an alignment view and marshall it to a
878    * JarOutputStream
879    * 
880    * @param ap
881    *          panel to create jalview model for
882    * @param fileName
883    *          name of alignment panel written to output stream
884    * @param storeDS
885    *          when true, only write the dataset for the alignment, not the data
886    *          associated with the view.
887    * @param jout
888    *          jar output stream
889    * @param out
890    *          jar entry name
891    */
892   protected JalviewModel saveState(AlignmentPanel ap, String fileName,
893           boolean storeDS, JarOutputStream jout, List<String> viewIds)
894   {
895     if (viewIds == null)
896     {
897       viewIds = new ArrayList<>();
898     }
899
900     initSeqRefs();
901
902     List<UserColourScheme> userColours = new ArrayList<>();
903
904     AlignViewport av = ap.av;
905     ViewportRanges vpRanges = av.getRanges();
906
907     final ObjectFactory objectFactory = new ObjectFactory();
908     JalviewModel object = objectFactory.createJalviewModel();
909     object.setVamsasModel(new VAMSAS());
910
911     // object.setCreationDate(new java.util.Date(System.currentTimeMillis()));
912     try
913     {
914       GregorianCalendar c = new GregorianCalendar();
915       DatatypeFactory datatypeFactory = DatatypeFactory.newInstance();
916       XMLGregorianCalendar now = datatypeFactory.newXMLGregorianCalendar(c);// gregorianCalendar);
917       object.setCreationDate(now);
918     } catch (DatatypeConfigurationException e)
919     {
920       System.err.println("error writing date: " + e.toString());
921     }
922     object.setVersion(
923             jalview.bin.Cache.getDefault("VERSION", "Development Build"));
924
925     /**
926      * rjal is full height alignment, jal is actual alignment with full metadata
927      * but excludes hidden sequences.
928      */
929     jalview.datamodel.AlignmentI rjal = av.getAlignment(), jal = rjal;
930
931     if (av.hasHiddenRows())
932     {
933       rjal = jal.getHiddenSequences().getFullAlignment();
934     }
935
936     SequenceSet vamsasSet = new SequenceSet();
937     Sequence vamsasSeq;
938     // JalviewModelSequence jms = new JalviewModelSequence();
939
940     vamsasSet.setGapChar(jal.getGapCharacter() + "");
941
942     if (jal.getDataset() != null)
943     {
944       // dataset id is the dataset's hashcode
945       vamsasSet.setDatasetId(getDatasetIdRef(jal.getDataset()));
946       if (storeDS)
947       {
948         // switch jal and the dataset
949         jal = jal.getDataset();
950         rjal = jal;
951       }
952     }
953     if (jal.getProperties() != null)
954     {
955       Enumeration en = jal.getProperties().keys();
956       while (en.hasMoreElements())
957       {
958         String key = en.nextElement().toString();
959         SequenceSetProperties ssp = new SequenceSetProperties();
960         ssp.setKey(key);
961         ssp.setValue(jal.getProperties().get(key).toString());
962         // vamsasSet.addSequenceSetProperties(ssp);
963         vamsasSet.getSequenceSetProperties().add(ssp);
964       }
965     }
966
967     JSeq jseq;
968     Set<String> calcIdSet = new HashSet<>();
969     // record the set of vamsas sequence XML POJO we create.
970     HashMap<String, Sequence> vamsasSetIds = new HashMap<>();
971     // SAVE SEQUENCES
972     for (final SequenceI jds : rjal.getSequences())
973     {
974       final SequenceI jdatasq = jds.getDatasetSequence() == null ? jds
975               : jds.getDatasetSequence();
976       String id = seqHash(jds);
977       if (vamsasSetIds.get(id) == null)
978       {
979         if (seqRefIds.get(id) != null && !storeDS)
980         {
981           // This happens for two reasons: 1. multiple views are being
982           // serialised.
983           // 2. the hashCode has collided with another sequence's code. This
984           // DOES
985           // HAPPEN! (PF00072.15.stk does this)
986           // JBPNote: Uncomment to debug writing out of files that do not read
987           // back in due to ArrayOutOfBoundExceptions.
988           // System.err.println("vamsasSeq backref: "+id+"");
989           // System.err.println(jds.getName()+"
990           // "+jds.getStart()+"-"+jds.getEnd()+" "+jds.getSequenceAsString());
991           // System.err.println("Hashcode: "+seqHash(jds));
992           // SequenceI rsq = (SequenceI) seqRefIds.get(id + "");
993           // System.err.println(rsq.getName()+"
994           // "+rsq.getStart()+"-"+rsq.getEnd()+" "+rsq.getSequenceAsString());
995           // System.err.println("Hashcode: "+seqHash(rsq));
996         }
997         else
998         {
999           vamsasSeq = createVamsasSequence(id, jds);
1000 //          vamsasSet.addSequence(vamsasSeq);
1001           vamsasSet.getSequence().add(vamsasSeq);
1002           vamsasSetIds.put(id, vamsasSeq);
1003           seqRefIds.put(id, jds);
1004         }
1005       }
1006       jseq = new JSeq();
1007       jseq.setStart(jds.getStart());
1008       jseq.setEnd(jds.getEnd());
1009       jseq.setColour(av.getSequenceColour(jds).getRGB());
1010
1011       jseq.setId(id); // jseq id should be a string not a number
1012       if (!storeDS)
1013       {
1014         // Store any sequences this sequence represents
1015         if (av.hasHiddenRows())
1016         {
1017           // use rjal, contains the full height alignment
1018           jseq.setHidden(
1019                   av.getAlignment().getHiddenSequences().isHidden(jds));
1020
1021           if (av.isHiddenRepSequence(jds))
1022           {
1023             jalview.datamodel.SequenceI[] reps = av
1024                     .getRepresentedSequences(jds).getSequencesInOrder(rjal);
1025
1026             for (int h = 0; h < reps.length; h++)
1027             {
1028               if (reps[h] != jds)
1029               {
1030                 // jseq.addHiddenSequences(rjal.findIndex(reps[h]));
1031                 jseq.getHiddenSequences().add(rjal.findIndex(reps[h]));
1032               }
1033             }
1034           }
1035         }
1036         // mark sequence as reference - if it is the reference for this view
1037         if (jal.hasSeqrep())
1038         {
1039           jseq.setViewreference(jds == jal.getSeqrep());
1040         }
1041       }
1042
1043       // TODO: omit sequence features from each alignment view's XML dump if we
1044       // are storing dataset
1045       List<SequenceFeature> sfs = jds.getSequenceFeatures();
1046       for (SequenceFeature sf : sfs)
1047       {
1048         // Features features = new Features();
1049         Feature features = new Feature();
1050
1051         features.setBegin(sf.getBegin());
1052         features.setEnd(sf.getEnd());
1053         features.setDescription(sf.getDescription());
1054         features.setType(sf.getType());
1055         features.setFeatureGroup(sf.getFeatureGroup());
1056         features.setScore(sf.getScore());
1057         if (sf.links != null)
1058         {
1059           for (int l = 0; l < sf.links.size(); l++)
1060           {
1061             OtherData keyValue = new OtherData();
1062             keyValue.setKey("LINK_" + l);
1063             keyValue.setValue(sf.links.elementAt(l).toString());
1064             // features.addOtherData(keyValue);
1065             features.getOtherData().add(keyValue);
1066           }
1067         }
1068         if (sf.otherDetails != null)
1069         {
1070           /*
1071            * save feature attributes, which may be simple strings or
1072            * map valued (have sub-attributes)
1073            */
1074           for (Entry<String, Object> entry : sf.otherDetails.entrySet())
1075           {
1076             String key = entry.getKey();
1077             Object value = entry.getValue();
1078             if (value instanceof Map<?, ?>)
1079             {
1080               for (Entry<String, Object> subAttribute : ((Map<String, Object>) value)
1081                       .entrySet())
1082               {
1083                 OtherData otherData = new OtherData();
1084                 otherData.setKey(key);
1085                 otherData.setKey2(subAttribute.getKey());
1086                 otherData.setValue(subAttribute.getValue().toString());
1087                 // features.addOtherData(otherData);
1088                 features.getOtherData().add(otherData);
1089               }
1090             }
1091             else
1092             {
1093               OtherData otherData = new OtherData();
1094               otherData.setKey(key);
1095               otherData.setValue(value.toString());
1096               // features.addOtherData(otherData);
1097               features.getOtherData().add(otherData);
1098             }
1099           }
1100         }
1101
1102         // jseq.addFeatures(features);
1103         jseq.getFeatures().add(features);
1104       }
1105
1106       if (jdatasq.getAllPDBEntries() != null)
1107       {
1108         Enumeration<PDBEntry> en = jdatasq.getAllPDBEntries().elements();
1109         while (en.hasMoreElements())
1110         {
1111           Pdbids pdb = new Pdbids();
1112           jalview.datamodel.PDBEntry entry = en.nextElement();
1113
1114           String pdbId = entry.getId();
1115           pdb.setId(pdbId);
1116           pdb.setType(entry.getType());
1117
1118           /*
1119            * Store any structure views associated with this sequence. This
1120            * section copes with duplicate entries in the project, so a dataset
1121            * only view *should* be coped with sensibly.
1122            */
1123           // This must have been loaded, is it still visible?
1124           JInternalFrame[] frames = Desktop.getDesktopPane().getAllFrames();
1125           String matchedFile = null;
1126           for (int f = frames.length - 1; f > -1; f--)
1127           {
1128             if (frames[f] instanceof StructureViewerBase)
1129             {
1130               StructureViewerBase viewFrame = (StructureViewerBase) frames[f];
1131               matchedFile = saveStructureState(ap, jds, pdb, entry, viewIds,
1132                       matchedFile, viewFrame);
1133               /*
1134                * Only store each structure viewer's state once in the project
1135                * jar. First time through only (storeDS==false)
1136                */
1137               String viewId = viewFrame.getViewId();
1138               if (!storeDS && !viewIds.contains(viewId))
1139               {
1140                 viewIds.add(viewId);
1141                 try
1142                 {
1143                   String viewerState = viewFrame.getStateInfo();
1144                   writeJarEntry(jout, getViewerJarEntryName(viewId),
1145                           viewerState.getBytes());
1146                 } catch (IOException e)
1147                 {
1148                   System.err.println(
1149                           "Error saving viewer state: " + e.getMessage());
1150                 }
1151               }
1152             }
1153           }
1154
1155           if (matchedFile != null || entry.getFile() != null)
1156           {
1157             if (entry.getFile() != null)
1158             {
1159               // use entry's file
1160               matchedFile = entry.getFile();
1161             }
1162             pdb.setFile(matchedFile); // entry.getFile());
1163             if (pdbfiles == null)
1164             {
1165               pdbfiles = new ArrayList<>();
1166             }
1167
1168             if (!pdbfiles.contains(pdbId))
1169             {
1170               pdbfiles.add(pdbId);
1171               copyFileToJar(jout, matchedFile, pdbId);
1172             }
1173           }
1174
1175           Enumeration<String> props = entry.getProperties();
1176           if (props.hasMoreElements())
1177           {
1178             // PdbentryItem item = new PdbentryItem();
1179             while (props.hasMoreElements())
1180             {
1181               Property prop = new Property();
1182               String key = props.nextElement();
1183               prop.setName(key);
1184               prop.setValue(entry.getProperty(key).toString());
1185               // item.addProperty(prop);
1186               pdb.getProperty().add(prop);
1187             }
1188             // pdb.addPdbentryItem(item);
1189           }
1190
1191           // jseq.addPdbids(pdb);
1192           jseq.getPdbids().add(pdb);
1193         }
1194       }
1195
1196       saveRnaViewers(jout, jseq, jds, viewIds, ap, storeDS);
1197
1198       // jms.addJSeq(jseq);
1199       object.getJSeq().add(jseq);
1200     }
1201
1202     if (!storeDS && av.hasHiddenRows())
1203     {
1204       jal = av.getAlignment();
1205     }
1206     // SAVE MAPPINGS
1207     // FOR DATASET
1208     if (storeDS && jal.getCodonFrames() != null)
1209     {
1210       List<AlignedCodonFrame> jac = jal.getCodonFrames();
1211       for (AlignedCodonFrame acf : jac)
1212       {
1213         AlcodonFrame alc = new AlcodonFrame();
1214         if (acf.getProtMappings() != null
1215                 && acf.getProtMappings().length > 0)
1216         {
1217           boolean hasMap = false;
1218           SequenceI[] dnas = acf.getdnaSeqs();
1219           jalview.datamodel.Mapping[] pmaps = acf.getProtMappings();
1220           for (int m = 0; m < pmaps.length; m++)
1221           {
1222             AlcodMap alcmap = new AlcodMap();
1223             alcmap.setDnasq(seqHash(dnas[m]));
1224             alcmap.setMapping(
1225                     createVamsasMapping(pmaps[m], dnas[m], null, false));
1226             // alc.addAlcodMap(alcmap);
1227             alc.getAlcodMap().add(alcmap);
1228             hasMap = true;
1229           }
1230           if (hasMap)
1231           {
1232             // vamsasSet.addAlcodonFrame(alc);
1233             vamsasSet.getAlcodonFrame().add(alc);
1234           }
1235         }
1236         // TODO: delete this ? dead code from 2.8.3->2.9 ?
1237         // {
1238         // AlcodonFrame alc = new AlcodonFrame();
1239         // vamsasSet.addAlcodonFrame(alc);
1240         // for (int p = 0; p < acf.aaWidth; p++)
1241         // {
1242         // Alcodon cmap = new Alcodon();
1243         // if (acf.codons[p] != null)
1244         // {
1245         // // Null codons indicate a gapped column in the translated peptide
1246         // // alignment.
1247         // cmap.setPos1(acf.codons[p][0]);
1248         // cmap.setPos2(acf.codons[p][1]);
1249         // cmap.setPos3(acf.codons[p][2]);
1250         // }
1251         // alc.addAlcodon(cmap);
1252         // }
1253         // if (acf.getProtMappings() != null
1254         // && acf.getProtMappings().length > 0)
1255         // {
1256         // SequenceI[] dnas = acf.getdnaSeqs();
1257         // jalview.datamodel.Mapping[] pmaps = acf.getProtMappings();
1258         // for (int m = 0; m < pmaps.length; m++)
1259         // {
1260         // AlcodMap alcmap = new AlcodMap();
1261         // alcmap.setDnasq(seqHash(dnas[m]));
1262         // alcmap.setMapping(createVamsasMapping(pmaps[m], dnas[m], null,
1263         // false));
1264         // alc.addAlcodMap(alcmap);
1265         // }
1266         // }
1267       }
1268     }
1269
1270     // SAVE TREES
1271     // /////////////////////////////////
1272     if (!storeDS && av.getCurrentTree() != null)
1273     {
1274       // FIND ANY ASSOCIATED TREES
1275       // NOT IMPLEMENTED FOR HEADLESS STATE AT PRESENT
1276       if (Desktop.getDesktopPane() != null)
1277       {
1278         JInternalFrame[] frames = Desktop.getDesktopPane().getAllFrames();
1279
1280         for (int t = 0; t < frames.length; t++)
1281         {
1282           if (frames[t] instanceof TreePanel)
1283           {
1284             TreePanel tp = (TreePanel) frames[t];
1285
1286             if (tp.getTreeCanvas().getViewport().getAlignment() == jal)
1287             {
1288               JalviewModel.Tree tree = new JalviewModel.Tree();
1289               tree.setTitle(tp.getTitle());
1290               tree.setCurrentTree((av.getCurrentTree() == tp.getTree()));
1291               tree.setNewick(tp.getTree().print());
1292               tree.setThreshold(tp.getTreeCanvas().getThreshold());
1293
1294               tree.setFitToWindow(tp.fitToWindow.getState());
1295               tree.setFontName(tp.getTreeFont().getName());
1296               tree.setFontSize(tp.getTreeFont().getSize());
1297               tree.setFontStyle(tp.getTreeFont().getStyle());
1298               tree.setMarkUnlinked(tp.placeholdersMenu.getState());
1299
1300               tree.setShowBootstrap(tp.bootstrapMenu.getState());
1301               tree.setShowDistances(tp.distanceMenu.getState());
1302
1303               tree.setHeight(tp.getHeight());
1304               tree.setWidth(tp.getWidth());
1305               tree.setXpos(tp.getX());
1306               tree.setYpos(tp.getY());
1307               tree.setId(makeHashCode(tp, null));
1308               tree.setLinkToAllViews(
1309                       tp.getTreeCanvas().isApplyToAllViews());
1310
1311               // jms.addTree(tree);
1312               object.getTree().add(tree);
1313             }
1314           }
1315         }
1316       }
1317     }
1318
1319     /*
1320      * save PCA viewers
1321      */
1322     if (!storeDS && Desktop.getDesktopPane() != null)
1323     {
1324       for (JInternalFrame frame : Desktop.getDesktopPane().getAllFrames())
1325       {
1326         if (frame instanceof PCAPanel)
1327         {
1328           PCAPanel panel = (PCAPanel) frame;
1329           if (panel.getAlignViewport().getAlignment() == jal)
1330           {
1331             savePCA(panel, object);
1332           }
1333         }
1334       }
1335     }
1336
1337     // SAVE ANNOTATIONS
1338     /**
1339      * store forward refs from an annotationRow to any groups
1340      */
1341     IdentityHashMap<SequenceGroup, String> groupRefs = new IdentityHashMap<>();
1342     if (storeDS)
1343     {
1344       for (SequenceI sq : jal.getSequences())
1345       {
1346         // Store annotation on dataset sequences only
1347         AlignmentAnnotation[] aa = sq.getAnnotation();
1348         if (aa != null && aa.length > 0)
1349         {
1350           storeAlignmentAnnotation(aa, groupRefs, av, calcIdSet, storeDS,
1351                   vamsasSet);
1352         }
1353       }
1354     }
1355     else
1356     {
1357       if (jal.getAlignmentAnnotation() != null)
1358       {
1359         // Store the annotation shown on the alignment.
1360         AlignmentAnnotation[] aa = jal.getAlignmentAnnotation();
1361         storeAlignmentAnnotation(aa, groupRefs, av, calcIdSet, storeDS,
1362                 vamsasSet);
1363       }
1364     }
1365     // SAVE GROUPS
1366     if (jal.getGroups() != null)
1367     {
1368       JGroup[] groups = new JGroup[jal.getGroups().size()];
1369       int i = -1;
1370       for (jalview.datamodel.SequenceGroup sg : jal.getGroups())
1371       {
1372         JGroup jGroup = new JGroup();
1373         groups[++i] = jGroup;
1374
1375         jGroup.setStart(sg.getStartRes());
1376         jGroup.setEnd(sg.getEndRes());
1377         jGroup.setName(sg.getName());
1378         if (groupRefs.containsKey(sg))
1379         {
1380           // group has references so set its ID field
1381           jGroup.setId(groupRefs.get(sg));
1382         }
1383         ColourSchemeI colourScheme = sg.getColourScheme();
1384         if (colourScheme != null)
1385         {
1386           ResidueShaderI groupColourScheme = sg.getGroupColourScheme();
1387           if (groupColourScheme.conservationApplied())
1388           {
1389             jGroup.setConsThreshold(groupColourScheme.getConservationInc());
1390
1391             if (colourScheme instanceof jalview.schemes.UserColourScheme)
1392             {
1393               jGroup.setColour(
1394                       setUserColourScheme(colourScheme, userColours,
1395                               object));
1396             }
1397             else
1398             {
1399               jGroup.setColour(colourScheme.getSchemeName());
1400             }
1401           }
1402           else if (colourScheme instanceof jalview.schemes.AnnotationColourGradient)
1403           {
1404             jGroup.setColour("AnnotationColourGradient");
1405             jGroup.setAnnotationColours(constructAnnotationColours(
1406                     (jalview.schemes.AnnotationColourGradient) colourScheme,
1407                     userColours, object));
1408           }
1409           else if (colourScheme instanceof jalview.schemes.UserColourScheme)
1410           {
1411             jGroup.setColour(
1412                     setUserColourScheme(colourScheme, userColours, object));
1413           }
1414           else
1415           {
1416             jGroup.setColour(colourScheme.getSchemeName());
1417           }
1418
1419           jGroup.setPidThreshold(groupColourScheme.getThreshold());
1420         }
1421
1422         jGroup.setOutlineColour(sg.getOutlineColour().getRGB());
1423         jGroup.setDisplayBoxes(sg.getDisplayBoxes());
1424         jGroup.setDisplayText(sg.getDisplayText());
1425         jGroup.setColourText(sg.getColourText());
1426         jGroup.setTextCol1(sg.textColour.getRGB());
1427         jGroup.setTextCol2(sg.textColour2.getRGB());
1428         jGroup.setTextColThreshold(sg.thresholdTextColour);
1429         jGroup.setShowUnconserved(sg.getShowNonconserved());
1430         jGroup.setIgnoreGapsinConsensus(sg.getIgnoreGapsConsensus());
1431         jGroup.setShowConsensusHistogram(sg.isShowConsensusHistogram());
1432         jGroup.setShowSequenceLogo(sg.isShowSequenceLogo());
1433         jGroup.setNormaliseSequenceLogo(sg.isNormaliseSequenceLogo());
1434         for (SequenceI seq : sg.getSequences())
1435         {
1436           // jGroup.addSeq(seqHash(seq));
1437           jGroup.getSeq().add(seqHash(seq));
1438         }
1439       }
1440
1441       //jms.setJGroup(groups);
1442       Object group;
1443       for (JGroup grp : groups)
1444       {
1445         object.getJGroup().add(grp);
1446       }
1447     }
1448     if (!storeDS)
1449     {
1450       // /////////SAVE VIEWPORT
1451       Viewport view = new Viewport();
1452       view.setTitle(ap.alignFrame.getTitle());
1453       view.setSequenceSetId(
1454               makeHashCode(av.getSequenceSetId(), av.getSequenceSetId()));
1455       view.setId(av.getViewId());
1456       if (av.getCodingComplement() != null)
1457       {
1458         view.setComplementId(av.getCodingComplement().getViewId());
1459       }
1460       view.setViewName(av.getViewName());
1461       view.setGatheredViews(av.isGatherViewsHere());
1462
1463       Rectangle size = ap.av.getExplodedGeometry();
1464       Rectangle position = size;
1465       if (size == null)
1466       {
1467         size = ap.alignFrame.getBounds();
1468         if (av.getCodingComplement() != null)
1469         {
1470           position = ((SplitFrame) ap.alignFrame.getSplitViewContainer())
1471                   .getBounds();
1472         }
1473         else
1474         {
1475           position = size;
1476         }
1477       }
1478       view.setXpos(position.x);
1479       view.setYpos(position.y);
1480
1481       view.setWidth(size.width);
1482       view.setHeight(size.height);
1483
1484       view.setStartRes(vpRanges.getStartRes());
1485       view.setStartSeq(vpRanges.getStartSeq());
1486
1487       if (av.getGlobalColourScheme() instanceof jalview.schemes.UserColourScheme)
1488       {
1489         view.setBgColour(setUserColourScheme(av.getGlobalColourScheme(),
1490                 userColours, object));
1491       }
1492       else if (av
1493               .getGlobalColourScheme() instanceof jalview.schemes.AnnotationColourGradient)
1494       {
1495         AnnotationColourScheme ac = constructAnnotationColours(
1496                 (jalview.schemes.AnnotationColourGradient) av
1497                         .getGlobalColourScheme(),
1498                 userColours, object);
1499
1500         view.setAnnotationColours(ac);
1501         view.setBgColour("AnnotationColourGradient");
1502       }
1503       else
1504       {
1505         view.setBgColour(ColourSchemeProperty
1506                 .getColourName(av.getGlobalColourScheme()));
1507       }
1508
1509       ResidueShaderI vcs = av.getResidueShading();
1510       ColourSchemeI cs = av.getGlobalColourScheme();
1511
1512       if (cs != null)
1513       {
1514         if (vcs.conservationApplied())
1515         {
1516           view.setConsThreshold(vcs.getConservationInc());
1517           if (cs instanceof jalview.schemes.UserColourScheme)
1518           {
1519             view.setBgColour(setUserColourScheme(cs, userColours, object));
1520           }
1521         }
1522         view.setPidThreshold(vcs.getThreshold());
1523       }
1524
1525       view.setConservationSelected(av.getConservationSelected());
1526       view.setPidSelected(av.getAbovePIDThreshold());
1527       final Font font = av.getFont();
1528       view.setFontName(font.getName());
1529       view.setFontSize(font.getSize());
1530       view.setFontStyle(font.getStyle());
1531       view.setScaleProteinAsCdna(av.getViewStyle().isScaleProteinAsCdna());
1532       view.setRenderGaps(av.isRenderGaps());
1533       view.setShowAnnotation(av.isShowAnnotation());
1534       view.setShowBoxes(av.getShowBoxes());
1535       view.setShowColourText(av.getColourText());
1536       view.setShowFullId(av.getShowJVSuffix());
1537       view.setRightAlignIds(av.isRightAlignIds());
1538       view.setShowSequenceFeatures(av.isShowSequenceFeatures());
1539       view.setShowText(av.getShowText());
1540       view.setShowUnconserved(av.getShowUnconserved());
1541       view.setWrapAlignment(av.getWrapAlignment());
1542       view.setTextCol1(av.getTextColour().getRGB());
1543       view.setTextCol2(av.getTextColour2().getRGB());
1544       view.setTextColThreshold(av.getThresholdTextColour());
1545       view.setShowConsensusHistogram(av.isShowConsensusHistogram());
1546       view.setShowSequenceLogo(av.isShowSequenceLogo());
1547       view.setNormaliseSequenceLogo(av.isNormaliseSequenceLogo());
1548       view.setShowGroupConsensus(av.isShowGroupConsensus());
1549       view.setShowGroupConservation(av.isShowGroupConservation());
1550       view.setShowNPfeatureTooltip(av.isShowNPFeats());
1551       view.setShowDbRefTooltip(av.isShowDBRefs());
1552       view.setFollowHighlight(av.isFollowHighlight());
1553       view.setFollowSelection(av.followSelection);
1554       view.setIgnoreGapsinConsensus(av.isIgnoreGapsConsensus());
1555       if (av.getFeaturesDisplayed() != null)
1556       {
1557         FeatureSettings fs = new FeatureSettings();
1558
1559         FeatureRenderer fr = ap.getSeqPanel().seqCanvas
1560                 .getFeatureRenderer();
1561         String[] renderOrder = fr.getRenderOrder().toArray(new String[0]);
1562
1563         Vector<String> settingsAdded = new Vector<>();
1564         if (renderOrder != null)
1565         {
1566           for (String featureType : renderOrder)
1567           {
1568             FeatureSettings.Setting setting = new FeatureSettings.Setting();
1569             setting.setType(featureType);
1570
1571             /*
1572              * save any filter for the feature type
1573              */
1574             FeatureMatcherSetI filter = fr.getFeatureFilter(featureType);
1575             if (filter != null)  {
1576               Iterator<FeatureMatcherI> filters = filter.getMatchers().iterator();
1577               FeatureMatcherI firstFilter = filters.next();
1578               setting.setMatcherSet(Jalview2XML.marshalFilter(
1579                       firstFilter, filters, filter.isAnded()));
1580             }
1581
1582             /*
1583              * save colour scheme for the feature type
1584              */
1585             FeatureColourI fcol = fr.getFeatureStyle(featureType);
1586             if (!fcol.isSimpleColour())
1587             {
1588               setting.setColour(fcol.getMaxColour().getRGB());
1589               setting.setMincolour(fcol.getMinColour().getRGB());
1590               setting.setMin(fcol.getMin());
1591               setting.setMax(fcol.getMax());
1592               setting.setColourByLabel(fcol.isColourByLabel());
1593               if (fcol.isColourByAttribute())
1594               {
1595                 String[] attName = fcol.getAttributeName();
1596                 setting.getAttributeName().add(attName[0]);
1597                 if (attName.length > 1)
1598                 {
1599                   setting.getAttributeName().add(attName[1]);
1600                 }
1601               }
1602               setting.setAutoScale(fcol.isAutoScaled());
1603               setting.setThreshold(fcol.getThreshold());
1604               Color noColour = fcol.getNoColour();
1605               if (noColour == null)
1606               {
1607                 setting.setNoValueColour(NoValueColour.NONE);
1608               }
1609               else if (noColour.equals(fcol.getMaxColour()))
1610               {
1611                 setting.setNoValueColour(NoValueColour.MAX);
1612               }
1613               else
1614               {
1615                 setting.setNoValueColour(NoValueColour.MIN);
1616               }
1617               // -1 = No threshold, 0 = Below, 1 = Above
1618               setting.setThreshstate(fcol.isAboveThreshold() ? 1
1619                       : (fcol.isBelowThreshold() ? 0 : -1));
1620             }
1621             else
1622             {
1623               setting.setColour(fcol.getColour().getRGB());
1624             }
1625
1626             setting.setDisplay(
1627                     av.getFeaturesDisplayed().isVisible(featureType));
1628             float rorder = fr
1629                     .getOrder(featureType);
1630             if (rorder > -1)
1631             {
1632               setting.setOrder(rorder);
1633             }
1634             /// fs.addSetting(setting);
1635             fs.getSetting().add(setting);
1636             settingsAdded.addElement(featureType);
1637           }
1638         }
1639
1640         // is groups actually supposed to be a map here ?
1641         Iterator<String> en = fr.getFeatureGroups().iterator();
1642         Vector<String> groupsAdded = new Vector<>();
1643         while (en.hasNext())
1644         {
1645           String grp = en.next();
1646           if (groupsAdded.contains(grp))
1647           {
1648             continue;
1649           }
1650           Group g = new Group();
1651           g.setName(grp);
1652           g.setDisplay(((Boolean) fr.checkGroupVisibility(grp, false))
1653                           .booleanValue());
1654           // fs.addGroup(g);
1655           fs.getGroup().add(g);
1656           groupsAdded.addElement(grp);
1657         }
1658         // jms.setFeatureSettings(fs);
1659         object.setFeatureSettings(fs);
1660       }
1661
1662       if (av.hasHiddenColumns())
1663       {
1664         jalview.datamodel.HiddenColumns hidden = av.getAlignment()
1665                 .getHiddenColumns();
1666         if (hidden == null)
1667         {
1668           warn("REPORT BUG: avoided null columnselection bug (DMAM reported). Please contact Jim about this.");
1669         }
1670         else
1671         {
1672           Iterator<int[]> hiddenRegions = hidden.iterator();
1673           while (hiddenRegions.hasNext())
1674           {
1675             int[] region = hiddenRegions.next();
1676             HiddenColumns hc = new HiddenColumns();
1677             hc.setStart(region[0]);
1678             hc.setEnd(region[1]);
1679             // view.addHiddenColumns(hc);
1680             view.getHiddenColumns().add(hc);
1681           }
1682         }
1683       }
1684       if (calcIdSet.size() > 0)
1685       {
1686         for (String calcId : calcIdSet)
1687         {
1688           if (calcId.trim().length() > 0)
1689           {
1690             CalcIdParam cidp = createCalcIdParam(calcId, av);
1691             // Some calcIds have no parameters.
1692             if (cidp != null)
1693             {
1694               // view.addCalcIdParam(cidp);
1695               view.getCalcIdParam().add(cidp);
1696             }
1697           }
1698         }
1699       }
1700
1701       // jms.addViewport(view);
1702       object.getViewport().add(view);
1703     }
1704     // object.setJalviewModelSequence(jms);
1705     // object.getVamsasModel().addSequenceSet(vamsasSet);
1706     object.getVamsasModel().getSequenceSet().add(vamsasSet);
1707
1708     if (jout != null && fileName != null)
1709     {
1710       // We may not want to write the object to disk,
1711       // eg we can copy the alignViewport to a new view object
1712       // using save and then load
1713       try
1714       {
1715         fileName = fileName.replace('\\', '/');
1716         System.out.println("Writing jar entry " + fileName);
1717         JarEntry entry = new JarEntry(fileName);
1718         jout.putNextEntry(entry);
1719         PrintWriter pout = new PrintWriter(
1720                 new OutputStreamWriter(jout, UTF_8));
1721         JAXBContext jaxbContext = JAXBContext
1722                 .newInstance(JalviewModel.class);
1723         Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
1724
1725         // output pretty printed
1726         // jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
1727         jaxbMarshaller.marshal(
1728                 new ObjectFactory().createJalviewModel(object), pout);
1729
1730         // jaxbMarshaller.marshal(object, pout);
1731         // marshaller.marshal(object);
1732         pout.flush();
1733         jout.closeEntry();
1734       } catch (Exception ex)
1735       {
1736         // TODO: raise error in GUI if marshalling failed.
1737         System.err.println("Error writing Jalview project");
1738         ex.printStackTrace();
1739       }
1740     }
1741     return object;
1742   }
1743
1744   /**
1745    * Writes PCA viewer attributes and computed values to an XML model object and
1746    * adds it to the JalviewModel. Any exceptions are reported by logging.
1747    */
1748   protected void savePCA(PCAPanel panel, JalviewModel object)
1749   {
1750     try
1751     {
1752       PcaViewer viewer = new PcaViewer();
1753       viewer.setHeight(panel.getHeight());
1754       viewer.setWidth(panel.getWidth());
1755       viewer.setXpos(panel.getX());
1756       viewer.setYpos(panel.getY());
1757       viewer.setTitle(panel.getTitle());
1758       PCAModel pcaModel = panel.getPcaModel();
1759       viewer.setScoreModelName(pcaModel.getScoreModelName());
1760       viewer.setXDim(panel.getSelectedDimensionIndex(X));
1761       viewer.setYDim(panel.getSelectedDimensionIndex(Y));
1762       viewer.setZDim(panel.getSelectedDimensionIndex(Z));
1763       viewer.setBgColour(
1764               panel.getRotatableCanvas().getBackgroundColour().getRGB());
1765       viewer.setScaleFactor(panel.getRotatableCanvas().getScaleFactor());
1766       float[] spMin = panel.getRotatableCanvas().getSeqMin();
1767       SeqPointMin spmin = new SeqPointMin();
1768       spmin.setXPos(spMin[0]);
1769       spmin.setYPos(spMin[1]);
1770       spmin.setZPos(spMin[2]);
1771       viewer.setSeqPointMin(spmin);
1772       float[] spMax = panel.getRotatableCanvas().getSeqMax();
1773       SeqPointMax spmax = new SeqPointMax();
1774       spmax.setXPos(spMax[0]);
1775       spmax.setYPos(spMax[1]);
1776       spmax.setZPos(spMax[2]);
1777       viewer.setSeqPointMax(spmax);
1778       viewer.setShowLabels(panel.getRotatableCanvas().isShowLabels());
1779       viewer.setLinkToAllViews(
1780               panel.getRotatableCanvas().isApplyToAllViews());
1781       SimilarityParamsI sp = pcaModel.getSimilarityParameters();
1782       viewer.setIncludeGaps(sp.includeGaps());
1783       viewer.setMatchGaps(sp.matchGaps());
1784       viewer.setIncludeGappedColumns(sp.includeGappedColumns());
1785       viewer.setDenominateByShortestLength(sp.denominateByShortestLength());
1786
1787       /*
1788        * sequence points on display
1789        */
1790       for (jalview.datamodel.SequencePoint spt : pcaModel
1791               .getSequencePoints())
1792       {
1793         SequencePoint point = new SequencePoint();
1794         point.setSequenceRef(seqHash(spt.getSequence()));
1795         point.setXPos(spt.coord.x);
1796         point.setYPos(spt.coord.y);
1797         point.setZPos(spt.coord.z);
1798         viewer.getSequencePoint().add(point);
1799       }
1800
1801       /*
1802        * (end points of) axes on display
1803        */
1804       for (Point p : panel.getRotatableCanvas().getAxisEndPoints())
1805       {
1806
1807         Axis axis = new Axis();
1808         axis.setXPos(p.x);
1809         axis.setYPos(p.y);
1810         axis.setZPos(p.z);
1811         viewer.getAxis().add(axis);
1812       }
1813
1814       /*
1815        * raw PCA data (note we are not restoring PCA inputs here -
1816        * alignment view, score model, similarity parameters)
1817        */
1818       PcaDataType data = new PcaDataType();
1819       viewer.setPcaData(data);
1820       PCA pca = pcaModel.getPcaData();
1821
1822       DoubleMatrix pm = new DoubleMatrix();
1823       saveDoubleMatrix(pca.getPairwiseScores(), pm);
1824       data.setPairwiseMatrix(pm);
1825
1826       DoubleMatrix tm = new DoubleMatrix();
1827       saveDoubleMatrix(pca.getTridiagonal(), tm);
1828       data.setTridiagonalMatrix(tm);
1829
1830       DoubleMatrix eigenMatrix = new DoubleMatrix();
1831       data.setEigenMatrix(eigenMatrix);
1832       saveDoubleMatrix(pca.getEigenmatrix(), eigenMatrix);
1833
1834       object.getPcaViewer().add(viewer);
1835     } catch (Throwable t)
1836     {
1837       Cache.log.error("Error saving PCA: " + t.getMessage());
1838     }
1839   }
1840
1841   /**
1842    * Stores values from a matrix into an XML element, including (if present) the
1843    * D or E vectors
1844    * 
1845    * @param m
1846    * @param xmlMatrix
1847    * @see #loadDoubleMatrix(DoubleMatrix)
1848    */
1849   protected void saveDoubleMatrix(MatrixI m, DoubleMatrix xmlMatrix)
1850   {
1851     xmlMatrix.setRows(m.height());
1852     xmlMatrix.setColumns(m.width());
1853     for (int i = 0; i < m.height(); i++)
1854     {
1855       DoubleVector row = new DoubleVector();
1856       for (int j = 0; j < m.width(); j++)
1857       {
1858         row.getV().add(m.getValue(i, j));
1859       }
1860       xmlMatrix.getRow().add(row);
1861     }
1862     if (m.getD() != null)
1863     {
1864       DoubleVector dVector = new DoubleVector();
1865       for (double d : m.getD())
1866       {
1867         dVector.getV().add(d);
1868       }
1869       xmlMatrix.setD(dVector);
1870     }
1871     if (m.getE() != null)
1872     {
1873       DoubleVector eVector = new DoubleVector();
1874       for (double e : m.getE())
1875       {
1876         eVector.getV().add(e);
1877       }
1878       xmlMatrix.setE(eVector);
1879     }
1880   }
1881
1882   /**
1883    * Loads XML matrix data into a new Matrix object, including the D and/or E
1884    * vectors (if present)
1885    * 
1886    * @param mData
1887    * @return
1888    * @see Jalview2XML#saveDoubleMatrix(MatrixI, DoubleMatrix)
1889    */
1890   protected MatrixI loadDoubleMatrix(DoubleMatrix mData)
1891   {
1892     int rows = mData.getRows();
1893     double[][] vals = new double[rows][];
1894
1895     for (int i = 0; i < rows; i++)
1896     {
1897       List<Double> dVector = mData.getRow().get(i).getV();
1898       vals[i] = new double[dVector.size()];
1899       int dvi = 0;
1900       for (Double d : dVector)
1901       {
1902         vals[i][dvi++] = d;
1903       }
1904     }
1905
1906     MatrixI m = new Matrix(vals);
1907
1908     if (mData.getD() != null)
1909     {
1910       List<Double> dVector = mData.getD().getV();
1911       double[] vec = new double[dVector.size()];
1912       int dvi = 0;
1913       for (Double d : dVector)
1914       {
1915         vec[dvi++] = d;
1916       }
1917       m.setD(vec);
1918     }
1919     if (mData.getE() != null)
1920     {
1921       List<Double> dVector = mData.getE().getV();
1922       double[] vec = new double[dVector.size()];
1923       int dvi = 0;
1924       for (Double d : dVector)
1925       {
1926         vec[dvi++] = d;
1927       }
1928       m.setE(vec);
1929     }
1930
1931     return m;
1932   }
1933
1934   /**
1935    * Save any Varna viewers linked to this sequence. Writes an rnaViewer element
1936    * for each viewer, with
1937    * <ul>
1938    * <li>viewer geometry (position, size, split pane divider location)</li>
1939    * <li>index of the selected structure in the viewer (currently shows gapped
1940    * or ungapped)</li>
1941    * <li>the id of the annotation holding RNA secondary structure</li>
1942    * <li>(currently only one SS is shown per viewer, may be more in future)</li>
1943    * </ul>
1944    * Varna viewer state is also written out (in native Varna XML) to separate
1945    * project jar entries. A separate entry is written for each RNA structure
1946    * displayed, with the naming convention
1947    * <ul>
1948    * <li>rna_viewId_sequenceId_annotationId_[gapped|trimmed]</li>
1949    * </ul>
1950    * 
1951    * @param jout
1952    * @param jseq
1953    * @param jds
1954    * @param viewIds
1955    * @param ap
1956    * @param storeDataset
1957    */
1958   protected void saveRnaViewers(JarOutputStream jout, JSeq jseq,
1959           final SequenceI jds, List<String> viewIds, AlignmentPanel ap,
1960           boolean storeDataset)
1961   {
1962     if (Desktop.getDesktopPane() == null)
1963     {
1964       return;
1965     }
1966     JInternalFrame[] frames = Desktop.getDesktopPane().getAllFrames();
1967     for (int f = frames.length - 1; f > -1; f--)
1968     {
1969       if (frames[f] instanceof AppVarna)
1970       {
1971         AppVarna varna = (AppVarna) frames[f];
1972         /*
1973          * link the sequence to every viewer that is showing it and is linked to
1974          * its alignment panel
1975          */
1976         if (varna.isListeningFor(jds) && ap == varna.getAlignmentPanel())
1977         {
1978           String viewId = varna.getViewId();
1979           RnaViewer rna = new RnaViewer();
1980           rna.setViewId(viewId);
1981           rna.setTitle(varna.getTitle());
1982           rna.setXpos(varna.getX());
1983           rna.setYpos(varna.getY());
1984           rna.setWidth(varna.getWidth());
1985           rna.setHeight(varna.getHeight());
1986           rna.setDividerLocation(varna.getDividerLocation());
1987           rna.setSelectedRna(varna.getSelectedIndex());
1988           // jseq.addRnaViewer(rna);
1989           jseq.getRnaViewer().add(rna);
1990
1991           /*
1992            * Store each Varna panel's state once in the project per sequence.
1993            * First time through only (storeDataset==false)
1994            */
1995           // boolean storeSessions = false;
1996           // String sequenceViewId = viewId + seqsToIds.get(jds);
1997           // if (!storeDataset && !viewIds.contains(sequenceViewId))
1998           // {
1999           // viewIds.add(sequenceViewId);
2000           // storeSessions = true;
2001           // }
2002           for (RnaModel model : varna.getModels())
2003           {
2004             if (model.seq == jds)
2005             {
2006               /*
2007                * VARNA saves each view (sequence or alignment secondary
2008                * structure, gapped or trimmed) as a separate XML file
2009                */
2010               String jarEntryName = rnaSessions.get(model);
2011               if (jarEntryName == null)
2012               {
2013
2014                 String varnaStateFile = varna.getStateInfo(model.rna);
2015                 jarEntryName = RNA_PREFIX + viewId + "_" + nextCounter();
2016                 copyFileToJar(jout, varnaStateFile, jarEntryName);
2017                 rnaSessions.put(model, jarEntryName);
2018               }
2019               SecondaryStructure ss = new SecondaryStructure();
2020               String annotationId = varna.getAnnotation(jds).annotationId;
2021               ss.setAnnotationId(annotationId);
2022               ss.setViewerState(jarEntryName);
2023               ss.setGapped(model.gapped);
2024               ss.setTitle(model.title);
2025               // rna.addSecondaryStructure(ss);
2026               rna.getSecondaryStructure().add(ss);
2027             }
2028           }
2029         }
2030       }
2031     }
2032   }
2033
2034   /**
2035    * Copy the contents of a file to a new entry added to the output jar
2036    * 
2037    * @param jout
2038    * @param infilePath
2039    * @param jarEntryName
2040    */
2041   protected void copyFileToJar(JarOutputStream jout, String infilePath,
2042           String jarEntryName)
2043   {
2044     DataInputStream dis = null;
2045     try
2046     {
2047       File file = new File(infilePath);
2048       if (file.exists() && jout != null)
2049       {
2050         dis = new DataInputStream(new FileInputStream(file));
2051         byte[] data = new byte[(int) file.length()];
2052         dis.readFully(data);
2053         writeJarEntry(jout, jarEntryName, data);
2054       }
2055     } catch (Exception ex)
2056     {
2057       ex.printStackTrace();
2058     } finally
2059     {
2060       if (dis != null)
2061       {
2062         try
2063         {
2064           dis.close();
2065         } catch (IOException e)
2066         {
2067           // ignore
2068         }
2069       }
2070     }
2071   }
2072
2073   /**
2074    * Write the data to a new entry of given name in the output jar file
2075    * 
2076    * @param jout
2077    * @param jarEntryName
2078    * @param data
2079    * @throws IOException
2080    */
2081   protected void writeJarEntry(JarOutputStream jout, String jarEntryName,
2082           byte[] data) throws IOException
2083   {
2084     if (jout != null)
2085     {
2086       jarEntryName = jarEntryName.replace('\\','/');
2087       System.out.println("Writing jar entry " + jarEntryName);
2088       jout.putNextEntry(new JarEntry(jarEntryName));
2089       DataOutputStream dout = new DataOutputStream(jout);
2090       dout.write(data, 0, data.length);
2091       dout.flush();
2092       jout.closeEntry();
2093     }
2094   }
2095
2096   /**
2097    * Save the state of a structure viewer
2098    * 
2099    * @param ap
2100    * @param jds
2101    * @param pdb
2102    *          the archive XML element under which to save the state
2103    * @param entry
2104    * @param viewIds
2105    * @param matchedFile
2106    * @param viewFrame
2107    * @return
2108    */
2109   protected String saveStructureState(AlignmentPanel ap, SequenceI jds,
2110           Pdbids pdb, PDBEntry entry, List<String> viewIds,
2111           String matchedFile, StructureViewerBase viewFrame)
2112   {
2113     final AAStructureBindingModel bindingModel = viewFrame.getBinding();
2114
2115     /*
2116      * Look for any bindings for this viewer to the PDB file of interest
2117      * (including part matches excluding chain id)
2118      */
2119     for (int peid = 0; peid < bindingModel.getPdbCount(); peid++)
2120     {
2121       final PDBEntry pdbentry = bindingModel.getPdbEntry(peid);
2122       final String pdbId = pdbentry.getId();
2123       if (!pdbId.equals(entry.getId())
2124               && !(entry.getId().length() > 4 && entry.getId().toLowerCase()
2125                       .startsWith(pdbId.toLowerCase())))
2126       {
2127         /*
2128          * not interested in a binding to a different PDB entry here
2129          */
2130         continue;
2131       }
2132       if (matchedFile == null)
2133       {
2134         matchedFile = pdbentry.getFile();
2135       }
2136       else if (!matchedFile.equals(pdbentry.getFile()))
2137       {
2138         Cache.log.warn(
2139                 "Probably lost some PDB-Sequence mappings for this structure file (which apparently has same PDB Entry code): "
2140                         + pdbentry.getFile());
2141       }
2142       // record the
2143       // file so we
2144       // can get at it if the ID
2145       // match is ambiguous (e.g.
2146       // 1QIP==1qipA)
2147
2148       for (int smap = 0; smap < viewFrame.getBinding()
2149               .getSequence()[peid].length; smap++)
2150       {
2151         // if (jal.findIndex(jmol.jmb.sequence[peid][smap]) > -1)
2152         if (jds == viewFrame.getBinding().getSequence()[peid][smap])
2153         {
2154           StructureState state = new StructureState();
2155           state.setVisible(true);
2156           state.setXpos(viewFrame.getX());
2157           state.setYpos(viewFrame.getY());
2158           state.setWidth(viewFrame.getWidth());
2159           state.setHeight(viewFrame.getHeight());
2160           final String viewId = viewFrame.getViewId();
2161           state.setViewId(viewId);
2162           state.setAlignwithAlignPanel(viewFrame.isUsedforaligment(ap));
2163           state.setColourwithAlignPanel(viewFrame.isUsedforcolourby(ap));
2164           state.setColourByJmol(viewFrame.isColouredByViewer());
2165           state.setType(viewFrame.getViewerType().toString());
2166           // pdb.addStructureState(state);
2167           pdb.getStructureState().add(state);
2168         }
2169       }
2170     }
2171     return matchedFile;
2172   }
2173
2174   /**
2175    * Populates the AnnotationColourScheme xml for save. This captures the
2176    * settings of the options in the 'Colour by Annotation' dialog.
2177    * 
2178    * @param acg
2179    * @param userColours
2180    * @param jm
2181    * @return
2182    */
2183   private AnnotationColourScheme constructAnnotationColours(
2184           AnnotationColourGradient acg, List<UserColourScheme> userColours,
2185           JalviewModel jm)
2186   {
2187     AnnotationColourScheme ac = new AnnotationColourScheme();
2188     ac.setAboveThreshold(acg.getAboveThreshold());
2189     ac.setThreshold(acg.getAnnotationThreshold());
2190     // 2.10.2 save annotationId (unique) not annotation label
2191     ac.setAnnotation(acg.getAnnotation().annotationId);
2192     if (acg.getBaseColour() instanceof UserColourScheme)
2193     {
2194       ac.setColourScheme(
2195               setUserColourScheme(acg.getBaseColour(), userColours, jm));
2196     }
2197     else
2198     {
2199       ac.setColourScheme(
2200               ColourSchemeProperty.getColourName(acg.getBaseColour()));
2201     }
2202
2203     ac.setMaxColour(acg.getMaxColour().getRGB());
2204     ac.setMinColour(acg.getMinColour().getRGB());
2205     ac.setPerSequence(acg.isSeqAssociated());
2206     ac.setPredefinedColours(acg.isPredefinedColours());
2207     return ac;
2208   }
2209
2210   private void storeAlignmentAnnotation(AlignmentAnnotation[] aa,
2211           IdentityHashMap<SequenceGroup, String> groupRefs,
2212           AlignmentViewport av, Set<String> calcIdSet, boolean storeDS,
2213           SequenceSet vamsasSet)
2214   {
2215
2216     for (int i = 0; i < aa.length; i++)
2217     {
2218       Annotation an = new Annotation();
2219
2220       AlignmentAnnotation annotation = aa[i];
2221       if (annotation.annotationId != null)
2222       {
2223         annotationIds.put(annotation.annotationId, annotation);
2224       }
2225
2226       an.setId(annotation.annotationId);
2227
2228       an.setVisible(annotation.visible);
2229
2230       an.setDescription(annotation.description);
2231
2232       if (annotation.sequenceRef != null)
2233       {
2234         // 2.9 JAL-1781 xref on sequence id rather than name
2235         an.setSequenceRef(seqsToIds.get(annotation.sequenceRef));
2236       }
2237       if (annotation.groupRef != null)
2238       {
2239         String groupIdr = groupRefs.get(annotation.groupRef);
2240         if (groupIdr == null)
2241         {
2242           // make a locally unique String
2243           groupRefs.put(annotation.groupRef,
2244                   groupIdr = ("" + System.currentTimeMillis()
2245                           + annotation.groupRef.getName()
2246                           + groupRefs.size()));
2247         }
2248         an.setGroupRef(groupIdr.toString());
2249       }
2250
2251       // store all visualization attributes for annotation
2252       an.setGraphHeight(annotation.graphHeight);
2253       an.setCentreColLabels(annotation.centreColLabels);
2254       an.setScaleColLabels(annotation.scaleColLabel);
2255       an.setShowAllColLabels(annotation.showAllColLabels);
2256       an.setBelowAlignment(annotation.belowAlignment);
2257
2258       if (annotation.graph > 0)
2259       {
2260         an.setGraph(true);
2261         an.setGraphType(annotation.graph);
2262         an.setGraphGroup(annotation.graphGroup);
2263         if (annotation.getThreshold() != null)
2264         {
2265           ThresholdLine line = new ThresholdLine();
2266           line.setLabel(annotation.getThreshold().label);
2267           line.setValue(annotation.getThreshold().value);
2268           line.setColour(annotation.getThreshold().colour.getRGB());
2269           an.setThresholdLine(line);
2270         }
2271       }
2272       else
2273       {
2274         an.setGraph(false);
2275       }
2276
2277       an.setLabel(annotation.label);
2278
2279       if (annotation == av.getAlignmentQualityAnnot()
2280               || annotation == av.getAlignmentConservationAnnotation()
2281               || annotation == av.getAlignmentConsensusAnnotation()
2282               || annotation.autoCalculated)
2283       {
2284         // new way of indicating autocalculated annotation -
2285         an.setAutoCalculated(annotation.autoCalculated);
2286       }
2287       if (annotation.hasScore())
2288       {
2289         an.setScore(annotation.getScore());
2290       }
2291
2292       if (annotation.getCalcId() != null)
2293       {
2294         calcIdSet.add(annotation.getCalcId());
2295         an.setCalcId(annotation.getCalcId());
2296       }
2297       if (annotation.hasProperties())
2298       {
2299         for (String pr : annotation.getProperties())
2300         {
2301           jalview.xml.binding.jalview.Annotation.Property prop = new jalview.xml.binding.jalview.Annotation.Property();
2302           prop.setName(pr);
2303           prop.setValue(annotation.getProperty(pr));
2304           // an.addProperty(prop);
2305           an.getProperty().add(prop);
2306         }
2307       }
2308
2309       AnnotationElement ae;
2310       if (annotation.annotations != null)
2311       {
2312         an.setScoreOnly(false);
2313         for (int a = 0; a < annotation.annotations.length; a++)
2314         {
2315           if ((annotation == null) || (annotation.annotations[a] == null))
2316           {
2317             continue;
2318           }
2319
2320           ae = new AnnotationElement();
2321           if (annotation.annotations[a].description != null)
2322           {
2323             ae.setDescription(annotation.annotations[a].description);
2324           }
2325           if (annotation.annotations[a].displayCharacter != null)
2326           {
2327             ae.setDisplayCharacter(
2328                     annotation.annotations[a].displayCharacter);
2329           }
2330
2331           if (!Float.isNaN(annotation.annotations[a].value))
2332           {
2333             ae.setValue(annotation.annotations[a].value);
2334           }
2335
2336           ae.setPosition(a);
2337           if (annotation.annotations[a].secondaryStructure > ' ')
2338           {
2339             ae.setSecondaryStructure(
2340                     annotation.annotations[a].secondaryStructure + "");
2341           }
2342
2343           if (annotation.annotations[a].colour != null
2344                   && annotation.annotations[a].colour != java.awt.Color.black)
2345           {
2346             ae.setColour(annotation.annotations[a].colour.getRGB());
2347           }
2348
2349           // an.addAnnotationElement(ae);
2350           an.getAnnotationElement().add(ae);
2351           if (annotation.autoCalculated)
2352           {
2353             // only write one non-null entry into the annotation row -
2354             // sufficient to get the visualization attributes necessary to
2355             // display data
2356             continue;
2357           }
2358         }
2359       }
2360       else
2361       {
2362         an.setScoreOnly(true);
2363       }
2364       if (!storeDS || (storeDS && !annotation.autoCalculated))
2365       {
2366         // skip autocalculated annotation - these are only provided for
2367         // alignments
2368         // vamsasSet.addAnnotation(an);
2369         vamsasSet.getAnnotation().add(an);
2370       }
2371     }
2372
2373   }
2374
2375   private CalcIdParam createCalcIdParam(String calcId, AlignViewport av)
2376   {
2377     AutoCalcSetting settings = av.getCalcIdSettingsFor(calcId);
2378     if (settings != null)
2379     {
2380       CalcIdParam vCalcIdParam = new CalcIdParam();
2381       vCalcIdParam.setCalcId(calcId);
2382       // vCalcIdParam.addServiceURL(settings.getServiceURI());
2383       vCalcIdParam.getServiceURL().add(settings.getServiceURI());
2384       // generic URI allowing a third party to resolve another instance of the
2385       // service used for this calculation
2386       for (String url : settings.getServiceURLs())
2387       {
2388         // vCalcIdParam.addServiceURL(urls);
2389         vCalcIdParam.getServiceURL().add(url);
2390       }
2391       vCalcIdParam.setVersion("1.0");
2392       if (settings.getPreset() != null)
2393       {
2394         WsParamSetI setting = settings.getPreset();
2395         vCalcIdParam.setName(setting.getName());
2396         vCalcIdParam.setDescription(setting.getDescription());
2397       }
2398       else
2399       {
2400         vCalcIdParam.setName("");
2401         vCalcIdParam.setDescription("Last used parameters");
2402       }
2403       // need to be able to recover 1) settings 2) user-defined presets or
2404       // recreate settings from preset 3) predefined settings provided by
2405       // service - or settings that can be transferred (or discarded)
2406       vCalcIdParam.setParameters(
2407               settings.getWsParamFile().replace("\n", "|\\n|"));
2408       vCalcIdParam.setAutoUpdate(settings.isAutoUpdate());
2409       // todo - decide if updateImmediately is needed for any projects.
2410
2411       return vCalcIdParam;
2412     }
2413     return null;
2414   }
2415
2416   private boolean recoverCalcIdParam(CalcIdParam calcIdParam,
2417           AlignViewport av)
2418   {
2419     if (calcIdParam.getVersion().equals("1.0"))
2420     {
2421       final String[] calcIds = calcIdParam.getServiceURL().toArray(new String[0]);
2422       Jws2Instance service = Jws2Discoverer.getInstance()
2423               .getPreferredServiceFor(calcIds);
2424       if (service != null)
2425       {
2426         WsParamSetI parmSet = null;
2427         try
2428         {
2429           parmSet = service.getParamStore().parseServiceParameterFile(
2430                   calcIdParam.getName(), calcIdParam.getDescription(),
2431                   calcIds,
2432                   calcIdParam.getParameters().replace("|\\n|", "\n"));
2433         } catch (IOException x)
2434         {
2435           warn("Couldn't parse parameter data for "
2436                   + calcIdParam.getCalcId(), x);
2437           return false;
2438         }
2439         List<ArgumentI> argList = null;
2440         if (calcIdParam.getName().length() > 0)
2441         {
2442           parmSet = service.getParamStore()
2443                   .getPreset(calcIdParam.getName());
2444           if (parmSet != null)
2445           {
2446             // TODO : check we have a good match with settings in AACon -
2447             // otherwise we'll need to create a new preset
2448           }
2449         }
2450         else
2451         {
2452           argList = parmSet.getArguments();
2453           parmSet = null;
2454         }
2455         AAConSettings settings = new AAConSettings(
2456                 calcIdParam.isAutoUpdate(), service, parmSet, argList);
2457         av.setCalcIdSettingsFor(calcIdParam.getCalcId(), settings,
2458                 calcIdParam.isNeedsUpdate());
2459         return true;
2460       }
2461       else
2462       {
2463         warn("Cannot resolve a service for the parameters used in this project. Try configuring a JABAWS server.");
2464         return false;
2465       }
2466     }
2467     throw new Error(MessageManager.formatMessage(
2468             "error.unsupported_version_calcIdparam", new Object[]
2469             { calcIdParam.toString() }));
2470   }
2471
2472   /**
2473    * External mapping between jalview objects and objects yielding a valid and
2474    * unique object ID string. This is null for normal Jalview project IO, but
2475    * non-null when a jalview project is being read or written as part of a
2476    * vamsas session.
2477    */
2478   IdentityHashMap jv2vobj = null;
2479
2480   /**
2481    * Construct a unique ID for jvobj using either existing bindings or if none
2482    * exist, the result of the hashcode call for the object.
2483    * 
2484    * @param jvobj
2485    *          jalview data object
2486    * @return unique ID for referring to jvobj
2487    */
2488   private String makeHashCode(Object jvobj, String altCode)
2489   {
2490     if (jv2vobj != null)
2491     {
2492       Object id = jv2vobj.get(jvobj);
2493       if (id != null)
2494       {
2495         return id.toString();
2496       }
2497       // check string ID mappings
2498       if (jvids2vobj != null && jvobj instanceof String)
2499       {
2500         id = jvids2vobj.get(jvobj);
2501       }
2502       if (id != null)
2503       {
2504         return id.toString();
2505       }
2506       // give up and warn that something has gone wrong
2507       warn("Cannot find ID for object in external mapping : " + jvobj);
2508     }
2509     return altCode;
2510   }
2511
2512   /**
2513    * return local jalview object mapped to ID, if it exists
2514    * 
2515    * @param idcode
2516    *          (may be null)
2517    * @return null or object bound to idcode
2518    */
2519   private Object retrieveExistingObj(String idcode)
2520   {
2521     if (idcode != null && vobj2jv != null)
2522     {
2523       return vobj2jv.get(idcode);
2524     }
2525     return null;
2526   }
2527
2528   /**
2529    * binding from ID strings from external mapping table to jalview data model
2530    * objects.
2531    */
2532   private Hashtable vobj2jv;
2533
2534   private Sequence createVamsasSequence(String id, SequenceI jds)
2535   {
2536     return createVamsasSequence(true, id, jds, null);
2537   }
2538
2539   private Sequence createVamsasSequence(boolean recurse, String id,
2540           SequenceI jds, SequenceI parentseq)
2541   {
2542     Sequence vamsasSeq = new Sequence();
2543     vamsasSeq.setId(id);
2544     vamsasSeq.setName(jds.getName());
2545     vamsasSeq.setSequence(jds.getSequenceAsString());
2546     vamsasSeq.setDescription(jds.getDescription());
2547     List<DBRefEntry> dbrefs = null;
2548     if (jds.getDatasetSequence() != null)
2549     {
2550       vamsasSeq.setDsseqid(seqHash(jds.getDatasetSequence()));
2551     }
2552     else
2553     {
2554       // seqId==dsseqid so we can tell which sequences really are
2555       // dataset sequences only
2556       vamsasSeq.setDsseqid(id);
2557       dbrefs = jds.getDBRefs();
2558       if (parentseq == null)
2559       {
2560         parentseq = jds;
2561       }
2562     }
2563     if (dbrefs != null)
2564     {
2565       for (int d = 0, nd = dbrefs.size(); d < nd; d++)
2566       {
2567         DBRef dbref = new DBRef();
2568         DBRefEntry ref = dbrefs.get(d);
2569         dbref.setSource(ref.getSource());
2570         dbref.setVersion(ref.getVersion());
2571         dbref.setAccessionId(ref.getAccessionId());
2572         if (ref.hasMap())
2573         {
2574           Mapping mp = createVamsasMapping(ref.getMap(), parentseq,
2575                   jds, recurse);
2576           dbref.setMapping(mp);
2577         }
2578         // vamsasSeq.addDBRef(dbref);
2579         vamsasSeq.getDBRef().add(dbref);
2580       }
2581     }
2582     return vamsasSeq;
2583   }
2584
2585   private Mapping createVamsasMapping(jalview.datamodel.Mapping jmp,
2586           SequenceI parentseq, SequenceI jds, boolean recurse)
2587   {
2588     Mapping mp = null;
2589     if (jmp.getMap() != null)
2590     {
2591       mp = new Mapping();
2592
2593       jalview.util.MapList mlst = jmp.getMap();
2594       List<int[]> r = mlst.getFromRanges();
2595       for (int[] range : r)
2596       {
2597         MapListFrom mfrom = new MapListFrom();
2598         mfrom.setStart(range[0]);
2599         mfrom.setEnd(range[1]);
2600         // mp.addMapListFrom(mfrom);
2601         mp.getMapListFrom().add(mfrom);
2602       }
2603       r = mlst.getToRanges();
2604       for (int[] range : r)
2605       {
2606         MapListTo mto = new MapListTo();
2607         mto.setStart(range[0]);
2608         mto.setEnd(range[1]);
2609         // mp.addMapListTo(mto);
2610         mp.getMapListTo().add(mto);
2611       }
2612       mp.setMapFromUnit(BigInteger.valueOf(mlst.getFromRatio()));
2613       mp.setMapToUnit(BigInteger.valueOf(mlst.getToRatio()));
2614       if (jmp.getTo() != null)
2615       {
2616         // MappingChoice mpc = new MappingChoice();
2617
2618         // check/create ID for the sequence referenced by getTo()
2619
2620         String jmpid = "";
2621         SequenceI ps = null;
2622         if (parentseq != jmp.getTo()
2623                 && parentseq.getDatasetSequence() != jmp.getTo())
2624         {
2625           // chaining dbref rather than a handshaking one
2626           jmpid = seqHash(ps = jmp.getTo());
2627         }
2628         else
2629         {
2630           jmpid = seqHash(ps = parentseq);
2631         }
2632         // mpc.setDseqFor(jmpid);
2633         mp.setDseqFor(jmpid);
2634         if (!seqRefIds.containsKey(jmpid))
2635         {
2636           jalview.bin.Cache.log.debug("creatign new DseqFor ID");
2637           seqRefIds.put(jmpid, ps);
2638         }
2639         else
2640         {
2641           jalview.bin.Cache.log.debug("reusing DseqFor ID");
2642         }
2643
2644         // mp.setMappingChoice(mpc);
2645       }
2646     }
2647     return mp;
2648   }
2649
2650   String setUserColourScheme(jalview.schemes.ColourSchemeI cs,
2651           List<UserColourScheme> userColours, JalviewModel jm)
2652   {
2653     String id = null;
2654     jalview.schemes.UserColourScheme ucs = (jalview.schemes.UserColourScheme) cs;
2655     boolean newucs = false;
2656     if (!userColours.contains(ucs))
2657     {
2658       userColours.add(ucs);
2659       newucs = true;
2660     }
2661     id = "ucs" + userColours.indexOf(ucs);
2662     if (newucs)
2663     {
2664       // actually create the scheme's entry in the XML model
2665       java.awt.Color[] colours = ucs.getColours();
2666       UserColours uc = new UserColours();
2667       // UserColourScheme jbucs = new UserColourScheme();
2668       JalviewUserColours jbucs = new JalviewUserColours();
2669
2670       for (int i = 0; i < colours.length; i++)
2671       {
2672         Colour col = new Colour();
2673         col.setName(ResidueProperties.aa[i]);
2674         col.setRGB(jalview.util.Format.getHexString(colours[i]));
2675         // jbucs.addColour(col);
2676         jbucs.getColour().add(col);
2677       }
2678       if (ucs.getLowerCaseColours() != null)
2679       {
2680         colours = ucs.getLowerCaseColours();
2681         for (int i = 0; i < colours.length; i++)
2682         {
2683           Colour col = new Colour();
2684           col.setName(ResidueProperties.aa[i].toLowerCase());
2685           col.setRGB(jalview.util.Format.getHexString(colours[i]));
2686           // jbucs.addColour(col);
2687           jbucs.getColour().add(col);
2688         }
2689       }
2690
2691       uc.setId(id);
2692       uc.setUserColourScheme(jbucs);
2693       // jm.addUserColours(uc);
2694       jm.getUserColours().add(uc);
2695     }
2696
2697     return id;
2698   }
2699
2700   jalview.schemes.UserColourScheme getUserColourScheme(
2701           JalviewModel jm, String id)
2702   {
2703     List<UserColours> uc = jm.getUserColours();
2704     UserColours colours = null;
2705 /*
2706     for (int i = 0; i < uc.length; i++)
2707     {
2708       if (uc[i].getId().equals(id))
2709       {
2710         colours = uc[i];
2711         break;
2712       }
2713     }
2714 */
2715     for (UserColours c : uc)
2716     {
2717       if (c.getId().equals(id))
2718       {
2719         colours = c;
2720         break;
2721       }
2722     }
2723
2724     java.awt.Color[] newColours = new java.awt.Color[24];
2725
2726     for (int i = 0; i < 24; i++)
2727     {
2728       newColours[i] = new java.awt.Color(Integer.parseInt(
2729               // colours.getUserColourScheme().getColour(i).getRGB(), 16));
2730               colours.getUserColourScheme().getColour().get(i).getRGB(),
2731               16));
2732     }
2733
2734     jalview.schemes.UserColourScheme ucs = new jalview.schemes.UserColourScheme(
2735             newColours);
2736
2737     if (colours.getUserColourScheme().getColour().size()/*Count()*/ > 24)
2738     {
2739       newColours = new java.awt.Color[23];
2740       for (int i = 0; i < 23; i++)
2741       {
2742         newColours[i] = new java.awt.Color(Integer.parseInt(
2743                 colours.getUserColourScheme().getColour().get(i + 24)
2744                         .getRGB(),
2745                 16));
2746       }
2747       ucs.setLowerCaseColours(newColours);
2748     }
2749
2750     return ucs;
2751   }
2752
2753   /**
2754    * Load a jalview project archive from a jar file
2755    * 
2756    * @param file
2757    *          - HTTP URL or filename
2758    */
2759   public AlignFrame loadJalviewAlign(final Object file)
2760   {
2761
2762     jalview.gui.AlignFrame af = null;
2763
2764     try
2765     {
2766       // create list to store references for any new Jmol viewers created
2767       newStructureViewers = new Vector<>();
2768       // UNMARSHALLER SEEMS TO CLOSE JARINPUTSTREAM, MOST ANNOYING
2769       // Workaround is to make sure caller implements the JarInputStreamProvider
2770       // interface
2771       // so we can re-open the jar input stream for each entry.
2772
2773       jarInputStreamProvider jprovider = createjarInputStreamProvider(file);
2774       af = loadJalviewAlign(jprovider);
2775       if (af != null)
2776       {
2777         af.setMenusForViewport();
2778       }
2779     } catch (MalformedURLException e)
2780     {
2781       errorMessage = "Invalid URL format for '" + file + "'";
2782       reportErrors();
2783     } finally
2784     {
2785       try
2786       {
2787
2788           // BH 2019 -- can't wait
2789         Jalview.execRunnable(new Runnable()
2790         {
2791           @Override
2792           public void run()
2793           {
2794             setLoadingFinishedForNewStructureViewers();
2795           }
2796         });
2797       } catch (Exception x)
2798       {
2799         System.err.println("Error loading alignment: " + x.getMessage());
2800       }
2801     }
2802     this.jarFile = null;
2803     return af;
2804   }
2805
2806         @SuppressWarnings("unused")
2807   private jarInputStreamProvider createjarInputStreamProvider(
2808           final Object ofile) throws MalformedURLException
2809   {
2810     try
2811     {
2812       String file = (ofile instanceof File
2813               ? ((File) ofile).getCanonicalPath()
2814               : ofile.toString());
2815       byte[] bytes = Platform.isJS() ? Platform.getFileBytes((File) ofile)
2816               : null;
2817       if (bytes != null)
2818       {
2819         this.jarFile = (File) ofile;
2820       }
2821       errorMessage = null;
2822       uniqueSetSuffix = null;
2823       seqRefIds = null;
2824       viewportsAdded.clear();
2825       frefedSequence = null;
2826
2827       URL url = file.startsWith("http://") ? new URL(file) : null;
2828       return new jarInputStreamProvider()
2829       {
2830         @Override
2831         public JarInputStream getJarInputStream() throws IOException
2832         {
2833           InputStream is = bytes != null ? new ByteArrayInputStream(bytes)
2834                   : (url != null ? url.openStream()
2835                           : new FileInputStream(file));
2836           return new JarInputStream(is);
2837         }
2838
2839         @Override
2840         public String getFilename()
2841         {
2842           return file;
2843         }
2844       };
2845     } catch (IOException e)
2846     {
2847       e.printStackTrace();
2848       return null;
2849     }
2850   }
2851
2852   /**
2853    * Recover jalview session from a jalview project archive. Caller may
2854    * initialise uniqueSetSuffix, seqRefIds, viewportsAdded and frefedSequence
2855    * themselves. Any null fields will be initialised with default values,
2856    * non-null fields are left alone.
2857    * 
2858    * @param jprovider
2859    * @return
2860    */
2861   public AlignFrame loadJalviewAlign(final jarInputStreamProvider jprovider)
2862   {
2863     errorMessage = null;
2864     if (uniqueSetSuffix == null)
2865     {
2866       uniqueSetSuffix = System.currentTimeMillis() % 100000 + "";
2867     }
2868     if (seqRefIds == null)
2869     {
2870       initSeqRefs();
2871     }
2872     AlignFrame af = null, _af = null;
2873     IdentityHashMap<AlignmentI, AlignmentI> importedDatasets = new IdentityHashMap<>();
2874     Map<String, AlignFrame> gatherToThisFrame = new HashMap<>();
2875     final String file = jprovider.getFilename();
2876
2877     List<AlignFrame> alignFrames = new ArrayList<>();
2878
2879     try
2880     {
2881       JarInputStream jin = null;
2882       JarEntry jarentry = null;
2883       int entryCount = 1;
2884
2885
2886       // Look for all the entry names ending with ".xml"
2887       // This includes all panels and at least one frame.
2888 //      Platform.timeCheck(null, Platform.TIME_MARK);
2889       do
2890       {
2891         jin = jprovider.getJarInputStream();
2892         for (int i = 0; i < entryCount; i++)
2893         {
2894           jarentry = jin.getNextJarEntry();
2895         }
2896         String name = (jarentry == null ? null : jarentry.getName());
2897
2898 //        System.out.println("Jalview2XML opening " + name);
2899         if (name != null && name.endsWith(".xml"))
2900         {
2901
2902           // DataSet for.... is read last.
2903           
2904           
2905           // The question here is what to do with the two
2906           // .xml files in the jvp file.
2907           // Some number of them, "...Dataset for...", will be the
2908           // Only AlignPanels and will have Viewport.
2909           // One or more will be the source data, with the DBRefs.
2910           //
2911           // JVP file writing (above) ensures tha the AlignPanels are written
2912           // first, then all relevant datasets (which are
2913           // Jalview.datamodel.Alignment).
2914           //
2915
2916 //          Platform.timeCheck("Jalview2XML JAXB " + name, Platform.TIME_MARK);
2917           JAXBContext jc = JAXBContext
2918                   .newInstance("jalview.xml.binding.jalview");
2919           XMLStreamReader streamReader = XMLInputFactory.newInstance()
2920                   .createXMLStreamReader(jin);
2921           javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
2922           JAXBElement<JalviewModel> jbe = um
2923                   .unmarshal(streamReader, JalviewModel.class);
2924           JalviewModel model = jbe.getValue();
2925
2926           if (true) // !skipViewport(object))
2927           {
2928             // Q: Do we have to load from the model, even if it
2929             // does not have a viewport, could we discover that early on?
2930             // Q: Do we need to load this object?
2931             _af = loadFromObject(model, file, true, jprovider);
2932 //            Platform.timeCheck("Jalview2XML.loadFromObject",
2933             // Platform.TIME_MARK);
2934
2935             if (_af != null)
2936             {
2937               alignFrames.add(_af);
2938             }
2939             if (_af != null && model.getViewport().size() > 0)
2940             {
2941
2942               // That is, this is one of the AlignmentPanel models
2943               if (af == null)
2944               {
2945                 // store a reference to the first view
2946                 af = _af;
2947               }
2948               if (_af.getViewport().isGatherViewsHere())
2949               {
2950                 // if this is a gathered view, keep its reference since
2951                 // after gathering views, only this frame will remain
2952                 af = _af;
2953                 gatherToThisFrame.put(_af.getViewport().getSequenceSetId(),
2954                         _af);
2955               }
2956               // Save dataset to register mappings once all resolved
2957               importedDatasets.put(
2958                       af.getViewport().getAlignment().getDataset(),
2959                       af.getViewport().getAlignment().getDataset());
2960             }
2961           }
2962 //          Platform.timeCheck("JAXB " + name, Platform.TIME_MARK);
2963           entryCount++;
2964         }
2965         else if (jarentry != null)
2966         {
2967           // Some other file here.
2968           entryCount++;
2969         }
2970       } while (jarentry != null);
2971 //      Platform.timeCheck("JAXB loop exit", Platform.TIME_MARK);
2972       resolveFrefedSequences();
2973 //      Platform.timeCheck("JAXB resolveFrefed", Platform.TIME_MARK);
2974
2975     } catch (IOException ex)
2976     {
2977       ex.printStackTrace();
2978       errorMessage = "Couldn't locate Jalview XML file : " + file;
2979       System.err.println(
2980               "Exception whilst loading jalview XML file : " + ex + "\n");
2981     } catch (Exception ex)
2982     {
2983       System.err.println("Parsing as Jalview Version 2 file failed.");
2984       ex.printStackTrace(System.err);
2985       if (attemptversion1parse)
2986       {
2987         // used to attempt to parse as V1 castor-generated xml
2988       }
2989       if (Desktop.getInstance() != null)
2990       {
2991         Desktop.getInstance().stopLoading();
2992       }
2993       if (af != null)
2994       {
2995         System.out.println("Successfully loaded archive file");
2996         return af;
2997       }
2998       ex.printStackTrace();
2999
3000       System.err.println(
3001               "Exception whilst loading jalview XML file : " + ex + "\n");
3002     } catch (OutOfMemoryError e)
3003     {
3004       // Don't use the OOM Window here
3005       errorMessage = "Out of memory loading jalview XML file";
3006       System.err.println("Out of memory whilst loading jalview XML file");
3007       e.printStackTrace();
3008     } finally
3009     {
3010       for (AlignFrame alf : alignFrames)
3011       {
3012         alf.alignPanel.setHoldRepaint(false);
3013       }
3014
3015     }
3016
3017     /*
3018      * Regather multiple views (with the same sequence set id) to the frame (if
3019      * any) that is flagged as the one to gather to, i.e. convert them to tabbed
3020      * views instead of separate frames. Note this doesn't restore a state where
3021      * some expanded views in turn have tabbed views - the last "first tab" read
3022      * in will play the role of gatherer for all.
3023      */
3024     for (AlignFrame fr : gatherToThisFrame.values())
3025     {
3026       Desktop.getInstance().gatherViews(fr);
3027     }
3028
3029     restoreSplitFrames();
3030     for (AlignmentI ds : importedDatasets.keySet())
3031     {
3032       if (ds.getCodonFrames() != null)
3033       {
3034         Desktop.getStructureSelectionManager()
3035                 .registerMappings(ds.getCodonFrames());
3036       }
3037     }
3038     if (errorMessage != null)
3039     {
3040       reportErrors();
3041     }
3042
3043     if (Desktop.getInstance() != null)
3044     {
3045       Desktop.getInstance().stopLoading();
3046     }
3047
3048     return af;
3049   }
3050
3051   /**
3052    * Try to reconstruct and display SplitFrame windows, where each contains
3053    * complementary dna and protein alignments. Done by pairing up AlignFrame
3054    * objects (created earlier) which have complementary viewport ids associated.
3055    */
3056   protected void restoreSplitFrames()
3057   {
3058     List<SplitFrame> gatherTo = new ArrayList<>();
3059     List<AlignFrame> addedToSplitFrames = new ArrayList<>();
3060     Map<String, AlignFrame> dna = new HashMap<>();
3061
3062     /*
3063      * Identify the DNA alignments
3064      */
3065     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
3066             .entrySet())
3067     {
3068       AlignFrame af = candidate.getValue();
3069       if (af.getViewport().getAlignment().isNucleotide())
3070       {
3071         dna.put(candidate.getKey().getId(), af);
3072       }
3073     }
3074
3075     /*
3076      * Try to match up the protein complements
3077      */
3078     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
3079             .entrySet())
3080     {
3081       AlignFrame af = candidate.getValue();
3082       if (!af.getViewport().getAlignment().isNucleotide())
3083       {
3084         String complementId = candidate.getKey().getComplementId();
3085         // only non-null complements should be in the Map
3086         if (complementId != null && dna.containsKey(complementId))
3087         {
3088           final AlignFrame dnaFrame = dna.get(complementId);
3089           SplitFrame sf = createSplitFrame(dnaFrame, af);
3090           addedToSplitFrames.add(dnaFrame);
3091           addedToSplitFrames.add(af);
3092           dnaFrame.setMenusForViewport();
3093           af.setMenusForViewport();
3094           if (af.getViewport().isGatherViewsHere())
3095           {
3096             gatherTo.add(sf);
3097           }
3098         }
3099       }
3100     }
3101
3102     /*
3103      * Open any that we failed to pair up (which shouldn't happen!) as
3104      * standalone AlignFrame's.
3105      */
3106     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
3107             .entrySet())
3108     {
3109       AlignFrame af = candidate.getValue();
3110       if (!addedToSplitFrames.contains(af))
3111       {
3112         Viewport view = candidate.getKey();
3113         Desktop.addInternalFrame(af, view.getTitle(),
3114                 safeInt(view.getWidth()), safeInt(view.getHeight()));
3115         af.setMenusForViewport();
3116         System.err.println("Failed to restore view " + view.getTitle()
3117                 + " to split frame");
3118       }
3119     }
3120
3121     /*
3122      * Gather back into tabbed views as flagged.
3123      */
3124     for (SplitFrame sf : gatherTo)
3125     {
3126       Desktop.getInstance().gatherViews(sf);
3127     }
3128
3129     splitFrameCandidates.clear();
3130   }
3131
3132   /**
3133    * Construct and display one SplitFrame holding DNA and protein alignments.
3134    * 
3135    * @param dnaFrame
3136    * @param proteinFrame
3137    * @return
3138    */
3139   protected SplitFrame createSplitFrame(AlignFrame dnaFrame,
3140           AlignFrame proteinFrame)
3141   {
3142     SplitFrame splitFrame = new SplitFrame(dnaFrame, proteinFrame);
3143     String title = MessageManager.getString("label.linked_view_title");
3144     int width = (int) dnaFrame.getBounds().getWidth();
3145     int height = (int) (dnaFrame.getBounds().getHeight()
3146             + proteinFrame.getBounds().getHeight() + 50);
3147
3148     /*
3149      * SplitFrame location is saved to both enclosed frames
3150      */
3151     splitFrame.setLocation(dnaFrame.getX(), dnaFrame.getY());
3152     Desktop.addInternalFrame(splitFrame, title, width, height);
3153
3154     /*
3155      * And compute cDNA consensus (couldn't do earlier with consensus as
3156      * mappings were not yet present)
3157      */
3158     proteinFrame.getViewport().alignmentChanged(proteinFrame.alignPanel);
3159
3160     return splitFrame;
3161   }
3162
3163   /**
3164    * check errorMessage for a valid error message and raise an error box in the
3165    * GUI or write the current errorMessage to stderr and then clear the error
3166    * state.
3167    */
3168   protected void reportErrors()
3169   {
3170     reportErrors(false);
3171   }
3172
3173   protected void reportErrors(final boolean saving)
3174   {
3175     if (errorMessage != null)
3176     {
3177       final String finalErrorMessage = errorMessage;
3178       if (raiseGUI)
3179       {
3180         javax.swing.SwingUtilities.invokeLater(new Runnable()
3181         {
3182           @Override
3183           public void run()
3184           {
3185             JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
3186                     finalErrorMessage,
3187                     "Error " + (saving ? "saving" : "loading")
3188                             + " Jalview file",
3189                     JvOptionPane.WARNING_MESSAGE);
3190           }
3191         });
3192       }
3193       else
3194       {
3195         System.err.println("Problem loading Jalview file: " + errorMessage);
3196       }
3197     }
3198     errorMessage = null;
3199   }
3200
3201   Map<String, String> alreadyLoadedPDB = new HashMap<>();
3202
3203   /**
3204    * when set, local views will be updated from view stored in JalviewXML
3205    * Currently (28th Sep 2008) things will go horribly wrong in vamsas document
3206    * sync if this is set to true.
3207    */
3208   private final boolean updateLocalViews = false;
3209
3210   /**
3211    * Returns the path to a temporary file holding the PDB file for the given PDB
3212    * id. The first time of asking, searches for a file of that name in the
3213    * Jalview project jar, and copies it to a new temporary file. Any repeat
3214    * requests just return the path to the file previously created.
3215    * 
3216    * @param jprovider
3217    * @param pdbId
3218    * @return
3219    */
3220   String loadPDBFile(jarInputStreamProvider jprovider, String pdbId,
3221           String origFile)
3222   {
3223     if (alreadyLoadedPDB.containsKey(pdbId))
3224     {
3225       return alreadyLoadedPDB.get(pdbId).toString();
3226     }
3227
3228     String tempFile = copyJarEntry(jprovider, pdbId, "jalview_pdb",
3229             origFile);
3230     if (tempFile != null)
3231     {
3232       alreadyLoadedPDB.put(pdbId, tempFile);
3233     }
3234     return tempFile;
3235   }
3236
3237   /**
3238    * Copies the jar entry of given name to a new temporary file and returns the
3239    * path to the file, or null if the entry is not found.
3240    * 
3241    * @param jprovider
3242    * @param jarEntryName
3243    * @param prefix
3244    *          a prefix for the temporary file name, must be at least three
3245    *          characters long
3246    * @param origFile
3247    *          null or original file - so new file can be given the same suffix
3248    *          as the old one
3249    * @return
3250    */
3251   protected String copyJarEntry(jarInputStreamProvider jprovider,
3252           String jarEntryName, String prefix, String origFile)
3253   {
3254     BufferedReader in = null;
3255     PrintWriter out = null;
3256     String suffix = ".tmp";
3257     if (origFile == null)
3258     {
3259       origFile = jarEntryName;
3260     }
3261     int sfpos = origFile.lastIndexOf(".");
3262     if (sfpos > -1 && sfpos < (origFile.length() - 3))
3263     {
3264       suffix = "." + origFile.substring(sfpos + 1);
3265     }
3266     try
3267     {
3268       JarInputStream jin = jprovider.getJarInputStream();
3269       /*
3270        * if (jprovider.startsWith("http://")) { jin = new JarInputStream(new
3271        * URL(jprovider).openStream()); } else { jin = new JarInputStream(new
3272        * FileInputStream(jprovider)); }
3273        */
3274
3275       JarEntry entry = null;
3276       do
3277       {
3278         entry = jin.getNextJarEntry();
3279       } while (entry != null && !entry.getName().equals(jarEntryName));
3280       if (entry != null)
3281       {
3282         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
3283         File outFile = File.createTempFile(prefix, suffix);
3284         outFile.deleteOnExit();
3285         out = new PrintWriter(new FileOutputStream(outFile));
3286         String data;
3287
3288         while ((data = in.readLine()) != null)
3289         {
3290           out.println(data);
3291         }
3292         out.flush();
3293         String t = outFile.getAbsolutePath();
3294         return t;
3295       }
3296       else
3297       {
3298         warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
3299       }
3300     } catch (Exception ex)
3301     {
3302       ex.printStackTrace();
3303     } finally
3304     {
3305       if (in != null)
3306       {
3307         try
3308         {
3309           in.close();
3310         } catch (IOException e)
3311         {
3312           // ignore
3313         }
3314       }
3315       if (out != null)
3316       {
3317         out.close();
3318       }
3319     }
3320
3321     return null;
3322   }
3323
3324   private class JvAnnotRow
3325   {
3326     public JvAnnotRow(int i, AlignmentAnnotation jaa)
3327     {
3328       order = i;
3329       template = jaa;
3330     }
3331
3332     /**
3333      * persisted version of annotation row from which to take vis properties
3334      */
3335     public jalview.datamodel.AlignmentAnnotation template;
3336
3337     /**
3338      * original position of the annotation row in the alignment
3339      */
3340     public int order;
3341   }
3342
3343   /**
3344    * Load alignment frame from jalview XML DOM object. For a DOM object that
3345    * includes one or more Viewport elements (one with a title that does NOT
3346    * contain "Dataset for"), create the frame.
3347    * 
3348    * @param jalviewModel
3349    *          DOM
3350    * @param file
3351    *          filename source string
3352    * @param loadTreesAndStructures
3353    *          when false only create Viewport
3354    * @param jprovider
3355    *          data source provider
3356    * @return alignment frame created from view stored in DOM
3357    */
3358   private AlignFrame loadFromObject(JalviewModel jalviewModel, String file,
3359           boolean loadTreesAndStructures, jarInputStreamProvider jprovider)
3360   {
3361
3362     // Platform.timeCheck("Jalview2XML.loadFromObject0", Platform.TIME_MARK);
3363
3364     SequenceSet vamsasSet = jalviewModel.getVamsasModel().getSequenceSet()
3365             .get(0);
3366     List<Sequence> vamsasSeqs = vamsasSet.getSequence();
3367
3368     // JalviewModelSequence jms = object.getJalviewModelSequence();
3369
3370     // Viewport view = (jms.getViewportCount() > 0) ? jms.getViewport(0)
3371     // : null;
3372     Viewport view = (jalviewModel.getViewport().size() > 0)
3373             ? jalviewModel.getViewport().get(0)
3374             : null;
3375
3376     // ////////////////////////////////
3377     // INITIALISE ALIGNMENT SEQUENCESETID AND VIEWID
3378     //
3379     //
3380     // If we just load in the same jar file again, the sequenceSetId
3381     // will be the same, and we end up with multiple references
3382     // to the same sequenceSet. We must modify this id on load
3383     // so that each load of the file gives a unique id
3384
3385     /**
3386      * used to resolve correct alignment dataset for alignments with multiple
3387      * views
3388      */
3389     String uniqueSeqSetId = null;
3390     String viewId = null;
3391     if (view != null)
3392     {
3393       uniqueSeqSetId = view.getSequenceSetId() + uniqueSetSuffix;
3394       viewId = (view.getId() == null ? null
3395               : view.getId() + uniqueSetSuffix);
3396     }
3397
3398     // Platform.timeCheck("Jalview2XML.loadFromObject1", Platform.TIME_MARK);
3399     // ////////////////////////////////
3400     // LOAD SEQUENCES
3401
3402     List<SequenceI> hiddenSeqs = null;
3403
3404     List<SequenceI> tmpseqs = new ArrayList<>();
3405
3406     boolean multipleView = false;
3407     SequenceI referenceseqForView = null;
3408     // JSeq[] jseqs = object.getJalviewModelSequence().getJSeq();
3409     List<JSeq> jseqs = jalviewModel.getJSeq();
3410     int vi = 0; // counter in vamsasSeq array
3411     for (int i = 0; i < jseqs.size(); i++)
3412     {
3413       JSeq jseq = jseqs.get(i);
3414       String seqId = jseq.getId();
3415
3416       SequenceI tmpSeq = seqRefIds.get(seqId);
3417       if (tmpSeq != null)
3418       {
3419         //
3420         if (!incompleteSeqs.containsKey(seqId))
3421         {
3422           // may not need this check, but keep it for at least 2.9,1 release
3423           if (tmpSeq.getStart() != jseq.getStart()
3424                   || tmpSeq.getEnd() != jseq.getEnd())
3425           {
3426             System.err.println(
3427                     "Warning JAL-2154 regression: updating start/end for sequence "
3428                             + tmpSeq.toString() + " to " + jseq);
3429           }
3430         }
3431         else
3432         {
3433           incompleteSeqs.remove(seqId);
3434         }
3435         if (vamsasSeqs.size() > vi
3436                 && vamsasSeqs.get(vi).getId().equals(seqId))
3437         {
3438           // most likely we are reading a dataset XML document so
3439           // update from vamsasSeq section of XML for this sequence
3440           tmpSeq.setName(vamsasSeqs.get(vi).getName());
3441           tmpSeq.setDescription(vamsasSeqs.get(vi).getDescription());
3442           tmpSeq.setSequence(vamsasSeqs.get(vi).getSequence());
3443           vi++;
3444         }
3445         else
3446         {
3447           // reading multiple views, so vamsasSeq set is a subset of JSeq
3448           multipleView = true;
3449         }
3450         tmpSeq.setStart(jseq.getStart());
3451         tmpSeq.setEnd(jseq.getEnd());
3452         tmpseqs.add(tmpSeq);
3453       }
3454       else
3455       {
3456         Sequence vamsasSeq = vamsasSeqs.get(vi);
3457         tmpSeq = new jalview.datamodel.Sequence(vamsasSeq.getName(),
3458                 vamsasSeq.getSequence());
3459         tmpSeq.setDescription(vamsasSeq.getDescription());
3460         tmpSeq.setStart(jseq.getStart());
3461         tmpSeq.setEnd(jseq.getEnd());
3462         tmpSeq.setVamsasId(uniqueSetSuffix + seqId);
3463         seqRefIds.put(vamsasSeq.getId(), tmpSeq);
3464         tmpseqs.add(tmpSeq);
3465         vi++;
3466       }
3467
3468       if (safeBoolean(jseq.isViewreference()))
3469       {
3470         referenceseqForView = tmpseqs.get(tmpseqs.size() - 1);
3471       }
3472
3473       if (jseq.isHidden() != null && jseq.isHidden().booleanValue())
3474       {
3475         if (hiddenSeqs == null)
3476         {
3477           hiddenSeqs = new ArrayList<>();
3478         }
3479
3480         hiddenSeqs.add(tmpSeq);
3481       }
3482     }
3483
3484     // Platform.timeCheck("Jalview2XML.loadFromObject-seq",
3485     // Platform.TIME_MARK);
3486     // /
3487     // Create the alignment object from the sequence set
3488     // ///////////////////////////////
3489     SequenceI[] orderedSeqs = tmpseqs
3490             .toArray(new SequenceI[tmpseqs.size()]);
3491
3492     AlignmentI al = null;
3493     // so we must create or recover the dataset alignment before going further
3494     // ///////////////////////////////
3495     if (vamsasSet.getDatasetId() == null || vamsasSet.getDatasetId() == "")
3496     {
3497       // older jalview projects do not have a dataset - so creat alignment and
3498       // dataset
3499       al = new Alignment(orderedSeqs);
3500       al.setDataset(null);
3501     }
3502     else
3503     {
3504       boolean isdsal = jalviewModel.getViewport().isEmpty();
3505       if (isdsal)
3506       {
3507         // we are importing a dataset record, so
3508         // recover reference to an alignment already materialsed as dataset
3509         al = getDatasetFor(vamsasSet.getDatasetId());
3510       }
3511       if (al == null)
3512       {
3513         // materialse the alignment
3514         al = new Alignment(orderedSeqs);
3515       }
3516       if (isdsal)
3517       {
3518         addDatasetRef(vamsasSet.getDatasetId(), al);
3519       }
3520
3521       // finally, verify all data in vamsasSet is actually present in al
3522       // passing on flag indicating if it is actually a stored dataset
3523       recoverDatasetFor(vamsasSet, al, isdsal, uniqueSeqSetId);
3524     }
3525
3526     // Platform.timeCheck("Jalview2XML.loadFromObject-align",
3527     // Platform.TIME_MARK);
3528     if (referenceseqForView != null)
3529     {
3530       al.setSeqrep(referenceseqForView);
3531     }
3532     // / Add the alignment properties
3533     for (int i = 0; i < vamsasSet.getSequenceSetProperties().size(); i++)
3534     {
3535       SequenceSetProperties ssp = vamsasSet.getSequenceSetProperties()
3536               .get(i);
3537       al.setProperty(ssp.getKey(), ssp.getValue());
3538     }
3539
3540     // Platform.timeCheck("Jalview2XML.loadFromObject-setseqprop",
3541     // Platform.TIME_MARK);
3542     // ///////////////////////////////
3543
3544     Hashtable pdbloaded = new Hashtable(); // TODO nothing writes to this??
3545     if (!multipleView)
3546     {
3547       // load sequence features, database references and any associated PDB
3548       // structures for the alignment
3549       //
3550       // prior to 2.10, this part would only be executed the first time a
3551       // sequence was encountered, but not afterwards.
3552       // now, for 2.10 projects, this is also done if the xml doc includes
3553       // dataset sequences not actually present in any particular view.
3554       //
3555       // Platform.timeCheck("J2XML features0", Platform.TIME_RESET);
3556       for (int i = 0; i < vamsasSeqs.size(); i++)
3557       {
3558         JSeq jseq = jseqs.get(i);
3559         if (jseq.getFeatures().size() > 0)
3560         {
3561           List<Feature> features = jseq.getFeatures();
3562           for (int f = 0; f < features.size(); f++)
3563           {
3564             Feature feat = features.get(f);
3565             SequenceFeature sf = new SequenceFeature(feat.getType(),
3566                     feat.getDescription(), feat.getBegin(), feat.getEnd(),
3567                     safeFloat(feat.getScore()), feat.getFeatureGroup());
3568             sf.setStatus(feat.getStatus());
3569
3570             /*
3571              * load any feature attributes - include map-valued attributes
3572              */
3573             Map<String, Map<String, String>> mapAttributes = new HashMap<>();
3574             for (int od = 0; od < feat.getOtherData().size(); od++)
3575             {
3576               OtherData keyValue = feat.getOtherData().get(od);
3577               String attributeName = keyValue.getKey();
3578               String attributeValue = keyValue.getValue();
3579               if (attributeName.startsWith("LINK"))
3580               {
3581                 sf.addLink(attributeValue);
3582               }
3583               else
3584               {
3585                 String subAttribute = keyValue.getKey2();
3586                 if (subAttribute == null)
3587                 {
3588                   // simple string-valued attribute
3589                   sf.setValue(attributeName, attributeValue);
3590                 }
3591                 else
3592                 {
3593                   // attribute 'key' has sub-attribute 'key2'
3594                   if (!mapAttributes.containsKey(attributeName))
3595                   {
3596                     mapAttributes.put(attributeName, new HashMap<>());
3597                   }
3598                   mapAttributes.get(attributeName).put(subAttribute,
3599                           attributeValue);
3600                 }
3601               }
3602             }
3603             for (Entry<String, Map<String, String>> mapAttribute : mapAttributes
3604                     .entrySet())
3605             {
3606               sf.setValue(mapAttribute.getKey(), mapAttribute.getValue());
3607             }
3608
3609             // adds feature to datasequence's feature set (since Jalview 2.10)
3610             // Platform.timeCheck(null, Platform.TIME_SET);
3611             al.getSequenceAt(i).addSequenceFeature(sf);
3612             // Platform.timeCheck(null, Platform.TIME_MARK);
3613           }
3614         }
3615         if (vamsasSeqs.get(i).getDBRef().size() > 0)
3616         {
3617           // adds dbrefs to datasequence's set (since Jalview 2.10)
3618           addDBRefs(
3619                   al.getSequenceAt(i).getDatasetSequence() == null
3620                           ? al.getSequenceAt(i)
3621                           : al.getSequenceAt(i).getDatasetSequence(),
3622                   vamsasSeqs.get(i));
3623         }
3624         if (jseq.getPdbids().size() > 0)
3625         {
3626           List<Pdbids> ids = jseq.getPdbids();
3627           for (int p = 0; p < ids.size(); p++)
3628           {
3629             Pdbids pdbid = ids.get(p);
3630             jalview.datamodel.PDBEntry entry = new jalview.datamodel.PDBEntry();
3631             entry.setId(pdbid.getId());
3632             if (pdbid.getType() != null)
3633             {
3634               if (PDBEntry.Type.getType(pdbid.getType()) != null)
3635               {
3636                 entry.setType(PDBEntry.Type.getType(pdbid.getType()));
3637               }
3638               else
3639               {
3640                 entry.setType(PDBEntry.Type.FILE);
3641               }
3642             }
3643             // jprovider is null when executing 'New View'
3644             if (pdbid.getFile() != null && jprovider != null)
3645             {
3646               if (!pdbloaded.containsKey(pdbid.getFile()))
3647               {
3648                 entry.setFile(loadPDBFile(jprovider, pdbid.getId(),
3649                         pdbid.getFile()));
3650               }
3651               else
3652               {
3653                 entry.setFile(pdbloaded.get(pdbid.getId()).toString());
3654               }
3655             }
3656             /*
3657             if (pdbid.getPdbentryItem() != null)
3658             {
3659               for (PdbentryItem item : pdbid.getPdbentryItem())
3660               {
3661                 for (Property pr : item.getProperty())
3662                 {
3663                   entry.setProperty(pr.getName(), pr.getValue());
3664                 }
3665               }
3666             }
3667             */
3668             for (Property prop : pdbid.getProperty())
3669             {
3670               entry.setProperty(prop.getName(), prop.getValue());
3671             }
3672             Desktop.getStructureSelectionManager().registerPDBEntry(entry);
3673             // adds PDBEntry to datasequence's set (since Jalview 2.10)
3674             if (al.getSequenceAt(i).getDatasetSequence() != null)
3675             {
3676               al.getSequenceAt(i).getDatasetSequence().addPDBId(entry);
3677             }
3678             else
3679             {
3680               al.getSequenceAt(i).addPDBId(entry);
3681             }
3682           }
3683         }
3684
3685       }
3686
3687       // Platform.timeCheck("features done", Platform.TIME_GET);
3688       // Platform.timeCheck("Jalview2XML.loadFromObject-endmultiview",
3689       // Platform.TIME_MARK);
3690     } // end !multipleview
3691
3692     // ///////////////////////////////
3693     // LOAD SEQUENCE MAPPINGS
3694
3695     if (vamsasSet.getAlcodonFrame().size() > 0)
3696     {
3697       // TODO Potentially this should only be done once for all views of an
3698       // alignment
3699       List<AlcodonFrame> alc = vamsasSet.getAlcodonFrame();
3700       for (int i = 0; i < alc.size(); i++)
3701       {
3702         AlignedCodonFrame cf = new AlignedCodonFrame();
3703         if (alc.get(i).getAlcodMap().size() > 0)
3704         {
3705           List<AlcodMap> maps = alc.get(i).getAlcodMap();
3706           for (int m = 0; m < maps.size(); m++)
3707           {
3708             AlcodMap map = maps.get(m);
3709             SequenceI dnaseq = seqRefIds.get(map.getDnasq());
3710             // Load Mapping
3711             jalview.datamodel.Mapping mapping = null;
3712             // attach to dna sequence reference.
3713             if (map.getMapping() != null)
3714             {
3715               mapping = addMapping(map.getMapping());
3716               if (dnaseq != null && mapping.getTo() != null)
3717               {
3718                 cf.addMap(dnaseq, mapping.getTo(), mapping.getMap());
3719               }
3720               else
3721               {
3722                 // defer to later
3723                 frefedSequence
3724                         .add(newAlcodMapRef(map.getDnasq(), cf, mapping));
3725               }
3726             }
3727           }
3728           al.addCodonFrame(cf);
3729         }
3730       }
3731       // Platform.timeCheck("Jalview2XML.loadFromObject-seqmap",
3732       // Platform.TIME_MARK);
3733     }
3734
3735     // ////////////////////////////////
3736     // LOAD ANNOTATIONS
3737     List<JvAnnotRow> autoAlan = new ArrayList<>();
3738
3739     /*
3740      * store any annotations which forward reference a group's ID
3741      */
3742     Map<String, List<AlignmentAnnotation>> groupAnnotRefs = new Hashtable<>();
3743
3744     if (vamsasSet.getAnnotation().size()/*Count()*/ > 0)
3745     {
3746       List<Annotation> an = vamsasSet.getAnnotation();
3747
3748       for (int i = 0; i < an.size(); i++)
3749       {
3750         Annotation annotation = an.get(i);
3751
3752         /**
3753          * test if annotation is automatically calculated for this view only
3754          */
3755         boolean autoForView = false;
3756         if (annotation.getLabel().equals("Quality")
3757                 || annotation.getLabel().equals("Conservation")
3758                 || annotation.getLabel().equals("Consensus"))
3759         {
3760           // Kludge for pre 2.5 projects which lacked the autocalculated flag
3761           autoForView = true;
3762           // JAXB has no has() test; schema defaults value to false
3763           // if (!annotation.hasAutoCalculated())
3764           // {
3765           // annotation.setAutoCalculated(true);
3766           // }
3767         }
3768         if (autoForView || annotation.isAutoCalculated())
3769         {
3770           // remove ID - we don't recover annotation from other views for
3771           // view-specific annotation
3772           annotation.setId(null);
3773         }
3774
3775         // set visibility for other annotation in this view
3776         String annotationId = annotation.getId();
3777         if (annotationId != null && annotationIds.containsKey(annotationId))
3778         {
3779           AlignmentAnnotation jda = annotationIds.get(annotationId);
3780           // in principle Visible should always be true for annotation displayed
3781           // in multiple views
3782           if (annotation.isVisible() != null)
3783           {
3784             jda.visible = annotation.isVisible();
3785           }
3786
3787           al.addAnnotation(jda);
3788
3789           continue;
3790         }
3791         // Construct new annotation from model.
3792         List<AnnotationElement> ae = annotation.getAnnotationElement();
3793         // System.err.println(
3794         // "Jalview2XML processing " + ae.size() + " annotations");
3795
3796         jalview.datamodel.Annotation[] anot = null;
3797         java.awt.Color firstColour = null;
3798         int anpos;
3799         if (!annotation.isScoreOnly())
3800         {
3801           anot = new jalview.datamodel.Annotation[al.getWidth()];
3802           for (int aa = 0; aa < ae.size() && aa < anot.length; aa++)
3803           {
3804             AnnotationElement annElement = ae.get(aa);
3805             anpos = annElement.getPosition();
3806
3807             if (anpos >= anot.length)
3808             {
3809               continue;
3810             }
3811
3812             float value = safeFloat(annElement.getValue());
3813             anot[anpos] = new jalview.datamodel.Annotation(
3814                     annElement.getDisplayCharacter(),
3815                     annElement.getDescription(),
3816                     (annElement.getSecondaryStructure() == null
3817                             || annElement.getSecondaryStructure()
3818                                     .length() == 0)
3819                                             ? ' '
3820                                             : annElement
3821                                                     .getSecondaryStructure()
3822                                                     .charAt(0),
3823                     value);
3824             anot[anpos].colour = new Color(safeInt(annElement.getColour()));
3825             if (firstColour == null)
3826             {
3827               firstColour = anot[anpos].colour;
3828             }
3829           }
3830         }
3831         // create the new AlignmentAnnotation
3832         jalview.datamodel.AlignmentAnnotation jaa = null;
3833
3834         if (annotation.isGraph())
3835         {
3836           float llim = 0, hlim = 0;
3837           // if (autoForView || an[i].isAutoCalculated()) {
3838           // hlim=11f;
3839           // }
3840           jaa = new jalview.datamodel.AlignmentAnnotation(
3841                   annotation.getLabel(), annotation.getDescription(), anot,
3842                   llim, hlim, safeInt(annotation.getGraphType()));
3843
3844           jaa.graphGroup = safeInt(annotation.getGraphGroup());
3845           jaa._linecolour = firstColour;
3846           if (annotation.getThresholdLine() != null)
3847           {
3848             jaa.setThreshold(new jalview.datamodel.GraphLine(
3849                     safeFloat(annotation.getThresholdLine().getValue()),
3850                     annotation.getThresholdLine().getLabel(),
3851                     new java.awt.Color(safeInt(
3852                             annotation.getThresholdLine().getColour()))));
3853           }
3854           if (autoForView || annotation.isAutoCalculated())
3855           {
3856             // Hardwire the symbol display line to ensure that labels for
3857             // histograms are displayed
3858             jaa.hasText = true;
3859           }
3860         }
3861         else
3862         {
3863           jaa = new jalview.datamodel.AlignmentAnnotation(
3864                   annotation.getLabel(), annotation.getDescription(), anot);
3865           jaa._linecolour = firstColour;
3866         }
3867         // register new annotation
3868         // Annotation graphs such as Conservation will not have id.
3869         if (annotation.getId() != null)
3870         {
3871           annotationIds.put(annotation.getId(), jaa);
3872           jaa.annotationId = annotation.getId();
3873         }
3874         // recover sequence association
3875         String sequenceRef = annotation.getSequenceRef();
3876         if (sequenceRef != null)
3877         {
3878           // from 2.9 sequenceRef is to sequence id (JAL-1781)
3879           SequenceI sequence = seqRefIds.get(sequenceRef);
3880           if (sequence == null)
3881           {
3882             // in pre-2.9 projects sequence ref is to sequence name
3883             sequence = al.findName(sequenceRef);
3884           }
3885           if (sequence != null)
3886           {
3887             jaa.createSequenceMapping(sequence, 1, true);
3888             sequence.addAlignmentAnnotation(jaa);
3889           }
3890         }
3891         // and make a note of any group association
3892         if (annotation.getGroupRef() != null
3893                 && annotation.getGroupRef().length() > 0)
3894         {
3895           List<jalview.datamodel.AlignmentAnnotation> aal = groupAnnotRefs
3896                   .get(annotation.getGroupRef());
3897           if (aal == null)
3898           {
3899             aal = new ArrayList<>();
3900             groupAnnotRefs.put(annotation.getGroupRef(), aal);
3901           }
3902           aal.add(jaa);
3903         }
3904
3905         if (annotation.getScore() != null)
3906         {
3907           jaa.setScore(annotation.getScore().doubleValue());
3908         }
3909         if (annotation.isVisible() != null)
3910         {
3911           jaa.visible = annotation.isVisible().booleanValue();
3912         }
3913
3914         if (annotation.isCentreColLabels() != null)
3915         {
3916           jaa.centreColLabels = annotation.isCentreColLabels()
3917                   .booleanValue();
3918         }
3919
3920         if (annotation.isScaleColLabels() != null)
3921         {
3922           jaa.scaleColLabel = annotation.isScaleColLabels().booleanValue();
3923         }
3924         if (annotation.isAutoCalculated())
3925         {
3926           // newer files have an 'autoCalculated' flag and store calculation
3927           // state in viewport properties
3928           jaa.autoCalculated = true; // means annotation will be marked for
3929           // update at end of load.
3930         }
3931         if (annotation.getGraphHeight() != null)
3932         {
3933           jaa.graphHeight = annotation.getGraphHeight().intValue();
3934         }
3935         jaa.belowAlignment = annotation.isBelowAlignment();
3936         jaa.setCalcId(annotation.getCalcId());
3937         if (annotation.getProperty().size() > 0)
3938         {
3939           for (Annotation.Property prop : annotation.getProperty())
3940           {
3941             jaa.setProperty(prop.getName(), prop.getValue());
3942           }
3943         }
3944         if (jaa.autoCalculated)
3945         {
3946           autoAlan.add(new JvAnnotRow(i, jaa));
3947         }
3948         else
3949         // if (!autoForView)
3950         {
3951           // add autocalculated group annotation and any user created annotation
3952           // for the view
3953           al.addAnnotation(jaa);
3954         }
3955       }
3956       // Platform.timeCheck("Jalview2XML.loadFromObject-annot",
3957       // Platform.TIME_MARK);
3958     }
3959     // ///////////////////////
3960     // LOAD GROUPS
3961     // Create alignment markup and styles for this view
3962     if (jalviewModel.getJGroup().size() > 0)
3963     {
3964       List<JGroup> groups = jalviewModel.getJGroup();
3965       boolean addAnnotSchemeGroup = false;
3966       for (int i = 0; i < groups.size(); i++)
3967       {
3968         JGroup jGroup = groups.get(i);
3969         ColourSchemeI cs = null;
3970         if (jGroup.getColour() != null)
3971         {
3972           if (jGroup.getColour().startsWith("ucs"))
3973           {
3974             cs = getUserColourScheme(jalviewModel, jGroup.getColour());
3975           }
3976           else if (jGroup.getColour().equals("AnnotationColourGradient")
3977                   && jGroup.getAnnotationColours() != null)
3978           {
3979             addAnnotSchemeGroup = true;
3980           }
3981           else
3982           {
3983             cs = ColourSchemeProperty.getColourScheme(null, al,
3984                     jGroup.getColour());
3985           }
3986         }
3987         int pidThreshold = safeInt(jGroup.getPidThreshold());
3988
3989         Vector<SequenceI> seqs = new Vector<>();
3990
3991         for (int s = 0; s < jGroup.getSeq().size(); s++)
3992         {
3993           String seqId = jGroup.getSeq().get(s);
3994           SequenceI ts = seqRefIds.get(seqId);
3995
3996           if (ts != null)
3997           {
3998             seqs.addElement(ts);
3999           }
4000         }
4001
4002         if (seqs.size() < 1)
4003         {
4004           continue;
4005         }
4006
4007         SequenceGroup sg = new SequenceGroup(seqs, jGroup.getName(), cs,
4008                 safeBoolean(jGroup.isDisplayBoxes()),
4009                 safeBoolean(jGroup.isDisplayText()),
4010                 safeBoolean(jGroup.isColourText()),
4011                 safeInt(jGroup.getStart()), safeInt(jGroup.getEnd()));
4012         sg.getGroupColourScheme().setThreshold(pidThreshold, true);
4013         sg.getGroupColourScheme()
4014                 .setConservationInc(safeInt(jGroup.getConsThreshold()));
4015         sg.setOutlineColour(new Color(safeInt(jGroup.getOutlineColour())));
4016
4017         sg.textColour = new Color(safeInt(jGroup.getTextCol1()));
4018         sg.textColour2 = new Color(safeInt(jGroup.getTextCol2()));
4019         sg.setShowNonconserved(safeBoolean(jGroup.isShowUnconserved()));
4020         sg.thresholdTextColour = safeInt(jGroup.getTextColThreshold());
4021         // attributes with a default in the schema are never null
4022         sg.setShowConsensusHistogram(jGroup.isShowConsensusHistogram());
4023         sg.setshowSequenceLogo(jGroup.isShowSequenceLogo());
4024         sg.setNormaliseSequenceLogo(jGroup.isNormaliseSequenceLogo());
4025         sg.setIgnoreGapsConsensus(jGroup.isIgnoreGapsinConsensus());
4026         if (jGroup.getConsThreshold() != null
4027                 && jGroup.getConsThreshold().intValue() != 0)
4028         {
4029           Conservation c = new Conservation("All", sg.getSequences(null), 0,
4030                   sg.getWidth() - 1);
4031           c.calculate();
4032           c.verdict(false, 25);
4033           sg.cs.setConservation(c);
4034         }
4035
4036         if (jGroup.getId() != null && groupAnnotRefs.size() > 0)
4037         {
4038           // re-instate unique group/annotation row reference
4039           List<AlignmentAnnotation> jaal = groupAnnotRefs
4040                   .get(jGroup.getId());
4041           if (jaal != null)
4042           {
4043             for (AlignmentAnnotation jaa : jaal)
4044             {
4045               jaa.groupRef = sg;
4046               if (jaa.autoCalculated)
4047               {
4048                 // match up and try to set group autocalc alignment row for this
4049                 // annotation
4050                 if (jaa.label.startsWith("Consensus for "))
4051                 {
4052                   sg.setConsensus(jaa);
4053                 }
4054                 // match up and try to set group autocalc alignment row for this
4055                 // annotation
4056                 if (jaa.label.startsWith("Conservation for "))
4057                 {
4058                   sg.setConservationRow(jaa);
4059                 }
4060               }
4061             }
4062           }
4063         }
4064         al.addGroup(sg);
4065         if (addAnnotSchemeGroup)
4066         {
4067           // reconstruct the annotation colourscheme
4068           sg.setColourScheme(
4069                   constructAnnotationColour(jGroup.getAnnotationColours(),
4070                           null, al, jalviewModel, false));
4071         }
4072       }
4073       // Platform.timeCheck("Jalview2XML.loadFromObject-groups",
4074       // Platform.TIME_MARK);
4075     }
4076     if (view == null)
4077     {
4078       // only dataset in this model, so just return.
4079       return null;
4080     }
4081     // ///////////////////////////////
4082     // LOAD VIEWPORT
4083
4084     // now check to see if we really need to create a new viewport.
4085     if (multipleView && viewportsAdded.size() == 0)
4086     {
4087       // We recovered an alignment for which a viewport already exists.
4088       // TODO: fix up any settings necessary for overlaying stored state onto
4089       // state recovered from another document. (may not be necessary).
4090       // we may need a binding from a viewport in memory to one recovered from
4091       // XML.
4092       // and then recover its containing af to allow the settings to be applied.
4093       // TODO: fix for vamsas demo
4094       System.err.println(
4095               "About to recover a viewport for existing alignment: Sequence set ID is "
4096                       + uniqueSeqSetId);
4097       Object seqsetobj = retrieveExistingObj(uniqueSeqSetId);
4098       if (seqsetobj != null)
4099       {
4100         if (seqsetobj instanceof String)
4101         {
4102           uniqueSeqSetId = (String) seqsetobj;
4103           System.err.println(
4104                   "Recovered extant sequence set ID mapping for ID : New Sequence set ID is "
4105                           + uniqueSeqSetId);
4106         }
4107         else
4108         {
4109           System.err.println(
4110                   "Warning : Collision between sequence set ID string and existing jalview object mapping.");
4111         }
4112
4113       }
4114       // Platform.timeCheck("Jalview2XML.loadFromObject-viewport",
4115       // Platform.TIME_MARK);
4116     }
4117     /**
4118      * indicate that annotation colours are applied across all groups (pre
4119      * Jalview 2.8.1 behaviour)
4120      */
4121     boolean doGroupAnnColour = Jalview2XML.isVersionStringLaterThan("2.8.1",
4122             jalviewModel.getVersion());
4123
4124     AlignFrame af = null;
4125     AlignmentPanel ap = null;
4126     AlignViewport av = null;
4127     if (viewId != null)
4128     {
4129       // Check to see if this alignment already has a view id == viewId
4130       jalview.gui.AlignmentPanel views[] = Desktop
4131               .getAlignmentPanels(uniqueSeqSetId);
4132       if (views != null && views.length > 0)
4133       {
4134         for (int v = 0; v < views.length; v++)
4135         {
4136           ap = views[v];
4137           av = ap.av;
4138           if (av.getViewId().equalsIgnoreCase(viewId))
4139           {
4140             // recover the existing alignpanel, alignframe, viewport
4141             af = ap.alignFrame;
4142             break;
4143             // TODO: could even skip resetting view settings if we don't want to
4144             // change the local settings from other jalview processes
4145           }
4146         }
4147       }
4148     }
4149
4150     if (af == null)
4151     {
4152       af = loadViewport(file, jseqs, hiddenSeqs, al, jalviewModel, view,
4153               uniqueSeqSetId, viewId, autoAlan);
4154       av = af.getViewport();
4155       // note that this only retrieves the most recently accessed
4156       // tab of an AlignFrame.
4157       ap = af.alignPanel;
4158     }
4159
4160     /*
4161      * Load any trees, PDB structures and viewers
4162      * 
4163      * Not done if flag is false (when this method is used for New View)
4164      */
4165     final AlignFrame af0 = af;
4166     final AlignViewport av0 = av;
4167     final AlignmentPanel ap0 = ap;
4168     // Platform.timeCheck("Jalview2XML.loadFromObject-beforetree",
4169     // Platform.TIME_MARK);
4170     if (loadTreesAndStructures)
4171     {
4172       if (!jalviewModel.getTree().isEmpty())
4173       {
4174         Jalview.execRunnable(new Runnable()
4175         {
4176           @Override
4177           public void run()
4178           {
4179             // Platform.timeCheck(null, Platform.TIME_MARK);
4180             loadTrees(jalviewModel, view, af0, av0, ap0);
4181             // Platform.timeCheck("Jalview2XML.loadTrees", Platform.TIME_MARK);
4182           }
4183         });
4184       }
4185       if (!jalviewModel.getPcaViewer().isEmpty())
4186       {
4187         Jalview.execRunnable(new Runnable()
4188         {
4189           @Override
4190           public void run()
4191           {
4192             // Platform.timeCheck(null, Platform.TIME_MARK);
4193             loadPCAViewers(jalviewModel, ap0);
4194             // Platform.timeCheck("Jalview2XML.loadPCA", Platform.TIME_MARK);
4195           }
4196         });
4197       }
4198       Jalview.execRunnable(new Runnable()
4199       {
4200         @Override
4201         public void run()
4202         {
4203           // Platform.timeCheck(null, Platform.TIME_MARK);
4204           loadPDBStructures(jprovider, jseqs, af0, ap0);
4205           // Platform.timeCheck("Jalview2XML.loadPDB", Platform.TIME_MARK);
4206         }
4207       });
4208       Jalview.execRunnable(new Runnable()
4209       {
4210         @Override
4211         public void run()
4212         {
4213           loadRnaViewers(jprovider, jseqs, ap0);
4214         }
4215       });
4216
4217     }
4218     // and finally return.
4219     // but do not set holdRepaint true just yet, because this could be the
4220     // initial frame with just its dataset.
4221     return af;
4222   }
4223
4224   /**
4225    * Instantiate and link any saved RNA (Varna) viewers. The state of the Varna
4226    * panel is restored from separate jar entries, two (gapped and trimmed) per
4227    * sequence and secondary structure.
4228    * 
4229    * Currently each viewer shows just one sequence and structure (gapped and
4230    * trimmed), however this method is designed to support multiple sequences or
4231    * structures in viewers if wanted in future.
4232    * 
4233    * @param jprovider
4234    * @param jseqs
4235    * @param ap
4236    */
4237   protected void loadRnaViewers(jarInputStreamProvider jprovider,
4238           List<JSeq> jseqs, AlignmentPanel ap)
4239   {
4240     /*
4241      * scan the sequences for references to viewers; create each one the first
4242      * time it is referenced, add Rna models to existing viewers
4243      */
4244     for (JSeq jseq : jseqs)
4245     {
4246       for (int i = 0; i < jseq.getRnaViewer().size(); i++)
4247       {
4248         RnaViewer viewer = jseq.getRnaViewer().get(i);
4249         AppVarna appVarna = findOrCreateVarnaViewer(viewer, uniqueSetSuffix,
4250                 ap);
4251
4252         for (int j = 0; j < viewer.getSecondaryStructure().size(); j++)
4253         {
4254           SecondaryStructure ss = viewer.getSecondaryStructure().get(j);
4255           SequenceI seq = seqRefIds.get(jseq.getId());
4256           AlignmentAnnotation ann = this.annotationIds
4257                   .get(ss.getAnnotationId());
4258
4259           /*
4260            * add the structure to the Varna display (with session state copied
4261            * from the jar to a temporary file)
4262            */
4263           boolean gapped = safeBoolean(ss.isGapped());
4264           String rnaTitle = ss.getTitle();
4265           String sessionState = ss.getViewerState();
4266           String tempStateFile = copyJarEntry(jprovider, sessionState,
4267                   "varna", null);
4268           RnaModel rna = new RnaModel(rnaTitle, ann, seq, null, gapped);
4269           appVarna.addModelSession(rna, rnaTitle, tempStateFile);
4270         }
4271         appVarna.setInitialSelection(safeInt(viewer.getSelectedRna()));
4272       }
4273     }
4274   }
4275
4276   /**
4277    * Locate and return an already instantiated matching AppVarna, or create one
4278    * if not found
4279    * 
4280    * @param viewer
4281    * @param viewIdSuffix
4282    * @param ap
4283    * @return
4284    */
4285   protected AppVarna findOrCreateVarnaViewer(RnaViewer viewer,
4286           String viewIdSuffix, AlignmentPanel ap)
4287   {
4288     /*
4289      * on each load a suffix is appended to the saved viewId, to avoid conflicts
4290      * if load is repeated
4291      */
4292     String postLoadId = viewer.getViewId() + viewIdSuffix;
4293     for (JInternalFrame frame : getAllFrames())
4294     {
4295       if (frame instanceof AppVarna)
4296       {
4297         AppVarna varna = (AppVarna) frame;
4298         if (postLoadId.equals(varna.getViewId()))
4299         {
4300           // this viewer is already instantiated
4301           // could in future here add ap as another 'parent' of the
4302           // AppVarna window; currently just 1-to-many
4303           return varna;
4304         }
4305       }
4306     }
4307
4308     /*
4309      * viewer not found - make it
4310      */
4311     RnaViewerModel model = new RnaViewerModel(postLoadId, viewer.getTitle(),
4312             safeInt(viewer.getXpos()), safeInt(viewer.getYpos()),
4313             safeInt(viewer.getWidth()), safeInt(viewer.getHeight()),
4314             safeInt(viewer.getDividerLocation()));
4315     AppVarna varna = new AppVarna(model, ap);
4316
4317     return varna;
4318   }
4319
4320   /**
4321    * Load any saved trees
4322    * 
4323    * @param jm
4324    * @param view
4325    * @param af
4326    * @param av
4327    * @param ap
4328    */
4329   protected void loadTrees(JalviewModel jm, Viewport view,
4330           AlignFrame af, AlignViewport av, AlignmentPanel ap)
4331   {
4332     // TODO result of automated refactoring - are all these parameters needed?
4333     try
4334     {
4335       for (int t = 0; t < jm.getTree().size(); t++)
4336       {
4337
4338         Tree tree = jm.getTree().get(t);
4339
4340         TreePanel tp = (TreePanel) retrieveExistingObj(tree.getId());
4341         if (tp == null)
4342         {
4343           tp = af.showNewickTree(new NewickFile(tree.getNewick()),
4344                   tree.getTitle(), safeInt(tree.getWidth()),
4345                   safeInt(tree.getHeight()), safeInt(tree.getXpos()),
4346                   safeInt(tree.getYpos()));
4347           if (tree.getId() != null)
4348           {
4349             // perhaps bind the tree id to something ?
4350           }
4351         }
4352         else
4353         {
4354           // update local tree attributes ?
4355           // TODO: should check if tp has been manipulated by user - if so its
4356           // settings shouldn't be modified
4357           tp.setTitle(tree.getTitle());
4358           tp.setBounds(new Rectangle(safeInt(tree.getXpos()),
4359                   safeInt(tree.getYpos()), safeInt(tree.getWidth()),
4360                   safeInt(tree.getHeight())));
4361           tp.setViewport(av); // af.viewport;
4362           // TODO: verify 'associate with all views' works still
4363           tp.getTreeCanvas().setViewport(av); // af.viewport;
4364           tp.getTreeCanvas().setAssociatedPanel(ap); // af.alignPanel;
4365         }
4366         tp.getTreeCanvas().setApplyToAllViews(tree.isLinkToAllViews());
4367         if (tp == null)
4368         {
4369           warn("There was a problem recovering stored Newick tree: \n"
4370                   + tree.getNewick());
4371           continue;
4372         }
4373
4374         tp.fitToWindow.setState(safeBoolean(tree.isFitToWindow()));
4375         tp.fitToWindow_actionPerformed(null);
4376
4377         if (tree.getFontName() != null)
4378         {
4379           tp.setTreeFont(
4380                   new Font(tree.getFontName(), safeInt(tree.getFontStyle()),
4381                           safeInt(tree.getFontSize())));
4382         }
4383         else
4384         {
4385           tp.setTreeFont(
4386                   new Font(view.getFontName(), safeInt(view.getFontStyle()),
4387                           safeInt(view.getFontSize())));
4388         }
4389
4390         tp.showPlaceholders(safeBoolean(tree.isMarkUnlinked()));
4391         tp.showBootstrap(safeBoolean(tree.isShowBootstrap()));
4392         tp.showDistances(safeBoolean(tree.isShowDistances()));
4393
4394         tp.getTreeCanvas().setThreshold(safeFloat(tree.getThreshold()));
4395
4396         if (safeBoolean(tree.isCurrentTree()))
4397         {
4398           af.getViewport().setCurrentTree(tp.getTree());
4399         }
4400       }
4401
4402     } catch (Exception ex)
4403     {
4404       ex.printStackTrace();
4405     }
4406   }
4407
4408   /**
4409    * Load and link any saved structure viewers.
4410    * 
4411    * @param jprovider
4412    * @param jseqs
4413    * @param af
4414    * @param ap
4415    */
4416   protected void loadPDBStructures(jarInputStreamProvider jprovider,
4417           List<JSeq> jseqs, AlignFrame af, AlignmentPanel ap)
4418   {
4419     /*
4420      * Run through all PDB ids on the alignment, and collect mappings between
4421      * distinct view ids and all sequences referring to that view.
4422      */
4423     Map<String, StructureViewerModel> structureViewers = new LinkedHashMap<>();
4424
4425     for (int i = 0; i < jseqs.size(); i++)
4426     {
4427       JSeq jseq = jseqs.get(i);
4428       if (jseq.getPdbids().size() > 0)
4429       {
4430         List<Pdbids> ids = jseq.getPdbids();
4431         for (int p = 0; p < ids.size(); p++)
4432         {
4433           Pdbids pdbid = ids.get(p);
4434           final int structureStateCount = pdbid.getStructureState().size();
4435           for (int s = 0; s < structureStateCount; s++)
4436           {
4437             // check to see if we haven't already created this structure view
4438             final StructureState structureState = pdbid
4439                     .getStructureState().get(s);
4440             String sviewid = (structureState.getViewId() == null) ? null
4441                     : structureState.getViewId() + uniqueSetSuffix;
4442             jalview.datamodel.PDBEntry jpdb = new jalview.datamodel.PDBEntry();
4443             // Originally : pdbid.getFile()
4444             // : TODO: verify external PDB file recovery still works in normal
4445             // jalview project load
4446             jpdb.setFile(
4447                     loadPDBFile(jprovider, pdbid.getId(), pdbid.getFile()));
4448             jpdb.setId(pdbid.getId());
4449
4450             int x = safeInt(structureState.getXpos());
4451             int y = safeInt(structureState.getYpos());
4452             int width = safeInt(structureState.getWidth());
4453             int height = safeInt(structureState.getHeight());
4454
4455             // Probably don't need to do this anymore...
4456             // Desktop.getDesktop().getComponentAt(x, y);
4457             // TODO: NOW: check that this recovers the PDB file correctly.
4458             String pdbFile = loadPDBFile(jprovider, pdbid.getId(),
4459                     pdbid.getFile());
4460             jalview.datamodel.SequenceI seq = seqRefIds
4461                     .get(jseq.getId() + "");
4462             if (sviewid == null)
4463             {
4464               sviewid = "_jalview_pre2_4_" + x + "," + y + "," + width + ","
4465                       + height;
4466             }
4467             if (!structureViewers.containsKey(sviewid))
4468             {
4469               structureViewers.put(sviewid,
4470                       new StructureViewerModel(x, y, width, height, false,
4471                               false, true, structureState.getViewId(),
4472                               structureState.getType()));
4473               // Legacy pre-2.7 conversion JAL-823 :
4474               // do not assume any view has to be linked for colour by
4475               // sequence
4476             }
4477
4478             // assemble String[] { pdb files }, String[] { id for each
4479             // file }, orig_fileloc, SequenceI[][] {{ seqs_file 1 }, {
4480             // seqs_file 2}, boolean[] {
4481             // linkAlignPanel,superposeWithAlignpanel}} from hash
4482             StructureViewerModel jmoldat = structureViewers.get(sviewid);
4483             jmoldat.setAlignWithPanel(jmoldat.isAlignWithPanel()
4484                     || structureState.isAlignwithAlignPanel());
4485
4486             /*
4487              * Default colour by linked panel to false if not specified (e.g.
4488              * for pre-2.7 projects)
4489              */
4490             boolean colourWithAlignPanel = jmoldat.isColourWithAlignPanel();
4491             colourWithAlignPanel |= structureState.isColourwithAlignPanel();
4492             jmoldat.setColourWithAlignPanel(colourWithAlignPanel);
4493
4494             /*
4495              * Default colour by viewer to true if not specified (e.g. for
4496              * pre-2.7 projects)
4497              */
4498             boolean colourByViewer = jmoldat.isColourByViewer();
4499             colourByViewer &= structureState.isColourByJmol();
4500             jmoldat.setColourByViewer(colourByViewer);
4501
4502             if (jmoldat.getStateData().length() < structureState
4503                     .getValue()/*Content()*/.length())
4504             {
4505               jmoldat.setStateData(structureState.getValue());// Content());
4506             }
4507             if (pdbid.getFile() != null)
4508             {
4509               File mapkey = new File(pdbid.getFile());
4510               StructureData seqstrmaps = jmoldat.getFileData().get(mapkey);
4511               if (seqstrmaps == null)
4512               {
4513                 jmoldat.getFileData().put(mapkey,
4514                         seqstrmaps = jmoldat.new StructureData(pdbFile,
4515                                 pdbid.getId()));
4516               }
4517               if (!seqstrmaps.getSeqList().contains(seq))
4518               {
4519                 seqstrmaps.getSeqList().add(seq);
4520                 // TODO and chains?
4521               }
4522             }
4523             else
4524             {
4525               errorMessage = ("The Jmol views in this project were imported\nfrom an older version of Jalview.\nPlease review the sequence colour associations\nin the Colour by section of the Jmol View menu.\n\nIn the case of problems, see note at\nhttp://issues.jalview.org/browse/JAL-747");
4526               warn(errorMessage);
4527             }
4528           }
4529         }
4530       }
4531     }
4532     // Instantiate the associated structure views
4533     for (Entry<String, StructureViewerModel> entry : structureViewers
4534             .entrySet())
4535     {
4536       try
4537       {
4538         createOrLinkStructureViewer(entry, af, ap, jprovider);
4539       } catch (Exception e)
4540       {
4541         System.err.println(
4542                 "Error loading structure viewer: " + e.getMessage());
4543         // failed - try the next one
4544       }
4545     }
4546   }
4547
4548   /**
4549    * 
4550    * @param viewerData
4551    * @param af
4552    * @param ap
4553    * @param jprovider
4554    */
4555   protected void createOrLinkStructureViewer(
4556           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
4557           AlignmentPanel ap, jarInputStreamProvider jprovider)
4558   {
4559     final StructureViewerModel stateData = viewerData.getValue();
4560
4561     /*
4562      * Search for any viewer windows already open from other alignment views
4563      * that exactly match the stored structure state
4564      */
4565     StructureViewerBase comp = findMatchingViewer(viewerData);
4566
4567     if (comp != null)
4568     {
4569       linkStructureViewer(ap, comp, stateData);
4570       return;
4571     }
4572
4573     /*
4574      * From 2.9: stateData.type contains JMOL or CHIMERA, data is in jar entry
4575      * "viewer_"+stateData.viewId
4576      */
4577     if (ViewerType.CHIMERA.toString().equals(stateData.getType()))
4578     {
4579       createChimeraViewer(viewerData, af, jprovider);
4580     }
4581     else
4582     {
4583       /*
4584        * else Jmol (if pre-2.9, stateData contains JMOL state string)
4585        */
4586       createJmolViewer(viewerData, af, jprovider);
4587     }
4588   }
4589
4590   /**
4591    * Create a new Chimera viewer.
4592    * 
4593    * @param data
4594    * @param af
4595    * @param jprovider
4596    */
4597   protected void createChimeraViewer(
4598           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
4599           jarInputStreamProvider jprovider)
4600   {
4601     StructureViewerModel data = viewerData.getValue();
4602     String chimeraSessionFile = data.getStateData();
4603
4604     /*
4605      * Copy Chimera session from jar entry "viewer_"+viewId to a temporary file
4606      * 
4607      * NB this is the 'saved' viewId as in the project file XML, _not_ the
4608      * 'uniquified' sviewid used to reconstruct the viewer here
4609      */
4610     String viewerJarEntryName = getViewerJarEntryName(data.getViewId());
4611     chimeraSessionFile = copyJarEntry(jprovider, viewerJarEntryName,
4612             "chimera", null);
4613
4614     Set<Entry<File, StructureData>> fileData = data.getFileData()
4615             .entrySet();
4616     List<PDBEntry> pdbs = new ArrayList<>();
4617     List<SequenceI[]> allseqs = new ArrayList<>();
4618     for (Entry<File, StructureData> pdb : fileData)
4619     {
4620       String filePath = pdb.getValue().getFilePath();
4621       String pdbId = pdb.getValue().getPdbId();
4622       // pdbs.add(new PDBEntry(filePath, pdbId));
4623       pdbs.add(new PDBEntry(pdbId, null, PDBEntry.Type.PDB, filePath));
4624       final List<SequenceI> seqList = pdb.getValue().getSeqList();
4625       SequenceI[] seqs = seqList.toArray(new SequenceI[seqList.size()]);
4626       allseqs.add(seqs);
4627     }
4628
4629     boolean colourByChimera = data.isColourByViewer();
4630     boolean colourBySequence = data.isColourWithAlignPanel();
4631
4632     // TODO use StructureViewer as a factory here, see JAL-1761
4633     final PDBEntry[] pdbArray = pdbs.toArray(new PDBEntry[pdbs.size()]);
4634     final SequenceI[][] seqsArray = allseqs
4635             .toArray(new SequenceI[allseqs.size()][]);
4636     String newViewId = viewerData.getKey();
4637
4638     ChimeraViewFrame cvf = new ChimeraViewFrame(chimeraSessionFile,
4639             af.alignPanel, pdbArray, seqsArray, colourByChimera,
4640             colourBySequence, newViewId);
4641     cvf.setSize(data.getWidth(), data.getHeight());
4642     cvf.setLocation(data.getX(), data.getY());
4643   }
4644
4645   /**
4646    * Create a new Jmol window. First parse the Jmol state to translate filenames
4647    * loaded into the view, and record the order in which files are shown in the
4648    * Jmol view, so we can add the sequence mappings in same order.
4649    * 
4650    * @param viewerData
4651    * @param af
4652    * @param jprovider
4653    */
4654   protected void createJmolViewer(
4655           final Entry<String, StructureViewerModel> viewerData,
4656           AlignFrame af, jarInputStreamProvider jprovider)
4657   {
4658     final StructureViewerModel svattrib = viewerData.getValue();
4659     String state = svattrib.getStateData();
4660
4661     /*
4662      * Pre-2.9: state element value is the Jmol state string
4663      * 
4664      * 2.9+: @type is "JMOL", state data is in a Jar file member named "viewer_"
4665      * + viewId
4666      */
4667     if (ViewerType.JMOL.toString().equals(svattrib.getType()))
4668     {
4669       state = readJarEntry(jprovider,
4670               getViewerJarEntryName(svattrib.getViewId()));
4671     }
4672
4673     List<String> pdbfilenames = new ArrayList<>();
4674     List<SequenceI[]> seqmaps = new ArrayList<>();
4675     List<String> pdbids = new ArrayList<>();
4676     StringBuilder newFileLoc = new StringBuilder(64);
4677     int cp = 0, ncp, ecp;
4678     Map<File, StructureData> oldFiles = svattrib.getFileData();
4679     while ((ncp = state.indexOf("load ", cp)) > -1)
4680     {
4681       do
4682       {
4683         // look for next filename in load statement
4684         newFileLoc.append(state.substring(cp,
4685                 ncp = (state.indexOf("\"", ncp + 1) + 1)));
4686         String oldfilenam = state.substring(ncp,
4687                 ecp = state.indexOf("\"", ncp));
4688         // recover the new mapping data for this old filename
4689         // have to normalize filename - since Jmol and jalview do
4690         // filename
4691         // translation differently.
4692         StructureData filedat = oldFiles.get(new File(oldfilenam));
4693         if (filedat == null)
4694         {
4695           String reformatedOldFilename = oldfilenam.replaceAll("/", "\\\\");
4696           filedat = oldFiles.get(new File(reformatedOldFilename));
4697         }
4698         newFileLoc.append(Platform.escapeString(filedat.getFilePath()));
4699         pdbfilenames.add(filedat.getFilePath());
4700         pdbids.add(filedat.getPdbId());
4701         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
4702         newFileLoc.append("\"");
4703         cp = ecp + 1; // advance beyond last \" and set cursor so we can
4704                       // look for next file statement.
4705       } while ((ncp = state.indexOf("/*file*/", cp)) > -1);
4706     }
4707     if (cp > 0)
4708     {
4709       // just append rest of state
4710       newFileLoc.append(state.substring(cp));
4711     }
4712     else
4713     {
4714       System.err.print("Ignoring incomplete Jmol state for PDB ids: ");
4715       newFileLoc = new StringBuilder(state);
4716       newFileLoc.append("; load append ");
4717       for (File id : oldFiles.keySet())
4718       {
4719         // add this and any other pdb files that should be present in
4720         // the viewer
4721         StructureData filedat = oldFiles.get(id);
4722         newFileLoc.append(filedat.getFilePath());
4723         pdbfilenames.add(filedat.getFilePath());
4724         pdbids.add(filedat.getPdbId());
4725         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
4726         newFileLoc.append(" \"");
4727         newFileLoc.append(filedat.getFilePath());
4728         newFileLoc.append("\"");
4729
4730       }
4731       newFileLoc.append(";");
4732     }
4733
4734     if (newFileLoc.length() == 0)
4735     {
4736       return;
4737     }
4738     int histbug = newFileLoc.indexOf("history = ");
4739     if (histbug > -1)
4740     {
4741       /*
4742        * change "history = [true|false];" to "history = [1|0];"
4743        */
4744       histbug += 10;
4745       int diff = histbug == -1 ? -1 : newFileLoc.indexOf(";", histbug);
4746       String val = (diff == -1) ? null
4747               : newFileLoc.substring(histbug, diff);
4748       if (val != null && val.length() >= 4)
4749       {
4750         if (val.contains("e")) // eh? what can it be?
4751         {
4752           if (val.trim().equals("true"))
4753           {
4754             val = "1";
4755           }
4756           else
4757           {
4758             val = "0";
4759           }
4760           newFileLoc.replace(histbug, diff, val);
4761         }
4762       }
4763     }
4764
4765     final String[] pdbf = pdbfilenames
4766             .toArray(new String[pdbfilenames.size()]);
4767     final String[] id = pdbids.toArray(new String[pdbids.size()]);
4768     final SequenceI[][] sq = seqmaps
4769             .toArray(new SequenceI[seqmaps.size()][]);
4770     final String fileloc = newFileLoc.toString();
4771     final String sviewid = viewerData.getKey();
4772     final AlignFrame alf = af;
4773     final Rectangle rect = new Rectangle(svattrib.getX(), svattrib.getY(),
4774             svattrib.getWidth(), svattrib.getHeight());
4775     // try
4776     // {
4777     Jalview.execRunnable(new Runnable()
4778       {
4779         @Override
4780         public void run()
4781         {
4782           JalviewStructureDisplayI sview = null;
4783           try
4784           {
4785             sview = new StructureViewer(
4786                     alf.alignPanel.getStructureSelectionManager())
4787                             .createView(StructureViewer.ViewerType.JMOL,
4788                                     pdbf, id, sq, alf.alignPanel, svattrib,
4789                                     fileloc, rect, sviewid);
4790             addNewStructureViewer(sview);
4791           } catch (OutOfMemoryError ex)
4792           {
4793             new OOMWarning("restoring structure view for PDB id " + id,
4794                     (OutOfMemoryError) ex.getCause());
4795             if (sview != null && sview.isVisible())
4796             {
4797               sview.closeViewer(false);
4798               sview.setVisible(false);
4799               sview.dispose();
4800             }
4801           }
4802         }
4803     });
4804     // javax.swing.SwingUtilities.invokeLater(r);
4805     // } catch (InvocationTargetException ex)
4806     // {
4807     // warn("Unexpected error when opening Jmol view.", ex);
4808     //
4809     // } catch (InterruptedException e)
4810     // {
4811     // // e.printStackTrace();
4812     // }
4813
4814   }
4815
4816   /**
4817    * Generates a name for the entry in the project jar file to hold state
4818    * information for a structure viewer
4819    * 
4820    * @param viewId
4821    * @return
4822    */
4823   protected String getViewerJarEntryName(String viewId)
4824   {
4825     return VIEWER_PREFIX + viewId;
4826   }
4827
4828   /**
4829    * Returns any open frame that matches given structure viewer data. The match
4830    * is based on the unique viewId, or (for older project versions) the frame's
4831    * geometry.
4832    * 
4833    * @param viewerData
4834    * @return
4835    */
4836   protected StructureViewerBase findMatchingViewer(
4837           Entry<String, StructureViewerModel> viewerData)
4838   {
4839     final String sviewid = viewerData.getKey();
4840     final StructureViewerModel svattrib = viewerData.getValue();
4841     StructureViewerBase comp = null;
4842     JInternalFrame[] frames = getAllFrames();
4843     for (JInternalFrame frame : frames)
4844     {
4845       if (frame instanceof StructureViewerBase)
4846       {
4847         /*
4848          * Post jalview 2.4 schema includes structure view id
4849          */
4850         if (sviewid != null && ((StructureViewerBase) frame).getViewId()
4851                 .equals(sviewid))
4852         {
4853           comp = (StructureViewerBase) frame;
4854           break; // break added in 2.9
4855         }
4856         /*
4857          * Otherwise test for matching position and size of viewer frame
4858          */
4859         else if (frame.getX() == svattrib.getX()
4860                 && frame.getY() == svattrib.getY()
4861                 && frame.getHeight() == svattrib.getHeight()
4862                 && frame.getWidth() == svattrib.getWidth())
4863         {
4864           comp = (StructureViewerBase) frame;
4865           // no break in faint hope of an exact match on viewId
4866         }
4867       }
4868     }
4869     return comp;
4870   }
4871
4872   /**
4873    * Link an AlignmentPanel to an existing structure viewer.
4874    * 
4875    * @param ap
4876    * @param viewer
4877    * @param oldFiles
4878    * @param useinViewerSuperpos
4879    * @param usetoColourbyseq
4880    * @param viewerColouring
4881    */
4882   protected void linkStructureViewer(AlignmentPanel ap,
4883           StructureViewerBase viewer, StructureViewerModel stateData)
4884   {
4885     // NOTE: if the jalview project is part of a shared session then
4886     // view synchronization should/could be done here.
4887
4888     final boolean useinViewerSuperpos = stateData.isAlignWithPanel();
4889     final boolean usetoColourbyseq = stateData.isColourWithAlignPanel();
4890     final boolean viewerColouring = stateData.isColourByViewer();
4891     Map<File, StructureData> oldFiles = stateData.getFileData();
4892
4893     /*
4894      * Add mapping for sequences in this view to an already open viewer
4895      */
4896     final AAStructureBindingModel binding = viewer.getBinding();
4897     for (File id : oldFiles.keySet())
4898     {
4899       // add this and any other pdb files that should be present in the
4900       // viewer
4901       StructureData filedat = oldFiles.get(id);
4902       String pdbFile = filedat.getFilePath();
4903       SequenceI[] seq = filedat.getSeqList().toArray(new SequenceI[0]);
4904       binding.getSsm().setMapping(seq, null, pdbFile, DataSourceType.FILE,
4905               null);
4906       binding.addSequenceForStructFile(pdbFile, seq);
4907     }
4908     // and add the AlignmentPanel's reference to the view panel
4909     viewer.addAlignmentPanel(ap);
4910     if (useinViewerSuperpos)
4911     {
4912       viewer.useAlignmentPanelForSuperposition(ap);
4913     }
4914     else
4915     {
4916       viewer.excludeAlignmentPanelForSuperposition(ap);
4917     }
4918     if (usetoColourbyseq)
4919     {
4920       viewer.useAlignmentPanelForColourbyseq(ap, !viewerColouring);
4921     }
4922     else
4923     {
4924       viewer.excludeAlignmentPanelForColourbyseq(ap);
4925     }
4926   }
4927
4928   /**
4929    * Get all frames within the Desktop.
4930    * 
4931    * @return
4932    */
4933   protected JInternalFrame[] getAllFrames()
4934   {
4935     JInternalFrame[] frames = null;
4936     // TODO is this necessary - is it safe - risk of hanging?
4937     do
4938     {
4939       try
4940       {
4941         frames = Desktop.getDesktopPane().getAllFrames();
4942       } catch (ArrayIndexOutOfBoundsException e)
4943       {
4944         // occasional No such child exceptions are thrown here...
4945         try
4946         {
4947           Thread.sleep(10);
4948         } catch (InterruptedException f)
4949         {
4950         }
4951       }
4952     } while (frames == null);
4953     return frames;
4954   }
4955
4956   /**
4957    * Answers true if 'version' is equal to or later than 'supported', where each
4958    * is formatted as major/minor versions like "2.8.3" or "2.3.4b1" for bugfix
4959    * changes. Development and test values for 'version' are leniently treated
4960    * i.e. answer true.
4961    * 
4962    * @param supported
4963    *          - minimum version we are comparing against
4964    * @param version
4965    *          - version of data being processsed
4966    * @return
4967    */
4968   public static boolean isVersionStringLaterThan(String supported,
4969           String version)
4970   {
4971     if (supported == null || version == null
4972             || version.equalsIgnoreCase("DEVELOPMENT BUILD")
4973             || version.equalsIgnoreCase("Test")
4974             || version.equalsIgnoreCase("AUTOMATED BUILD"))
4975     {
4976       System.err.println("Assuming project file with "
4977               + (version == null ? "null" : version)
4978               + " is compatible with Jalview version " + supported);
4979       return true;
4980     }
4981     else
4982     {
4983       return StringUtils.compareVersions(version, supported, "b") >= 0;
4984     }
4985   }
4986
4987   Vector<JalviewStructureDisplayI> newStructureViewers = null;
4988
4989   protected void addNewStructureViewer(JalviewStructureDisplayI sview)
4990   {
4991     if (newStructureViewers != null)
4992     {
4993       sview.getBinding().setFinishedLoadingFromArchive(false);
4994       newStructureViewers.add(sview);
4995     }
4996   }
4997
4998   protected void setLoadingFinishedForNewStructureViewers()
4999   {
5000     if (newStructureViewers != null)
5001     {
5002       for (JalviewStructureDisplayI sview : newStructureViewers)
5003       {
5004         sview.getBinding().setFinishedLoadingFromArchive(true);
5005       }
5006       newStructureViewers.clear();
5007       newStructureViewers = null;
5008     }
5009   }
5010
5011   AlignFrame loadViewport(String fileName, List<JSeq> JSEQ,
5012           List<SequenceI> hiddenSeqs, AlignmentI al, JalviewModel jm,
5013           Viewport view, String uniqueSeqSetId, String viewId,
5014           List<JvAnnotRow> autoAlan)
5015   {
5016     AlignFrame af = null;
5017     af = new AlignFrame(al, safeInt(view.getWidth()),
5018             safeInt(view.getHeight()), uniqueSeqSetId, viewId)
5019     // {
5020     //
5021     // @Override
5022     // protected void processKeyEvent(java.awt.event.KeyEvent e) {
5023     // System.out.println("Jalview2XML AF " + e);
5024     // super.processKeyEvent(e);
5025     //
5026     // }
5027     //
5028     // }
5029     ;
5030     af.alignPanel.setHoldRepaint(true);
5031     af.setFileName(fileName, FileFormat.Jalview);
5032     af.setFileObject(jarFile); // BH 2019 JAL-3436
5033
5034     final AlignViewport viewport = af.getViewport();
5035     for (int i = 0; i < JSEQ.size(); i++)
5036     {
5037       int colour = safeInt(JSEQ.get(i).getColour());
5038       viewport.setSequenceColour(viewport.getAlignment().getSequenceAt(i),
5039               new Color(colour));
5040     }
5041
5042     if (al.hasSeqrep())
5043     {
5044       viewport.setColourByReferenceSeq(true);
5045       viewport.setDisplayReferenceSeq(true);
5046     }
5047
5048     viewport.setGatherViewsHere(safeBoolean(view.isGatheredViews()));
5049
5050     if (view.getSequenceSetId() != null)
5051     {
5052       AlignmentViewport av = viewportsAdded.get(uniqueSeqSetId);
5053
5054       viewport.setSequenceSetId(uniqueSeqSetId);
5055       if (av != null)
5056       {
5057         // propagate shared settings to this new view
5058         viewport.setHistoryList(av.getHistoryList());
5059         viewport.setRedoList(av.getRedoList());
5060       }
5061       else
5062       {
5063         viewportsAdded.put(uniqueSeqSetId, viewport);
5064       }
5065       // TODO: check if this method can be called repeatedly without
5066       // side-effects if alignpanel already registered.
5067       PaintRefresher.Register(af.alignPanel, uniqueSeqSetId);
5068     }
5069     // apply Hidden regions to view.
5070     if (hiddenSeqs != null)
5071     {
5072       for (int s = 0; s < JSEQ.size(); s++)
5073       {
5074         SequenceGroup hidden = new SequenceGroup();
5075         boolean isRepresentative = false;
5076         for (int r = 0; r < JSEQ.get(s).getHiddenSequences().size(); r++)
5077         {
5078           isRepresentative = true;
5079           SequenceI sequenceToHide = al
5080                   .getSequenceAt(JSEQ.get(s).getHiddenSequences().get(r));
5081           hidden.addSequence(sequenceToHide, false);
5082           // remove from hiddenSeqs list so we don't try to hide it twice
5083           hiddenSeqs.remove(sequenceToHide);
5084         }
5085         if (isRepresentative)
5086         {
5087           SequenceI representativeSequence = al.getSequenceAt(s);
5088           hidden.addSequence(representativeSequence, false);
5089           viewport.hideRepSequences(representativeSequence, hidden);
5090         }
5091       }
5092
5093       SequenceI[] hseqs = hiddenSeqs
5094               .toArray(new SequenceI[hiddenSeqs.size()]);
5095       viewport.hideSequence(hseqs);
5096
5097     }
5098     // recover view properties and display parameters
5099
5100     viewport.setShowAnnotation(safeBoolean(view.isShowAnnotation()));
5101     viewport.setAbovePIDThreshold(safeBoolean(view.isPidSelected()));
5102     final int pidThreshold = safeInt(view.getPidThreshold());
5103     viewport.setThreshold(pidThreshold);
5104
5105     viewport.setColourText(safeBoolean(view.isShowColourText()));
5106
5107     viewport.setConservationSelected(
5108             safeBoolean(view.isConservationSelected()));
5109     viewport.setIncrement(safeInt(view.getConsThreshold()));
5110     viewport.setShowJVSuffix(safeBoolean(view.isShowFullId()));
5111     viewport.setRightAlignIds(safeBoolean(view.isRightAlignIds()));
5112     viewport.setFont(new Font(view.getFontName(),
5113             safeInt(view.getFontStyle()), safeInt(view.getFontSize())),
5114             true);
5115     ViewStyleI vs = viewport.getViewStyle();
5116     vs.setScaleProteinAsCdna(view.isScaleProteinAsCdna());
5117     viewport.setViewStyle(vs);
5118     // TODO: allow custom charWidth/Heights to be restored by updating them
5119     // after setting font - which means set above to false
5120     viewport.setRenderGaps(safeBoolean(view.isRenderGaps()));
5121     viewport.setWrapAlignment(safeBoolean(view.isWrapAlignment()));
5122     viewport.setShowAnnotation(safeBoolean(view.isShowAnnotation()));
5123
5124     viewport.setShowBoxes(safeBoolean(view.isShowBoxes()));
5125
5126     viewport.setShowText(safeBoolean(view.isShowText()));
5127
5128     viewport.setTextColour(new Color(safeInt(view.getTextCol1())));
5129     viewport.setTextColour2(new Color(safeInt(view.getTextCol2())));
5130     viewport.setThresholdTextColour(safeInt(view.getTextColThreshold()));
5131     viewport.setShowUnconserved(view.isShowUnconserved());
5132     viewport.getRanges().setStartRes(safeInt(view.getStartRes()));
5133
5134     if (view.getViewName() != null)
5135     {
5136       viewport.setViewName(view.getViewName());
5137       af.setInitialTabVisible();
5138     }
5139     int x = safeInt(view.getXpos());
5140     int y = safeInt(view.getYpos());
5141     int w = safeInt(view.getWidth());
5142     int h = safeInt(view.getHeight());
5143     // // BH we cannot let the title bar go off the top
5144     // if (Platform.isJS())
5145     // {
5146     // x = Math.max(50 - w, x);
5147     // y = Math.max(0, y);
5148     // }
5149
5150     af.setBounds(x, y, w, h);
5151     // startSeq set in af.alignPanel.updateLayout below
5152     af.alignPanel.updateLayout();
5153     ColourSchemeI cs = null;
5154     // apply colourschemes
5155     if (view.getBgColour() != null)
5156     {
5157       if (view.getBgColour().startsWith("ucs"))
5158       {
5159         cs = getUserColourScheme(jm, view.getBgColour());
5160       }
5161       else if (view.getBgColour().startsWith("Annotation"))
5162       {
5163         AnnotationColourScheme viewAnnColour = view.getAnnotationColours();
5164         cs = constructAnnotationColour(viewAnnColour, af, al, jm, true);
5165
5166         // annpos
5167
5168       }
5169       else
5170       {
5171         cs = ColourSchemeProperty.getColourScheme(af.getViewport(), al,
5172                 view.getBgColour());
5173       }
5174     }
5175
5176     /*
5177      * turn off 'alignment colour applies to all groups'
5178      * while restoring global colour scheme
5179      */
5180     viewport.setColourAppliesToAllGroups(false);
5181     viewport.setGlobalColourScheme(cs);
5182     viewport.getResidueShading().setThreshold(pidThreshold,
5183             view.isIgnoreGapsinConsensus());
5184     viewport.getResidueShading()
5185             .setConsensus(viewport.getSequenceConsensusHash());
5186     if (safeBoolean(view.isConservationSelected()) && cs != null)
5187     {
5188       viewport.getResidueShading()
5189               .setConservationInc(safeInt(view.getConsThreshold()));
5190     }
5191     af.changeColour(cs);
5192     viewport.setColourAppliesToAllGroups(true);
5193
5194     viewport.setShowSequenceFeatures(
5195             safeBoolean(view.isShowSequenceFeatures()));
5196
5197     viewport.setCentreColumnLabels(view.isCentreColumnLabels());
5198     viewport.setIgnoreGapsConsensus(view.isIgnoreGapsinConsensus(), null);
5199     viewport.setFollowHighlight(view.isFollowHighlight());
5200     viewport.followSelection = view.isFollowSelection();
5201     viewport.setShowConsensusHistogram(view.isShowConsensusHistogram());
5202     viewport.setShowSequenceLogo(view.isShowSequenceLogo());
5203     viewport.setNormaliseSequenceLogo(view.isNormaliseSequenceLogo());
5204     viewport.setShowDBRefs(safeBoolean(view.isShowDbRefTooltip()));
5205     viewport.setShowNPFeats(safeBoolean(view.isShowNPfeatureTooltip()));
5206     viewport.setShowGroupConsensus(view.isShowGroupConsensus());
5207     viewport.setShowGroupConservation(view.isShowGroupConservation());
5208
5209     // recover feature settings
5210     if (jm.getFeatureSettings() != null)
5211     {
5212       FeatureRenderer fr = af.alignPanel.getSeqPanel().seqCanvas
5213               .getFeatureRenderer();
5214       FeaturesDisplayed fdi;
5215       viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
5216       String[] renderOrder = new String[jm.getFeatureSettings().getSetting()
5217               .size()];
5218       Map<String, FeatureColourI> featureColours = new Hashtable<>();
5219       Map<String, Float> featureOrder = new Hashtable<>();
5220
5221       for (int fs = 0; fs < jm.getFeatureSettings().getSetting()
5222               .size(); fs++)
5223       {
5224         Setting setting = jm.getFeatureSettings().getSetting().get(fs);
5225         String featureType = setting.getType();
5226
5227         /*
5228          * restore feature filters (if any)
5229          */
5230         jalview.xml.binding.jalview.FeatureMatcherSet filters = setting
5231                 .getMatcherSet();
5232         if (filters != null)
5233         {
5234           FeatureMatcherSetI filter = Jalview2XML.parseFilter(featureType,
5235                   filters);
5236           if (!filter.isEmpty())
5237           {
5238             fr.setFeatureFilter(featureType, filter);
5239           }
5240         }
5241
5242         /*
5243          * restore feature colour scheme
5244          */
5245         Color maxColour = new Color(setting.getColour());
5246         if (setting.getMincolour() != null)
5247         {
5248           /*
5249            * minColour is always set unless a simple colour
5250            * (including for colour by label though it doesn't use it)
5251            */
5252           Color minColour = new Color(setting.getMincolour().intValue());
5253           Color noValueColour = minColour;
5254           NoValueColour noColour = setting.getNoValueColour();
5255           if (noColour == NoValueColour.NONE)
5256           {
5257             noValueColour = null;
5258           }
5259           else if (noColour == NoValueColour.MAX)
5260           {
5261             noValueColour = maxColour;
5262           }
5263           float min = safeFloat(safeFloat(setting.getMin()));
5264           float max = setting.getMax() == null ? 1f
5265                   : setting.getMax().floatValue();
5266           FeatureColourI gc = new FeatureColour(maxColour, minColour,
5267                   maxColour, noValueColour, min, max);
5268           if (setting.getAttributeName().size() > 0)
5269           {
5270             gc.setAttributeName(setting.getAttributeName().toArray(
5271                     new String[setting.getAttributeName().size()]));
5272           }
5273           if (setting.getThreshold() != null)
5274           {
5275             gc.setThreshold(setting.getThreshold().floatValue());
5276             int threshstate = safeInt(setting.getThreshstate());
5277             // -1 = None, 0 = Below, 1 = Above threshold
5278             if (threshstate == 0)
5279             {
5280               gc.setBelowThreshold(true);
5281             }
5282             else if (threshstate == 1)
5283             {
5284               gc.setAboveThreshold(true);
5285             }
5286           }
5287           gc.setAutoScaled(true); // default
5288           if (setting.isAutoScale() != null)
5289           {
5290             gc.setAutoScaled(setting.isAutoScale());
5291           }
5292           if (setting.isColourByLabel() != null)
5293           {
5294             gc.setColourByLabel(setting.isColourByLabel());
5295           }
5296           // and put in the feature colour table.
5297           featureColours.put(featureType, gc);
5298         }
5299         else
5300         {
5301           featureColours.put(featureType, new FeatureColour(maxColour));
5302         }
5303         renderOrder[fs] = featureType;
5304         if (setting.getOrder() != null)
5305         {
5306           featureOrder.put(featureType, setting.getOrder().floatValue());
5307         }
5308         else
5309         {
5310           featureOrder.put(featureType, new Float(
5311                   fs / jm.getFeatureSettings().getSetting().size()));
5312         }
5313         if (safeBoolean(setting.isDisplay()))
5314         {
5315           fdi.setVisible(featureType);
5316         }
5317       }
5318       Map<String, Boolean> fgtable = new Hashtable<>();
5319       for (int gs = 0; gs < jm.getFeatureSettings().getGroup().size(); gs++)
5320       {
5321         Group grp = jm.getFeatureSettings().getGroup().get(gs);
5322         fgtable.put(grp.getName(), new Boolean(grp.isDisplay()));
5323       }
5324       // FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
5325       // fgtable, featureColours, jms.getFeatureSettings().hasTransparency() ?
5326       // jms.getFeatureSettings().getTransparency() : 0.0, featureOrder);
5327       FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
5328               fgtable, featureColours, 1.0f, featureOrder);
5329       fr.transferSettings(frs);
5330     }
5331
5332     if (view.getHiddenColumns().size() > 0)
5333     {
5334       for (int c = 0; c < view.getHiddenColumns().size(); c++)
5335       {
5336         final HiddenColumns hc = view.getHiddenColumns().get(c);
5337         viewport.hideColumns(safeInt(hc.getStart()),
5338                 safeInt(hc.getEnd()) /* +1 */);
5339       }
5340     }
5341     if (view.getCalcIdParam() != null)
5342     {
5343       for (CalcIdParam calcIdParam : view.getCalcIdParam())
5344       {
5345         if (calcIdParam != null)
5346         {
5347           if (recoverCalcIdParam(calcIdParam, viewport))
5348           {
5349           }
5350           else
5351           {
5352             warn("Couldn't recover parameters for "
5353                     + calcIdParam.getCalcId());
5354           }
5355         }
5356       }
5357     }
5358     af.setMenusFromViewport(viewport);
5359     af.setTitle(view.getTitle());
5360     // TODO: we don't need to do this if the viewport is aready visible.
5361     /*
5362      * Add the AlignFrame to the desktop (it may be 'gathered' later), unless it
5363      * has a 'cdna/protein complement' view, in which case save it in order to
5364      * populate a SplitFrame once all views have been read in.
5365      */
5366     String complementaryViewId = view.getComplementId();
5367     if (complementaryViewId == null)
5368     {
5369       Dimension dim = Platform.getDimIfEmbedded(af,
5370               safeInt(view.getWidth()), safeInt(view.getHeight()));
5371       Desktop.addInternalFrame(af, view.getTitle(), dim.width, dim.height);
5372       // recompute any autoannotation
5373       af.alignPanel.updateAnnotation(false, true);
5374       reorderAutoannotation(af, al, autoAlan);
5375       af.alignPanel.alignmentChanged();
5376     }
5377     else
5378     {
5379       splitFrameCandidates.put(view, af);
5380     }
5381     return af;
5382   }
5383
5384   /**
5385    * Reads saved data to restore Colour by Annotation settings
5386    * 
5387    * @param viewAnnColour
5388    * @param af
5389    * @param al
5390    * @param model
5391    * @param checkGroupAnnColour
5392    * @return
5393    */
5394   private ColourSchemeI constructAnnotationColour(
5395           AnnotationColourScheme viewAnnColour, AlignFrame af,
5396           AlignmentI al, JalviewModel model, boolean checkGroupAnnColour)
5397   {
5398     boolean propagateAnnColour = false;
5399     AlignmentI annAlignment = af != null ? af.getViewport().getAlignment()
5400             : al;
5401     if (checkGroupAnnColour && al.getGroups() != null
5402             && al.getGroups().size() > 0)
5403     {
5404       // pre 2.8.1 behaviour
5405       // check to see if we should transfer annotation colours
5406       propagateAnnColour = true;
5407       for (SequenceGroup sg : al.getGroups())
5408       {
5409         if (sg.getColourScheme() instanceof AnnotationColourGradient)
5410         {
5411           propagateAnnColour = false;
5412         }
5413       }
5414     }
5415
5416     /*
5417      * 2.10.2- : saved annotationId is AlignmentAnnotation.annotationId
5418      */
5419     String annotationId = viewAnnColour.getAnnotation();
5420     AlignmentAnnotation matchedAnnotation = annotationIds.get(annotationId);
5421
5422     /*
5423      * pre 2.10.2: saved annotationId is AlignmentAnnotation.label
5424      */
5425     if (matchedAnnotation == null
5426             && annAlignment.getAlignmentAnnotation() != null)
5427     {
5428       for (int i = 0; i < annAlignment.getAlignmentAnnotation().length; i++)
5429       {
5430         if (annotationId
5431                 .equals(annAlignment.getAlignmentAnnotation()[i].label))
5432         {
5433           matchedAnnotation = annAlignment.getAlignmentAnnotation()[i];
5434           break;
5435         }
5436       }
5437     }
5438     if (matchedAnnotation == null)
5439     {
5440       System.err.println("Failed to match annotation colour scheme for "
5441               + annotationId);
5442       return null;
5443     }
5444     if (matchedAnnotation.getThreshold() == null)
5445     {
5446       matchedAnnotation.setThreshold(
5447               new GraphLine(safeFloat(viewAnnColour.getThreshold()),
5448                       "Threshold", Color.black));
5449     }
5450
5451     AnnotationColourGradient cs = null;
5452     if (viewAnnColour.getColourScheme().equals("None"))
5453     {
5454       cs = new AnnotationColourGradient(matchedAnnotation,
5455               new Color(safeInt(viewAnnColour.getMinColour())),
5456               new Color(safeInt(viewAnnColour.getMaxColour())),
5457               safeInt(viewAnnColour.getAboveThreshold()));
5458     }
5459     else if (viewAnnColour.getColourScheme().startsWith("ucs"))
5460     {
5461       cs = new AnnotationColourGradient(matchedAnnotation,
5462               getUserColourScheme(model, viewAnnColour.getColourScheme()),
5463               safeInt(viewAnnColour.getAboveThreshold()));
5464     }
5465     else
5466     {
5467       cs = new AnnotationColourGradient(matchedAnnotation,
5468               ColourSchemeProperty.getColourScheme(af.getViewport(), al,
5469                       viewAnnColour.getColourScheme()),
5470               safeInt(viewAnnColour.getAboveThreshold()));
5471     }
5472
5473     boolean perSequenceOnly = safeBoolean(viewAnnColour.isPerSequence());
5474     boolean useOriginalColours = safeBoolean(
5475             viewAnnColour.isPredefinedColours());
5476     cs.setSeqAssociated(perSequenceOnly);
5477     cs.setPredefinedColours(useOriginalColours);
5478
5479     if (propagateAnnColour && al.getGroups() != null)
5480     {
5481       // Also use these settings for all the groups
5482       for (int g = 0; g < al.getGroups().size(); g++)
5483       {
5484         SequenceGroup sg = al.getGroups().get(g);
5485         if (sg.getGroupColourScheme() == null)
5486         {
5487           continue;
5488         }
5489
5490         AnnotationColourGradient groupScheme = new AnnotationColourGradient(
5491                 matchedAnnotation, sg.getColourScheme(),
5492                 safeInt(viewAnnColour.getAboveThreshold()));
5493         sg.setColourScheme(groupScheme);
5494         groupScheme.setSeqAssociated(perSequenceOnly);
5495         groupScheme.setPredefinedColours(useOriginalColours);
5496       }
5497     }
5498     return cs;
5499   }
5500
5501   private void reorderAutoannotation(AlignFrame af, AlignmentI al,
5502           List<JvAnnotRow> autoAlan)
5503   {
5504     // copy over visualization settings for autocalculated annotation in the
5505     // view
5506     if (al.getAlignmentAnnotation() != null)
5507     {
5508       /**
5509        * Kludge for magic autoannotation names (see JAL-811)
5510        */
5511       String[] magicNames = new String[] { "Consensus", "Quality",
5512           "Conservation" };
5513       JvAnnotRow nullAnnot = new JvAnnotRow(-1, null);
5514       Hashtable<String, JvAnnotRow> visan = new Hashtable<>();
5515       for (String nm : magicNames)
5516       {
5517         visan.put(nm, nullAnnot);
5518       }
5519       for (JvAnnotRow auan : autoAlan)
5520       {
5521         visan.put(auan.template.label
5522                 + (auan.template.getCalcId() == null ? ""
5523                         : "\t" + auan.template.getCalcId()),
5524                 auan);
5525       }
5526       int hSize = al.getAlignmentAnnotation().length;
5527       List<JvAnnotRow> reorder = new ArrayList<>();
5528       // work through any autoCalculated annotation already on the view
5529       // removing it if it should be placed in a different location on the
5530       // annotation panel.
5531       List<String> remains = new ArrayList<>(visan.keySet());
5532       for (int h = 0; h < hSize; h++)
5533       {
5534         jalview.datamodel.AlignmentAnnotation jalan = al
5535                 .getAlignmentAnnotation()[h];
5536         if (jalan.autoCalculated)
5537         {
5538           String k;
5539           JvAnnotRow valan = visan.get(k = jalan.label);
5540           if (jalan.getCalcId() != null)
5541           {
5542             valan = visan.get(k = jalan.label + "\t" + jalan.getCalcId());
5543           }
5544
5545           if (valan != null)
5546           {
5547             // delete the auto calculated row from the alignment
5548             al.deleteAnnotation(jalan, false);
5549             remains.remove(k);
5550             hSize--;
5551             h--;
5552             if (valan != nullAnnot)
5553             {
5554               if (jalan != valan.template)
5555               {
5556                 // newly created autoannotation row instance
5557                 // so keep a reference to the visible annotation row
5558                 // and copy over all relevant attributes
5559                 if (valan.template.graphHeight >= 0)
5560
5561                 {
5562                   jalan.graphHeight = valan.template.graphHeight;
5563                 }
5564                 jalan.visible = valan.template.visible;
5565               }
5566               reorder.add(new JvAnnotRow(valan.order, jalan));
5567             }
5568           }
5569         }
5570       }
5571       // Add any (possibly stale) autocalculated rows that were not appended to
5572       // the view during construction
5573       for (String other : remains)
5574       {
5575         JvAnnotRow othera = visan.get(other);
5576         if (othera != nullAnnot && othera.template.getCalcId() != null
5577                 && othera.template.getCalcId().length() > 0)
5578         {
5579           reorder.add(othera);
5580         }
5581       }
5582       // now put the automatic annotation in its correct place
5583       int s = 0, srt[] = new int[reorder.size()];
5584       JvAnnotRow[] rws = new JvAnnotRow[reorder.size()];
5585       for (JvAnnotRow jvar : reorder)
5586       {
5587         rws[s] = jvar;
5588         srt[s++] = jvar.order;
5589       }
5590       reorder.clear();
5591       jalview.util.QuickSort.sort(srt, rws);
5592       // and re-insert the annotation at its correct position
5593       for (JvAnnotRow jvar : rws)
5594       {
5595         al.addAnnotation(jvar.template, jvar.order);
5596       }
5597       af.alignPanel.adjustAnnotationHeight();
5598     }
5599   }
5600
5601   Hashtable skipList = null;
5602
5603   /**
5604    * TODO remove this method
5605    * 
5606    * @param view
5607    * @return AlignFrame bound to sequenceSetId from view, if one exists. private
5608    *         AlignFrame getSkippedFrame(Viewport view) { if (skipList==null) {
5609    *         throw new Error("Implementation Error. No skipList defined for this
5610    *         Jalview2XML instance."); } return (AlignFrame)
5611    *         skipList.get(view.getSequenceSetId()); }
5612    */
5613
5614   /**
5615    * Check if the Jalview view contained in object should be skipped or not.
5616    * 
5617    * @param object
5618    * @return true if view's sequenceSetId is a key in skipList
5619    */
5620   private boolean skipViewport(JalviewModel object)
5621   {
5622     if (skipList == null)
5623     {
5624       return false;
5625     }
5626     String id = object.getViewport().get(0).getSequenceSetId();
5627     if (skipList.containsKey(id))
5628     {
5629       if (Cache.log != null && Cache.log.isDebugEnabled())
5630       {
5631         Cache.log.debug("Skipping seuqence set id " + id);
5632       }
5633       return true;
5634     }
5635     return false;
5636   }
5637
5638   protected void addToSkipList(AlignFrame af)
5639   {
5640     if (skipList == null)
5641     {
5642       skipList = new Hashtable();
5643     }
5644     skipList.put(af.getViewport().getSequenceSetId(), af);
5645   }
5646
5647   protected void clearSkipList()
5648   {
5649     if (skipList != null)
5650     {
5651       skipList.clear();
5652       skipList = null;
5653     }
5654   }
5655
5656   private void recoverDatasetFor(SequenceSet vamsasSet, AlignmentI al,
5657           boolean ignoreUnrefed, String uniqueSeqSetId)
5658   {
5659     jalview.datamodel.AlignmentI ds = getDatasetFor(
5660             vamsasSet.getDatasetId());
5661     AlignmentI xtant_ds = ds;
5662     if (xtant_ds == null)
5663     {
5664       // good chance we are about to create a new dataset, but check if we've
5665       // seen some of the dataset sequence IDs before.
5666       // TODO: skip this check if we are working with project generated by
5667       // version 2.11 or later
5668       xtant_ds = checkIfHasDataset(vamsasSet.getSequence());
5669       if (xtant_ds != null)
5670       {
5671         ds = xtant_ds;
5672         addDatasetRef(vamsasSet.getDatasetId(), ds);
5673       }
5674     }
5675     Vector dseqs = null;
5676     if (!ignoreUnrefed)
5677     {
5678       // recovering an alignment View
5679       AlignmentI seqSetDS = getDatasetFor(UNIQSEQSETID + uniqueSeqSetId);
5680       if (seqSetDS != null)
5681       {
5682         if (ds != null && ds != seqSetDS)
5683         {
5684           warn("JAL-3171 regression: Overwriting a dataset reference for an alignment"
5685                   + " - CDS/Protein crossreference data may be lost");
5686           if (xtant_ds != null)
5687           {
5688             // This can only happen if the unique sequence set ID was bound to a
5689             // dataset that did not contain any of the sequences in the view
5690             // currently being restored.
5691             warn("JAL-3171 SERIOUS!  TOTAL CONFUSION - please consider contacting the Jalview Development team so they can investigate why your project caused this message to be displayed.");
5692           }
5693         }
5694         ds = seqSetDS;
5695         addDatasetRef(vamsasSet.getDatasetId(), ds);
5696       }
5697     }
5698     if (ds == null)
5699     {
5700       // try even harder to restore dataset
5701       AlignmentI xtantDS = checkIfHasDataset(vamsasSet.getSequence());
5702       // create a list of new dataset sequences
5703       dseqs = new Vector();
5704     }
5705     for (int i = 0, iSize = vamsasSet.getSequence().size(); i < iSize; i++)
5706     {
5707       Sequence vamsasSeq = vamsasSet.getSequence().get(i);
5708       ensureJalviewDatasetSequence(vamsasSeq, ds, dseqs, ignoreUnrefed, i);
5709     }
5710     // create a new dataset
5711     if (ds == null)
5712     {
5713       SequenceI[] dsseqs = new SequenceI[dseqs.size()];
5714       dseqs.copyInto(dsseqs);
5715       ds = new jalview.datamodel.Alignment(dsseqs);
5716 //      debug("Jalview2XML Created new dataset " + vamsasSet.getDatasetId()
5717 //              + " for alignment " + System.identityHashCode(al));
5718       addDatasetRef(vamsasSet.getDatasetId(), ds);
5719     }
5720     // set the dataset for the newly imported alignment.
5721     if (al.getDataset() == null && !ignoreUnrefed)
5722     {
5723       al.setDataset(ds);
5724       // register dataset for the alignment's uniqueSeqSetId for legacy projects
5725       addDatasetRef(UNIQSEQSETID + uniqueSeqSetId, ds);
5726     }
5727     updateSeqDatasetBinding(vamsasSet.getSequence(), ds);
5728   }
5729
5730   /**
5731    * XML dataset sequence ID to materialised dataset reference
5732    */
5733   HashMap<String, AlignmentI> seqToDataset = new HashMap<>();
5734
5735   /**
5736    * @return the first materialised dataset reference containing a dataset
5737    *         sequence referenced in the given view
5738    * @param list
5739    *          - sequences from the view
5740    */
5741   AlignmentI checkIfHasDataset(List<Sequence> list)
5742   {
5743     for (Sequence restoredSeq : list)
5744     {
5745       AlignmentI datasetFor = seqToDataset.get(restoredSeq.getDsseqid());
5746       if (datasetFor != null)
5747       {
5748         return datasetFor;
5749       }
5750     }
5751     return null;
5752   }
5753
5754   /**
5755    * Register ds as the containing dataset for the dataset sequences referenced
5756    * by sequences in list
5757    * 
5758    * @param list
5759    *          - sequences in a view
5760    * @param ds
5761    */
5762   void updateSeqDatasetBinding(List<Sequence> list, AlignmentI ds)
5763   {
5764     for (Sequence restoredSeq : list)
5765     {
5766       AlignmentI prevDS = seqToDataset.put(restoredSeq.getDsseqid(), ds);
5767       if (prevDS != null && prevDS != ds)
5768       {
5769         warn("Dataset sequence appears in many datasets: "
5770                 + restoredSeq.getDsseqid());
5771         // TODO: try to merge!
5772       }
5773     }
5774   }
5775   /**
5776    * 
5777    * @param vamsasSeq
5778    *          sequence definition to create/merge dataset sequence for
5779    * @param ds
5780    *          dataset alignment
5781    * @param dseqs
5782    *          vector to add new dataset sequence to
5783    * @param ignoreUnrefed
5784    *          - when true, don't create new sequences from vamsasSeq if it's id
5785    *          doesn't already have an asssociated Jalview sequence.
5786    * @param vseqpos
5787    *          - used to reorder the sequence in the alignment according to the
5788    *          vamsasSeq array ordering, to preserve ordering of dataset
5789    */
5790   private void ensureJalviewDatasetSequence(Sequence vamsasSeq,
5791           AlignmentI ds, Vector dseqs, boolean ignoreUnrefed, int vseqpos)
5792   {
5793     // JBP TODO: Check this is called for AlCodonFrames to support recovery of
5794     // xRef Codon Maps
5795     SequenceI sq = seqRefIds.get(vamsasSeq.getId());
5796     boolean reorder = false;
5797     SequenceI dsq = null;
5798     if (sq != null && sq.getDatasetSequence() != null)
5799     {
5800       dsq = sq.getDatasetSequence();
5801     }
5802     else
5803     {
5804       reorder = true;
5805     }
5806     if (sq == null && ignoreUnrefed)
5807     {
5808       return;
5809     }
5810     String sqid = vamsasSeq.getDsseqid();
5811     if (dsq == null)
5812     {
5813       // need to create or add a new dataset sequence reference to this sequence
5814       if (sqid != null)
5815       {
5816         dsq = seqRefIds.get(sqid);
5817       }
5818       // check again
5819       if (dsq == null)
5820       {
5821         // make a new dataset sequence
5822         dsq = sq.createDatasetSequence();
5823         if (sqid == null)
5824         {
5825           // make up a new dataset reference for this sequence
5826           sqid = seqHash(dsq);
5827         }
5828         dsq.setVamsasId(uniqueSetSuffix + sqid);
5829         seqRefIds.put(sqid, dsq);
5830         if (ds == null)
5831         {
5832           if (dseqs != null)
5833           {
5834             dseqs.addElement(dsq);
5835           }
5836         }
5837         else
5838         {
5839           ds.addSequence(dsq);
5840         }
5841       }
5842       else
5843       {
5844         if (sq != dsq)
5845         { // make this dataset sequence sq's dataset sequence
5846           sq.setDatasetSequence(dsq);
5847           // and update the current dataset alignment
5848           if (ds == null)
5849           {
5850             if (dseqs != null)
5851             {
5852               if (!dseqs.contains(dsq))
5853               {
5854                 dseqs.add(dsq);
5855               }
5856             }
5857             else
5858             {
5859               if (ds.findIndex(dsq) < 0)
5860               {
5861                 ds.addSequence(dsq);
5862               }
5863             }
5864           }
5865         }
5866       }
5867     }
5868     // TODO: refactor this as a merge dataset sequence function
5869     // now check that sq (the dataset sequence) sequence really is the union of
5870     // all references to it
5871     // boolean pre = sq.getStart() < dsq.getStart();
5872     // boolean post = sq.getEnd() > dsq.getEnd();
5873     // if (pre || post)
5874     if (sq != dsq)
5875     {
5876       // StringBuffer sb = new StringBuffer();
5877       String newres = jalview.analysis.AlignSeq.extractGaps(
5878               jalview.util.Comparison.GapChars, sq.getSequenceAsString());
5879       if (!newres.equalsIgnoreCase(dsq.getSequenceAsString())
5880               && newres.length() > dsq.getLength())
5881       {
5882         // Update with the longer sequence.
5883         synchronized (dsq)
5884         {
5885           /*
5886            * if (pre) { sb.insert(0, newres .substring(0, dsq.getStart() -
5887            * sq.getStart())); dsq.setStart(sq.getStart()); } if (post) {
5888            * sb.append(newres.substring(newres.length() - sq.getEnd() -
5889            * dsq.getEnd())); dsq.setEnd(sq.getEnd()); }
5890            */
5891           dsq.setSequence(newres);
5892         }
5893         // TODO: merges will never happen if we 'know' we have the real dataset
5894         // sequence - this should be detected when id==dssid
5895         System.err.println(
5896                 "DEBUG Notice:  Merged dataset sequence (if you see this often, post at http://issues.jalview.org/browse/JAL-1474)"); // ("
5897         // + (pre ? "prepended" : "") + " "
5898         // + (post ? "appended" : ""));
5899       }
5900     }
5901     else
5902     {
5903       // sequence refs are identical. We may need to update the existing dataset
5904       // alignment with this one, though.
5905       if (ds != null && dseqs == null)
5906       {
5907         int opos = ds.findIndex(dsq);
5908         SequenceI tseq = null;
5909         if (opos != -1 && vseqpos != opos)
5910         {
5911           // remove from old position
5912           ds.deleteSequence(dsq);
5913         }
5914         if (vseqpos < ds.getHeight())
5915         {
5916           if (vseqpos != opos)
5917           {
5918             // save sequence at destination position
5919             tseq = ds.getSequenceAt(vseqpos);
5920             ds.replaceSequenceAt(vseqpos, dsq);
5921             ds.addSequence(tseq);
5922           }
5923         }
5924         else
5925         {
5926           ds.addSequence(dsq);
5927         }
5928       }
5929     }
5930   }
5931
5932   /*
5933    * TODO use AlignmentI here and in related methods - needs
5934    * AlignmentI.getDataset() changed to return AlignmentI instead of Alignment
5935    */
5936   Hashtable<String, AlignmentI> datasetIds = null;
5937
5938   IdentityHashMap<AlignmentI, String> dataset2Ids = null;
5939
5940   private AlignmentI getDatasetFor(String datasetId)
5941   {
5942     if (datasetIds == null)
5943     {
5944       datasetIds = new Hashtable<>();
5945       return null;
5946     }
5947     if (datasetIds.containsKey(datasetId))
5948     {
5949       return datasetIds.get(datasetId);
5950     }
5951     return null;
5952   }
5953
5954   private void addDatasetRef(String datasetId, AlignmentI dataset)
5955   {
5956     if (datasetIds == null)
5957     {
5958       datasetIds = new Hashtable<>();
5959     }
5960     datasetIds.put(datasetId, dataset);
5961   }
5962
5963   /**
5964    * make a new dataset ID for this jalview dataset alignment
5965    * 
5966    * @param dataset
5967    * @return
5968    */
5969   private String getDatasetIdRef(AlignmentI dataset)
5970   {
5971     if (dataset.getDataset() != null)
5972     {
5973       warn("Serious issue!  Dataset Object passed to getDatasetIdRef is not a Jalview DATASET alignment...");
5974     }
5975     String datasetId = makeHashCode(dataset, null);
5976     if (datasetId == null)
5977     {
5978       // make a new datasetId and record it
5979       if (dataset2Ids == null)
5980       {
5981         dataset2Ids = new IdentityHashMap<>();
5982       }
5983       else
5984       {
5985         datasetId = dataset2Ids.get(dataset);
5986       }
5987       if (datasetId == null)
5988       {
5989         datasetId = "ds" + dataset2Ids.size() + 1;
5990         dataset2Ids.put(dataset, datasetId);
5991       }
5992     }
5993     return datasetId;
5994   }
5995
5996   private void addDBRefs(SequenceI datasetSequence, Sequence sequence)
5997   {
5998     for (int d = 0; d < sequence.getDBRef().size(); d++)
5999     {
6000       DBRef dr = sequence.getDBRef().get(d);
6001       jalview.datamodel.DBRefEntry entry = new jalview.datamodel.DBRefEntry(
6002               dr.getSource(), dr.getVersion(), dr.getAccessionId());
6003       if (dr.getMapping() != null)
6004       {
6005         entry.setMap(addMapping(dr.getMapping()));
6006       }
6007       datasetSequence.addDBRef(entry);
6008     }
6009   }
6010
6011   private jalview.datamodel.Mapping addMapping(Mapping m)
6012   {
6013     SequenceI dsto = null;
6014     // Mapping m = dr.getMapping();
6015     int fr[] = new int[m.getMapListFrom().size() * 2];
6016     Iterator<MapListFrom> from = m.getMapListFrom().iterator();// enumerateMapListFrom();
6017     for (int _i = 0; from.hasNext(); _i += 2)
6018     {
6019       MapListFrom mf = from.next();
6020       fr[_i] = mf.getStart();
6021       fr[_i + 1] = mf.getEnd();
6022     }
6023     int fto[] = new int[m.getMapListTo().size() * 2];
6024     Iterator<MapListTo> to = m.getMapListTo().iterator();// enumerateMapListTo();
6025     for (int _i = 0; to.hasNext(); _i += 2)
6026     {
6027       MapListTo mf = to.next();
6028       fto[_i] = mf.getStart();
6029       fto[_i + 1] = mf.getEnd();
6030     }
6031     jalview.datamodel.Mapping jmap = new jalview.datamodel.Mapping(dsto, fr,
6032             fto, m.getMapFromUnit().intValue(),
6033             m.getMapToUnit().intValue());
6034
6035     /*
6036      * (optional) choice of dseqFor or Sequence
6037      */
6038     if (m.getDseqFor() != null)
6039     {
6040       String dsfor = m.getDseqFor();
6041       if (seqRefIds.containsKey(dsfor))
6042       {
6043         /*
6044          * recover from hash
6045          */
6046         jmap.setTo(seqRefIds.get(dsfor));
6047       }
6048       else
6049       {
6050         frefedSequence.add(newMappingRef(dsfor, jmap));
6051       }
6052     }
6053     else if (m.getSequence() != null)
6054     {
6055       /*
6056        * local sequence definition
6057        */
6058       Sequence ms = m.getSequence();
6059       SequenceI djs = null;
6060       String sqid = ms.getDsseqid();
6061       if (sqid != null && sqid.length() > 0)
6062       {
6063         /*
6064          * recover dataset sequence
6065          */
6066         djs = seqRefIds.get(sqid);
6067       }
6068       else
6069       {
6070         System.err.println(
6071                 "Warning - making up dataset sequence id for DbRef sequence map reference");
6072         sqid = ((Object) ms).toString(); // make up a new hascode for
6073         // undefined dataset sequence hash
6074         // (unlikely to happen)
6075       }
6076
6077       if (djs == null)
6078       {
6079         /**
6080          * make a new dataset sequence and add it to refIds hash
6081          */
6082         djs = new jalview.datamodel.Sequence(ms.getName(),
6083                 ms.getSequence());
6084         djs.setStart(jmap.getMap().getToLowest());
6085         djs.setEnd(jmap.getMap().getToHighest());
6086         djs.setVamsasId(uniqueSetSuffix + sqid);
6087         jmap.setTo(djs);
6088         incompleteSeqs.put(sqid, djs);
6089         seqRefIds.put(sqid, djs);
6090
6091       }
6092       jalview.bin.Cache.log.debug("about to recurse on addDBRefs.");
6093       addDBRefs(djs, ms);
6094
6095     }
6096
6097     return jmap;
6098   }
6099
6100   /**
6101    * Provides a 'copy' of an alignment view (on action New View) by 'saving' the
6102    * view as XML (but not to file), and then reloading it
6103    * 
6104    * @param ap
6105    * @return
6106    */
6107   public AlignmentPanel copyAlignPanel(AlignmentPanel ap)
6108   {
6109     initSeqRefs();
6110     JalviewModel jm = saveState(ap, null, null, null);
6111
6112     addDatasetRef(
6113             jm.getVamsasModel().getSequenceSet().get(0).getDatasetId(),
6114             ap.getAlignment().getDataset());
6115
6116     uniqueSetSuffix = "";
6117     // jm.getJalviewModelSequence().getViewport(0).setId(null);
6118     jm.getViewport().get(0).setId(null);
6119     // we don't overwrite the view we just copied
6120
6121     if (this.frefedSequence == null)
6122     {
6123       frefedSequence = new Vector<>();
6124     }
6125
6126     viewportsAdded.clear();
6127
6128     AlignFrame af = loadFromObject(jm, null, false, null);
6129     af.getAlignPanels().clear();
6130     af.closeMenuItem_actionPerformed(true);
6131     af.alignPanel.setHoldRepaint(false);
6132     this.jarFile = null;
6133
6134     return af.alignPanel;
6135   }
6136
6137   private Hashtable jvids2vobj;
6138
6139   private void warn(String msg)
6140   {
6141     warn(msg, null);
6142   }
6143
6144   private void warn(String msg, Exception e)
6145   {
6146     if (Cache.log != null)
6147     {
6148       if (e != null)
6149       {
6150         Cache.log.warn(msg, e);
6151       }
6152       else
6153       {
6154         Cache.log.warn(msg);
6155       }
6156     }
6157     else
6158     {
6159       System.err.println("Warning: " + msg);
6160       if (e != null)
6161       {
6162         e.printStackTrace();
6163       }
6164     }
6165   }
6166
6167   private void debug(String string)
6168   {
6169     debug(string, null);
6170   }
6171
6172   private void debug(String msg, Exception e)
6173   {
6174     if (Cache.log != null)
6175     {
6176       if (e != null)
6177       {
6178         Cache.log.debug(msg, e);
6179       }
6180       else
6181       {
6182         Cache.log.debug(msg);
6183       }
6184     }
6185     else
6186     {
6187       System.err.println("Warning: " + msg);
6188       if (e != null)
6189       {
6190         e.printStackTrace();
6191       }
6192     }
6193   }
6194
6195   /**
6196    * set the object to ID mapping tables used to write/recover objects and XML
6197    * ID strings for the jalview project. If external tables are provided then
6198    * finalize and clearSeqRefs will not clear the tables when the Jalview2XML
6199    * object goes out of scope. - also populates the datasetIds hashtable with
6200    * alignment objects containing dataset sequences
6201    * 
6202    * @param vobj2jv
6203    *          Map from ID strings to jalview datamodel
6204    * @param jv2vobj
6205    *          Map from jalview datamodel to ID strings
6206    * 
6207    * 
6208    */
6209   public void setObjectMappingTables(Hashtable vobj2jv,
6210           IdentityHashMap jv2vobj)
6211   {
6212     this.jv2vobj = jv2vobj;
6213     this.vobj2jv = vobj2jv;
6214     Iterator ds = jv2vobj.keySet().iterator();
6215     String id;
6216     while (ds.hasNext())
6217     {
6218       Object jvobj = ds.next();
6219       id = jv2vobj.get(jvobj).toString();
6220       if (jvobj instanceof jalview.datamodel.Alignment)
6221       {
6222         if (((jalview.datamodel.Alignment) jvobj).getDataset() == null)
6223         {
6224           addDatasetRef(id, (jalview.datamodel.Alignment) jvobj);
6225         }
6226       }
6227       else if (jvobj instanceof jalview.datamodel.Sequence)
6228       {
6229         // register sequence object so the XML parser can recover it.
6230         if (seqRefIds == null)
6231         {
6232           seqRefIds = new HashMap<>();
6233         }
6234         if (seqsToIds == null)
6235         {
6236           seqsToIds = new IdentityHashMap<>();
6237         }
6238         seqRefIds.put(jv2vobj.get(jvobj).toString(), (SequenceI) jvobj);
6239         seqsToIds.put((SequenceI) jvobj, id);
6240       }
6241       else if (jvobj instanceof jalview.datamodel.AlignmentAnnotation)
6242       {
6243         String anid;
6244         AlignmentAnnotation jvann = (AlignmentAnnotation) jvobj;
6245         annotationIds.put(anid = jv2vobj.get(jvobj).toString(), jvann);
6246         if (jvann.annotationId == null)
6247         {
6248           jvann.annotationId = anid;
6249         }
6250         if (!jvann.annotationId.equals(anid))
6251         {
6252           // TODO verify that this is the correct behaviour
6253           this.warn("Overriding Annotation ID for " + anid
6254                   + " from different id : " + jvann.annotationId);
6255           jvann.annotationId = anid;
6256         }
6257       }
6258       else if (jvobj instanceof String)
6259       {
6260         if (jvids2vobj == null)
6261         {
6262           jvids2vobj = new Hashtable();
6263           jvids2vobj.put(jvobj, jv2vobj.get(jvobj).toString());
6264         }
6265       }
6266       else
6267       {
6268         Cache.log.debug("Ignoring " + jvobj.getClass() + " (ID = " + id);
6269       }
6270     }
6271   }
6272
6273   /**
6274    * set the uniqueSetSuffix used to prefix/suffix object IDs for jalview
6275    * objects created from the project archive. If string is null (default for
6276    * construction) then suffix will be set automatically.
6277    * 
6278    * @param string
6279    */
6280   public void setUniqueSetSuffix(String string)
6281   {
6282     uniqueSetSuffix = string;
6283
6284   }
6285
6286   /**
6287    * uses skipList2 as the skipList for skipping views on sequence sets
6288    * associated with keys in the skipList
6289    * 
6290    * @param skipList2
6291    */
6292   public void setSkipList(Hashtable skipList2)
6293   {
6294     skipList = skipList2;
6295   }
6296
6297   /**
6298    * Reads the jar entry of given name and returns its contents, or null if the
6299    * entry is not found.
6300    * 
6301    * @param jprovider
6302    * @param jarEntryName
6303    * @return
6304    */
6305   protected String readJarEntry(jarInputStreamProvider jprovider,
6306           String jarEntryName)
6307   {
6308     String result = null;
6309     BufferedReader in = null;
6310
6311     try
6312     {
6313       /*
6314        * Reopen the jar input stream and traverse its entries to find a matching
6315        * name
6316        */
6317       JarInputStream jin = jprovider.getJarInputStream();
6318       JarEntry entry = null;
6319       do
6320       {
6321         entry = jin.getNextJarEntry();
6322       } while (entry != null && !entry.getName().equals(jarEntryName));
6323
6324       if (entry != null)
6325       {
6326         StringBuilder out = new StringBuilder(256);
6327         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
6328         String data;
6329
6330         while ((data = in.readLine()) != null)
6331         {
6332           out.append(data);
6333         }
6334         result = out.toString();
6335       }
6336       else
6337       {
6338         warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
6339       }
6340     } catch (Exception ex)
6341     {
6342       ex.printStackTrace();
6343     } finally
6344     {
6345       if (in != null)
6346       {
6347         try
6348         {
6349           in.close();
6350         } catch (IOException e)
6351         {
6352           // ignore
6353         }
6354       }
6355     }
6356
6357     return result;
6358   }
6359
6360   /**
6361    * Returns an incrementing counter (0, 1, 2...)
6362    * 
6363    * @return
6364    */
6365   private synchronized int nextCounter()
6366   {
6367     return counter++;
6368   }
6369
6370   /**
6371    * Loads any saved PCA viewers
6372    * 
6373    * @param jms
6374    * @param ap
6375    */
6376   protected void loadPCAViewers(JalviewModel model, AlignmentPanel ap)
6377   {
6378     try
6379     {
6380       List<PcaViewer> pcaviewers = model.getPcaViewer();
6381       for (PcaViewer viewer : pcaviewers)
6382       {
6383         String modelName = viewer.getScoreModelName();
6384         SimilarityParamsI params = new SimilarityParams(
6385                 viewer.isIncludeGappedColumns(), viewer.isMatchGaps(),
6386                 viewer.isIncludeGaps(),
6387                 viewer.isDenominateByShortestLength());
6388
6389         /*
6390          * create the panel (without computing the PCA)
6391          */
6392         PCAPanel panel = new PCAPanel(ap, modelName, params);
6393
6394         panel.setTitle(viewer.getTitle());
6395         panel.setBounds(new Rectangle(viewer.getXpos(), viewer.getYpos(),
6396                 viewer.getWidth(), viewer.getHeight()));
6397
6398         boolean showLabels = viewer.isShowLabels();
6399         panel.setShowLabels(showLabels);
6400         panel.getRotatableCanvas().setShowLabels(showLabels);
6401         panel.getRotatableCanvas()
6402                 .setBgColour(new Color(viewer.getBgColour()));
6403         panel.getRotatableCanvas()
6404                 .setApplyToAllViews(viewer.isLinkToAllViews());
6405
6406         /*
6407          * load PCA output data
6408          */
6409         ScoreModelI scoreModel = ScoreModels.getInstance()
6410                 .getScoreModel(modelName, ap);
6411         PCA pca = new PCA(null, scoreModel, params);
6412         PcaDataType pcaData = viewer.getPcaData();
6413
6414         MatrixI pairwise = loadDoubleMatrix(pcaData.getPairwiseMatrix());
6415         pca.setPairwiseScores(pairwise);
6416
6417         MatrixI triDiag = loadDoubleMatrix(pcaData.getTridiagonalMatrix());
6418         pca.setTridiagonal(triDiag);
6419
6420         MatrixI result = loadDoubleMatrix(pcaData.getEigenMatrix());
6421         pca.setEigenmatrix(result);
6422
6423         panel.getPcaModel().setPCA(pca);
6424
6425         /*
6426          * we haven't saved the input data! (JAL-2647 to do)
6427          */
6428         panel.setInputData(null);
6429
6430         /*
6431          * add the sequence points for the PCA display
6432          */
6433         List<jalview.datamodel.SequencePoint> seqPoints = new ArrayList<>();
6434         for (SequencePoint sp : viewer.getSequencePoint())
6435         {
6436           String seqId = sp.getSequenceRef();
6437           SequenceI seq = seqRefIds.get(seqId);
6438           if (seq == null)
6439           {
6440             throw new IllegalStateException(
6441                     "Unmatched seqref for PCA: " + seqId);
6442           }
6443           Point pt = new Point(sp.getXPos(), sp.getYPos(), sp.getZPos());
6444           jalview.datamodel.SequencePoint seqPoint = new jalview.datamodel.SequencePoint(
6445                   seq, pt);
6446           seqPoints.add(seqPoint);
6447         }
6448         panel.getRotatableCanvas().setPoints(seqPoints, seqPoints.size());
6449
6450         /*
6451          * set min-max ranges and scale after setPoints (which recomputes them)
6452          */
6453         panel.getRotatableCanvas().setScaleFactor(viewer.getScaleFactor());
6454         SeqPointMin spMin = viewer.getSeqPointMin();
6455         float[] min = new float[] { spMin.getXPos(), spMin.getYPos(),
6456             spMin.getZPos() };
6457         SeqPointMax spMax = viewer.getSeqPointMax();
6458         float[] max = new float[] { spMax.getXPos(), spMax.getYPos(),
6459             spMax.getZPos() };
6460         panel.getRotatableCanvas().setSeqMinMax(min, max);
6461
6462         // todo: hold points list in PCAModel only
6463         panel.getPcaModel().setSequencePoints(seqPoints);
6464
6465         panel.setSelectedDimensionIndex(viewer.getXDim(), X);
6466         panel.setSelectedDimensionIndex(viewer.getYDim(), Y);
6467         panel.setSelectedDimensionIndex(viewer.getZDim(), Z);
6468
6469         // is this duplication needed?
6470         panel.setTop(seqPoints.size() - 1);
6471         panel.getPcaModel().setTop(seqPoints.size() - 1);
6472
6473         /*
6474          * add the axes' end points for the display
6475          */
6476         for (int i = 0; i < 3; i++)
6477         {
6478           Axis axis = viewer.getAxis().get(i);
6479           panel.getRotatableCanvas().getAxisEndPoints()[i] = new Point(
6480                   axis.getXPos(), axis.getYPos(), axis.getZPos());
6481         }
6482
6483         Dimension dim = Platform.getDimIfEmbedded(panel, 475, 450);
6484         Desktop.addInternalFrame(panel, MessageManager.formatMessage(
6485                 "label.calc_title", "PCA", modelName), dim.width,
6486                 dim.height);
6487       }
6488     } catch (Exception ex)
6489     {
6490       Cache.log.error("Error loading PCA: " + ex.toString());
6491     }
6492   }
6493
6494   /**
6495    * Populates an XML model of the feature colour scheme for one feature type
6496    * 
6497    * @param featureType
6498    * @param fcol
6499    * @return
6500    */
6501   public static Colour marshalColour(
6502           String featureType, FeatureColourI fcol)
6503   {
6504     Colour col = new Colour();
6505     if (fcol.isSimpleColour())
6506     {
6507       col.setRGB(Format.getHexString(fcol.getColour()));
6508     }
6509     else
6510     {
6511       col.setRGB(Format.getHexString(fcol.getMaxColour()));
6512       col.setMin(fcol.getMin());
6513       col.setMax(fcol.getMax());
6514       col.setMinRGB(jalview.util.Format.getHexString(fcol.getMinColour()));
6515       col.setAutoScale(fcol.isAutoScaled());
6516       col.setThreshold(fcol.getThreshold());
6517       col.setColourByLabel(fcol.isColourByLabel());
6518       col.setThreshType(fcol.isAboveThreshold() ? ThresholdType.ABOVE
6519               : (fcol.isBelowThreshold() ? ThresholdType.BELOW
6520                       : ThresholdType.NONE));
6521       if (fcol.isColourByAttribute())
6522       {
6523         final String[] attName = fcol.getAttributeName();
6524         col.getAttributeName().add(attName[0]);
6525         if (attName.length > 1)
6526         {
6527           col.getAttributeName().add(attName[1]);
6528         }
6529       }
6530       Color noColour = fcol.getNoColour();
6531       if (noColour == null)
6532       {
6533         col.setNoValueColour(NoValueColour.NONE);
6534       }
6535       else if (noColour == fcol.getMaxColour())
6536       {
6537         col.setNoValueColour(NoValueColour.MAX);
6538       }
6539       else
6540       {
6541         col.setNoValueColour(NoValueColour.MIN);
6542       }
6543     }
6544     col.setName(featureType);
6545     return col;
6546   }
6547
6548   /**
6549    * Populates an XML model of the feature filter(s) for one feature type
6550    * 
6551    * @param firstMatcher
6552    *          the first (or only) match condition)
6553    * @param filter
6554    *          remaining match conditions (if any)
6555    * @param and
6556    *          if true, conditions are and-ed, else or-ed
6557    */
6558   public static jalview.xml.binding.jalview.FeatureMatcherSet marshalFilter(
6559           FeatureMatcherI firstMatcher, Iterator<FeatureMatcherI> filters,
6560           boolean and)
6561   {
6562     jalview.xml.binding.jalview.FeatureMatcherSet result = new jalview.xml.binding.jalview.FeatureMatcherSet();
6563   
6564     if (filters.hasNext())
6565     {
6566       /*
6567        * compound matcher
6568        */
6569       CompoundMatcher compound = new CompoundMatcher();
6570       compound.setAnd(and);
6571       jalview.xml.binding.jalview.FeatureMatcherSet matcher1 = marshalFilter(
6572               firstMatcher, Collections.emptyIterator(), and);
6573       // compound.addMatcherSet(matcher1);
6574       compound.getMatcherSet().add(matcher1);
6575       FeatureMatcherI nextMatcher = filters.next();
6576       jalview.xml.binding.jalview.FeatureMatcherSet matcher2 = marshalFilter(
6577               nextMatcher, filters, and);
6578       // compound.addMatcherSet(matcher2);
6579       compound.getMatcherSet().add(matcher2);
6580       result.setCompoundMatcher(compound);
6581     }
6582     else
6583     {
6584       /*
6585        * single condition matcher
6586        */
6587       // MatchCondition matcherModel = new MatchCondition();
6588       jalview.xml.binding.jalview.FeatureMatcher matcherModel = new jalview.xml.binding.jalview.FeatureMatcher();
6589       matcherModel.setCondition(
6590               firstMatcher.getMatcher().getCondition().getStableName());
6591       matcherModel.setValue(firstMatcher.getMatcher().getPattern());
6592       if (firstMatcher.isByAttribute())
6593       {
6594         matcherModel.setBy(FilterBy.BY_ATTRIBUTE);
6595         // matcherModel.setAttributeName(firstMatcher.getAttribute());
6596         String[] attName = firstMatcher.getAttribute();
6597         matcherModel.getAttributeName().add(attName[0]); // attribute
6598         if (attName.length > 1)
6599         {
6600           matcherModel.getAttributeName().add(attName[1]); // sub-attribute
6601         }
6602       }
6603       else if (firstMatcher.isByLabel())
6604       {
6605         matcherModel.setBy(FilterBy.BY_LABEL);
6606       }
6607       else if (firstMatcher.isByScore())
6608       {
6609         matcherModel.setBy(FilterBy.BY_SCORE);
6610       }
6611       result.setMatchCondition(matcherModel);
6612     }
6613   
6614     return result;
6615   }
6616
6617   /**
6618    * Loads one XML model of a feature filter to a Jalview object
6619    * 
6620    * @param featureType
6621    * @param matcherSetModel
6622    * @return
6623    */
6624   public static FeatureMatcherSetI parseFilter(
6625           String featureType,
6626           jalview.xml.binding.jalview.FeatureMatcherSet matcherSetModel)
6627   {
6628     FeatureMatcherSetI result = new FeatureMatcherSet();
6629     try
6630     {
6631       parseFilterConditions(result, matcherSetModel, true);
6632     } catch (IllegalStateException e)
6633     {
6634       // mixing AND and OR conditions perhaps
6635       System.err.println(
6636               String.format("Error reading filter conditions for '%s': %s",
6637                       featureType, e.getMessage()));
6638       // return as much as was parsed up to the error
6639     }
6640   
6641     return result;
6642   }
6643
6644   /**
6645    * Adds feature match conditions to matcherSet as unmarshalled from XML
6646    * (possibly recursively for compound conditions)
6647    * 
6648    * @param matcherSet
6649    * @param matcherSetModel
6650    * @param and
6651    *          if true, multiple conditions are AND-ed, else they are OR-ed
6652    * @throws IllegalStateException
6653    *           if AND and OR conditions are mixed
6654    */
6655   protected static void parseFilterConditions(
6656           FeatureMatcherSetI matcherSet,
6657           jalview.xml.binding.jalview.FeatureMatcherSet matcherSetModel,
6658           boolean and)
6659   {
6660     jalview.xml.binding.jalview.FeatureMatcher mc = matcherSetModel
6661             .getMatchCondition();
6662     if (mc != null)
6663     {
6664       /*
6665        * single condition
6666        */
6667       FilterBy filterBy = mc.getBy();
6668       Condition cond = Condition.fromString(mc.getCondition());
6669       String pattern = mc.getValue();
6670       FeatureMatcherI matchCondition = null;
6671       if (filterBy == FilterBy.BY_LABEL)
6672       {
6673         matchCondition = FeatureMatcher.byLabel(cond, pattern);
6674       }
6675       else if (filterBy == FilterBy.BY_SCORE)
6676       {
6677         matchCondition = FeatureMatcher.byScore(cond, pattern);
6678   
6679       }
6680       else if (filterBy == FilterBy.BY_ATTRIBUTE)
6681       {
6682         final List<String> attributeName = mc.getAttributeName();
6683         String[] attNames = attributeName
6684                 .toArray(new String[attributeName.size()]);
6685         matchCondition = FeatureMatcher.byAttribute(cond, pattern,
6686                 attNames);
6687       }
6688   
6689       /*
6690        * note this throws IllegalStateException if AND-ing to a 
6691        * previously OR-ed compound condition, or vice versa
6692        */
6693       if (and)
6694       {
6695         matcherSet.and(matchCondition);
6696       }
6697       else
6698       {
6699         matcherSet.or(matchCondition);
6700       }
6701     }
6702     else
6703     {
6704       /*
6705        * compound condition
6706        */
6707       List<jalview.xml.binding.jalview.FeatureMatcherSet> matchers = matcherSetModel
6708               .getCompoundMatcher().getMatcherSet();
6709       boolean anded = matcherSetModel.getCompoundMatcher().isAnd();
6710       if (matchers.size() == 2)
6711       {
6712         parseFilterConditions(matcherSet, matchers.get(0), anded);
6713         parseFilterConditions(matcherSet, matchers.get(1), anded);
6714       }
6715       else
6716       {
6717         System.err.println("Malformed compound filter condition");
6718       }
6719     }
6720   }
6721
6722   /**
6723    * Loads one XML model of a feature colour to a Jalview object
6724    * 
6725    * @param colourModel
6726    * @return
6727    */
6728   public static FeatureColourI parseColour(Colour colourModel)
6729   {
6730     FeatureColourI colour = null;
6731   
6732     if (colourModel.getMax() != null)
6733     {
6734       Color mincol = null;
6735       Color maxcol = null;
6736       Color noValueColour = null;
6737   
6738       try
6739       {
6740         mincol = new Color(Integer.parseInt(colourModel.getMinRGB(), 16));
6741         maxcol = new Color(Integer.parseInt(colourModel.getRGB(), 16));
6742       } catch (Exception e)
6743       {
6744         Cache.log.warn("Couldn't parse out graduated feature color.", e);
6745       }
6746   
6747       NoValueColour noCol = colourModel.getNoValueColour();
6748       if (noCol == NoValueColour.MIN)
6749       {
6750         noValueColour = mincol;
6751       }
6752       else if (noCol == NoValueColour.MAX)
6753       {
6754         noValueColour = maxcol;
6755       }
6756   
6757       colour = new FeatureColour(maxcol, mincol, maxcol, noValueColour,
6758               safeFloat(colourModel.getMin()),
6759               safeFloat(colourModel.getMax()));
6760       final List<String> attributeName = colourModel.getAttributeName();
6761       String[] attributes = attributeName
6762               .toArray(new String[attributeName.size()]);
6763       if (attributes != null && attributes.length > 0)
6764       {
6765         colour.setAttributeName(attributes);
6766       }
6767       if (colourModel.isAutoScale() != null)
6768       {
6769         colour.setAutoScaled(colourModel.isAutoScale().booleanValue());
6770       }
6771       if (colourModel.isColourByLabel() != null)
6772       {
6773         colour.setColourByLabel(
6774                 colourModel.isColourByLabel().booleanValue());
6775       }
6776       if (colourModel.getThreshold() != null)
6777       {
6778         colour.setThreshold(colourModel.getThreshold().floatValue());
6779       }
6780       ThresholdType ttyp = colourModel.getThreshType();
6781       if (ttyp == ThresholdType.ABOVE)
6782       {
6783         colour.setAboveThreshold(true);
6784       }
6785       else if (ttyp == ThresholdType.BELOW)
6786       {
6787         colour.setBelowThreshold(true);
6788       }
6789     }
6790     else
6791     {
6792       Color color = new Color(Integer.parseInt(colourModel.getRGB(), 16));
6793       colour = new FeatureColour(color);
6794     }
6795   
6796     return colour;
6797   }
6798 }