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