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