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