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