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