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