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