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