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