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