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