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