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