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