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