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