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