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