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