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