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