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