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