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