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