JAL-3487 help/splash <html> wrap issue + associated tooltip breaking
[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   protected 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   protected 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   protected 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   protected 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   protected 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     this.jarFile = null;
2802     return af;
2803   }
2804
2805         @SuppressWarnings("unused")
2806   private jarInputStreamProvider createjarInputStreamProvider(
2807           final Object ofile) throws MalformedURLException
2808   {
2809     try
2810     {
2811       String file = (ofile instanceof File
2812               ? ((File) ofile).getCanonicalPath()
2813               : ofile.toString());
2814       byte[] bytes = Platform.isJS() ? Platform.getFileBytes((File) ofile)
2815               : null;
2816       if (bytes != null)
2817       {
2818         this.jarFile = (File) ofile;
2819       }
2820       errorMessage = null;
2821       uniqueSetSuffix = null;
2822       seqRefIds = null;
2823       viewportsAdded.clear();
2824       frefedSequence = null;
2825
2826       URL url = file.startsWith("http://") ? new URL(file) : null;
2827       return new jarInputStreamProvider()
2828       {
2829         @Override
2830         public JarInputStream getJarInputStream() throws IOException
2831         {
2832           InputStream is = bytes != null ? new ByteArrayInputStream(bytes)
2833                   : (url != null ? url.openStream()
2834                           : new FileInputStream(file));
2835           return new JarInputStream(is);
2836         }
2837
2838         @Override
2839         public String getFilename()
2840         {
2841           return file;
2842         }
2843       };
2844     } catch (IOException e)
2845     {
2846       e.printStackTrace();
2847       return null;
2848     }
2849   }
2850
2851   /**
2852    * Recover jalview session from a jalview project archive. Caller may
2853    * initialise uniqueSetSuffix, seqRefIds, viewportsAdded and frefedSequence
2854    * themselves. Any null fields will be initialised with default values,
2855    * non-null fields are left alone.
2856    * 
2857    * @param jprovider
2858    * @return
2859    */
2860   public AlignFrame loadJalviewAlign(final jarInputStreamProvider jprovider)
2861   {
2862     errorMessage = null;
2863     if (uniqueSetSuffix == null)
2864     {
2865       uniqueSetSuffix = System.currentTimeMillis() % 100000 + "";
2866     }
2867     if (seqRefIds == null)
2868     {
2869       initSeqRefs();
2870     }
2871     AlignFrame af = null, _af = null;
2872     IdentityHashMap<AlignmentI, AlignmentI> importedDatasets = new IdentityHashMap<>();
2873     Map<String, AlignFrame> gatherToThisFrame = new HashMap<>();
2874     final String file = jprovider.getFilename();
2875
2876     List<AlignFrame> alignFrames = new ArrayList<>();
2877
2878     try
2879     {
2880       JarInputStream jin = null;
2881       JarEntry jarentry = null;
2882       int entryCount = 1;
2883
2884
2885       // Look for all the entry names ending with ".xml"
2886       // This includes all panels and at least one frame.
2887 //      Platform.timeCheck(null, Platform.TIME_MARK);
2888       do
2889       {
2890         jin = jprovider.getJarInputStream();
2891         for (int i = 0; i < entryCount; i++)
2892         {
2893           jarentry = jin.getNextJarEntry();
2894         }
2895         String name = (jarentry == null ? null : jarentry.getName());
2896
2897 //        System.out.println("Jalview2XML opening " + name);
2898         if (name != null && name.endsWith(".xml"))
2899         {
2900
2901           // DataSet for.... is read last.
2902           
2903           
2904           // The question here is what to do with the two
2905           // .xml files in the jvp file.
2906           // Some number of them, "...Dataset for...", will be the
2907           // Only AlignPanels and will have Viewport.
2908           // One or more will be the source data, with the DBRefs.
2909           //
2910           // JVP file writing (above) ensures tha the AlignPanels are written
2911           // first, then all relevant datasets (which are
2912           // Jalview.datamodel.Alignment).
2913           //
2914
2915 //          Platform.timeCheck("Jalview2XML JAXB " + name, Platform.TIME_MARK);
2916           JAXBContext jc = JAXBContext
2917                   .newInstance("jalview.xml.binding.jalview");
2918           XMLStreamReader streamReader = XMLInputFactory.newInstance()
2919                   .createXMLStreamReader(jin);
2920           javax.xml.bind.Unmarshaller um = jc.createUnmarshaller();
2921           JAXBElement<JalviewModel> jbe = um
2922                   .unmarshal(streamReader, JalviewModel.class);
2923           JalviewModel model = jbe.getValue();
2924
2925           if (true) // !skipViewport(object))
2926           {
2927             // Q: Do we have to load from the model, even if it
2928             // does not have a viewport, could we discover that early on?
2929             // Q: Do we need to load this object?
2930             _af = loadFromObject(model, file, true, jprovider);
2931 //            Platform.timeCheck("Jalview2XML.loadFromObject",
2932             // Platform.TIME_MARK);
2933
2934             if (_af != null)
2935             {
2936               alignFrames.add(_af);
2937             }
2938             if (_af != null && model.getViewport().size() > 0)
2939             {
2940
2941               // That is, this is one of the AlignmentPanel models
2942               if (af == null)
2943               {
2944                 // store a reference to the first view
2945                 af = _af;
2946               }
2947               if (_af.getViewport().isGatherViewsHere())
2948               {
2949                 // if this is a gathered view, keep its reference since
2950                 // after gathering views, only this frame will remain
2951                 af = _af;
2952                 gatherToThisFrame.put(_af.getViewport().getSequenceSetId(),
2953                         _af);
2954               }
2955               // Save dataset to register mappings once all resolved
2956               importedDatasets.put(
2957                       af.getViewport().getAlignment().getDataset(),
2958                       af.getViewport().getAlignment().getDataset());
2959             }
2960           }
2961 //          Platform.timeCheck("JAXB " + name, Platform.TIME_MARK);
2962           entryCount++;
2963         }
2964         else if (jarentry != null)
2965         {
2966           // Some other file here.
2967           entryCount++;
2968         }
2969       } while (jarentry != null);
2970 //      Platform.timeCheck("JAXB loop exit", Platform.TIME_MARK);
2971       resolveFrefedSequences();
2972 //      Platform.timeCheck("JAXB resolveFrefed", Platform.TIME_MARK);
2973
2974     } catch (IOException ex)
2975     {
2976       ex.printStackTrace();
2977       errorMessage = "Couldn't locate Jalview XML file : " + file;
2978       System.err.println(
2979               "Exception whilst loading jalview XML file : " + ex + "\n");
2980     } catch (Exception ex)
2981     {
2982       System.err.println("Parsing as Jalview Version 2 file failed.");
2983       ex.printStackTrace(System.err);
2984       if (attemptversion1parse)
2985       {
2986         // used to attempt to parse as V1 castor-generated xml
2987       }
2988       if (Desktop.getInstance() != null)
2989       {
2990         Desktop.getInstance().stopLoading();
2991       }
2992       if (af != null)
2993       {
2994         System.out.println("Successfully loaded archive file");
2995         return af;
2996       }
2997       ex.printStackTrace();
2998
2999       System.err.println(
3000               "Exception whilst loading jalview XML file : " + ex + "\n");
3001     } catch (OutOfMemoryError e)
3002     {
3003       // Don't use the OOM Window here
3004       errorMessage = "Out of memory loading jalview XML file";
3005       System.err.println("Out of memory whilst loading jalview XML file");
3006       e.printStackTrace();
3007     } finally
3008     {
3009       for (AlignFrame alf : alignFrames)
3010       {
3011         alf.alignPanel.setHoldRepaint(false);
3012       }
3013
3014     }
3015
3016     /*
3017      * Regather multiple views (with the same sequence set id) to the frame (if
3018      * any) that is flagged as the one to gather to, i.e. convert them to tabbed
3019      * views instead of separate frames. Note this doesn't restore a state where
3020      * some expanded views in turn have tabbed views - the last "first tab" read
3021      * in will play the role of gatherer for all.
3022      */
3023     for (AlignFrame fr : gatherToThisFrame.values())
3024     {
3025       Desktop.getInstance().gatherViews(fr);
3026     }
3027
3028     restoreSplitFrames();
3029     for (AlignmentI ds : importedDatasets.keySet())
3030     {
3031       if (ds.getCodonFrames() != null)
3032       {
3033         Desktop.getStructureSelectionManager()
3034                 .registerMappings(ds.getCodonFrames());
3035       }
3036     }
3037     if (errorMessage != null)
3038     {
3039       reportErrors();
3040     }
3041
3042     if (Desktop.getInstance() != null)
3043     {
3044       Desktop.getInstance().stopLoading();
3045     }
3046
3047     return af;
3048   }
3049
3050   /**
3051    * Try to reconstruct and display SplitFrame windows, where each contains
3052    * complementary dna and protein alignments. Done by pairing up AlignFrame
3053    * objects (created earlier) which have complementary viewport ids associated.
3054    */
3055   protected void restoreSplitFrames()
3056   {
3057     List<SplitFrame> gatherTo = new ArrayList<>();
3058     List<AlignFrame> addedToSplitFrames = new ArrayList<>();
3059     Map<String, AlignFrame> dna = new HashMap<>();
3060
3061     /*
3062      * Identify the DNA alignments
3063      */
3064     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
3065             .entrySet())
3066     {
3067       AlignFrame af = candidate.getValue();
3068       if (af.getViewport().getAlignment().isNucleotide())
3069       {
3070         dna.put(candidate.getKey().getId(), af);
3071       }
3072     }
3073
3074     /*
3075      * Try to match up the protein complements
3076      */
3077     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
3078             .entrySet())
3079     {
3080       AlignFrame af = candidate.getValue();
3081       if (!af.getViewport().getAlignment().isNucleotide())
3082       {
3083         String complementId = candidate.getKey().getComplementId();
3084         // only non-null complements should be in the Map
3085         if (complementId != null && dna.containsKey(complementId))
3086         {
3087           final AlignFrame dnaFrame = dna.get(complementId);
3088           SplitFrame sf = createSplitFrame(dnaFrame, af);
3089           addedToSplitFrames.add(dnaFrame);
3090           addedToSplitFrames.add(af);
3091           dnaFrame.setMenusForViewport();
3092           af.setMenusForViewport();
3093           if (af.getViewport().isGatherViewsHere())
3094           {
3095             gatherTo.add(sf);
3096           }
3097         }
3098       }
3099     }
3100
3101     /*
3102      * Open any that we failed to pair up (which shouldn't happen!) as
3103      * standalone AlignFrame's.
3104      */
3105     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
3106             .entrySet())
3107     {
3108       AlignFrame af = candidate.getValue();
3109       if (!addedToSplitFrames.contains(af))
3110       {
3111         Viewport view = candidate.getKey();
3112         Desktop.addInternalFrame(af, view.getTitle(),
3113                 safeInt(view.getWidth()), safeInt(view.getHeight()));
3114         af.setMenusForViewport();
3115         System.err.println("Failed to restore view " + view.getTitle()
3116                 + " to split frame");
3117       }
3118     }
3119
3120     /*
3121      * Gather back into tabbed views as flagged.
3122      */
3123     for (SplitFrame sf : gatherTo)
3124     {
3125       Desktop.getInstance().gatherViews(sf);
3126     }
3127
3128     splitFrameCandidates.clear();
3129   }
3130
3131   /**
3132    * Construct and display one SplitFrame holding DNA and protein alignments.
3133    * 
3134    * @param dnaFrame
3135    * @param proteinFrame
3136    * @return
3137    */
3138   protected SplitFrame createSplitFrame(AlignFrame dnaFrame,
3139           AlignFrame proteinFrame)
3140   {
3141     SplitFrame splitFrame = new SplitFrame(dnaFrame, proteinFrame);
3142     String title = MessageManager.getString("label.linked_view_title");
3143     int width = (int) dnaFrame.getBounds().getWidth();
3144     int height = (int) (dnaFrame.getBounds().getHeight()
3145             + proteinFrame.getBounds().getHeight() + 50);
3146
3147     /*
3148      * SplitFrame location is saved to both enclosed frames
3149      */
3150     splitFrame.setLocation(dnaFrame.getX(), dnaFrame.getY());
3151     Desktop.addInternalFrame(splitFrame, title, width, height);
3152
3153     /*
3154      * And compute cDNA consensus (couldn't do earlier with consensus as
3155      * mappings were not yet present)
3156      */
3157     proteinFrame.getViewport().alignmentChanged(proteinFrame.alignPanel);
3158
3159     return splitFrame;
3160   }
3161
3162   /**
3163    * check errorMessage for a valid error message and raise an error box in the
3164    * GUI or write the current errorMessage to stderr and then clear the error
3165    * state.
3166    */
3167   protected void reportErrors()
3168   {
3169     reportErrors(false);
3170   }
3171
3172   protected void reportErrors(final boolean saving)
3173   {
3174     if (errorMessage != null)
3175     {
3176       final String finalErrorMessage = errorMessage;
3177       if (raiseGUI)
3178       {
3179         javax.swing.SwingUtilities.invokeLater(new Runnable()
3180         {
3181           @Override
3182           public void run()
3183           {
3184             JvOptionPane.showInternalMessageDialog(Desktop.getDesktopPane(),
3185                     finalErrorMessage,
3186                     "Error " + (saving ? "saving" : "loading")
3187                             + " Jalview file",
3188                     JvOptionPane.WARNING_MESSAGE);
3189           }
3190         });
3191       }
3192       else
3193       {
3194         System.err.println("Problem loading Jalview file: " + errorMessage);
3195       }
3196     }
3197     errorMessage = null;
3198   }
3199
3200   Map<String, String> alreadyLoadedPDB = new HashMap<>();
3201
3202   /**
3203    * when set, local views will be updated from view stored in JalviewXML
3204    * Currently (28th Sep 2008) things will go horribly wrong in vamsas document
3205    * sync if this is set to true.
3206    */
3207   private final boolean updateLocalViews = false;
3208
3209   /**
3210    * Returns the path to a temporary file holding the PDB file for the given PDB
3211    * id. The first time of asking, searches for a file of that name in the
3212    * Jalview project jar, and copies it to a new temporary file. Any repeat
3213    * requests just return the path to the file previously created.
3214    * 
3215    * @param jprovider
3216    * @param pdbId
3217    * @return
3218    */
3219   String loadPDBFile(jarInputStreamProvider jprovider, String pdbId,
3220           String origFile)
3221   {
3222     if (alreadyLoadedPDB.containsKey(pdbId))
3223     {
3224       return alreadyLoadedPDB.get(pdbId).toString();
3225     }
3226
3227     String tempFile = copyJarEntry(jprovider, pdbId, "jalview_pdb",
3228             origFile);
3229     if (tempFile != null)
3230     {
3231       alreadyLoadedPDB.put(pdbId, tempFile);
3232     }
3233     return tempFile;
3234   }
3235
3236   /**
3237    * Copies the jar entry of given name to a new temporary file and returns the
3238    * path to the file, or null if the entry is not found.
3239    * 
3240    * @param jprovider
3241    * @param jarEntryName
3242    * @param prefix
3243    *          a prefix for the temporary file name, must be at least three
3244    *          characters long
3245    * @param origFile
3246    *          null or original file - so new file can be given the same suffix
3247    *          as the old one
3248    * @return
3249    */
3250   protected String copyJarEntry(jarInputStreamProvider jprovider,
3251           String jarEntryName, String prefix, String origFile)
3252   {
3253     BufferedReader in = null;
3254     PrintWriter out = null;
3255     String suffix = ".tmp";
3256     if (origFile == null)
3257     {
3258       origFile = jarEntryName;
3259     }
3260     int sfpos = origFile.lastIndexOf(".");
3261     if (sfpos > -1 && sfpos < (origFile.length() - 3))
3262     {
3263       suffix = "." + origFile.substring(sfpos + 1);
3264     }
3265     try
3266     {
3267       JarInputStream jin = jprovider.getJarInputStream();
3268       /*
3269        * if (jprovider.startsWith("http://")) { jin = new JarInputStream(new
3270        * URL(jprovider).openStream()); } else { jin = new JarInputStream(new
3271        * FileInputStream(jprovider)); }
3272        */
3273
3274       JarEntry entry = null;
3275       do
3276       {
3277         entry = jin.getNextJarEntry();
3278       } while (entry != null && !entry.getName().equals(jarEntryName));
3279       if (entry != null)
3280       {
3281         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
3282         File outFile = File.createTempFile(prefix, suffix);
3283         outFile.deleteOnExit();
3284         out = new PrintWriter(new FileOutputStream(outFile));
3285         String data;
3286
3287         while ((data = in.readLine()) != null)
3288         {
3289           out.println(data);
3290         }
3291         out.flush();
3292         String t = outFile.getAbsolutePath();
3293         return t;
3294       }
3295       else
3296       {
3297         warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
3298       }
3299     } catch (Exception ex)
3300     {
3301       ex.printStackTrace();
3302     } finally
3303     {
3304       if (in != null)
3305       {
3306         try
3307         {
3308           in.close();
3309         } catch (IOException e)
3310         {
3311           // ignore
3312         }
3313       }
3314       if (out != null)
3315       {
3316         out.close();
3317       }
3318     }
3319
3320     return null;
3321   }
3322
3323   private class JvAnnotRow
3324   {
3325     public JvAnnotRow(int i, AlignmentAnnotation jaa)
3326     {
3327       order = i;
3328       template = jaa;
3329     }
3330
3331     /**
3332      * persisted version of annotation row from which to take vis properties
3333      */
3334     public jalview.datamodel.AlignmentAnnotation template;
3335
3336     /**
3337      * original position of the annotation row in the alignment
3338      */
3339     public int order;
3340   }
3341
3342   /**
3343    * Load alignment frame from jalview XML DOM object. For a DOM object that
3344    * includes one or more Viewport elements (one with a title that does NOT
3345    * contain "Dataset for"), create the frame.
3346    * 
3347    * @param jalviewModel
3348    *          DOM
3349    * @param file
3350    *          filename source string
3351    * @param loadTreesAndStructures
3352    *          when false only create Viewport
3353    * @param jprovider
3354    *          data source provider
3355    * @return alignment frame created from view stored in DOM
3356    */
3357   AlignFrame loadFromObject(JalviewModel jalviewModel, String file,
3358           boolean loadTreesAndStructures, jarInputStreamProvider jprovider)
3359   {
3360
3361 //    Platform.timeCheck("Jalview2XML.loadFromObject0", Platform.TIME_MARK);
3362
3363     SequenceSet vamsasSet = jalviewModel.getVamsasModel().getSequenceSet().get(0);
3364     List<Sequence> vamsasSeqs = vamsasSet.getSequence();
3365
3366
3367     // JalviewModelSequence jms = object.getJalviewModelSequence();
3368
3369     // Viewport view = (jms.getViewportCount() > 0) ? jms.getViewport(0)
3370     // : null;
3371     Viewport view = (jalviewModel.getViewport().size() > 0)
3372             ? jalviewModel.getViewport().get(0)
3373             : null;
3374
3375     // ////////////////////////////////
3376     // INITIALISE ALIGNMENT SEQUENCESETID AND VIEWID
3377     //
3378     //
3379     // If we just load in the same jar file again, the sequenceSetId
3380     // will be the same, and we end up with multiple references
3381     // to the same sequenceSet. We must modify this id on load
3382     // so that each load of the file gives a unique id
3383
3384     /**
3385      * used to resolve correct alignment dataset for alignments with multiple
3386      * views
3387      */
3388     String uniqueSeqSetId = null;
3389     String viewId = null;
3390     if (view != null)
3391     {
3392       uniqueSeqSetId = view.getSequenceSetId() + uniqueSetSuffix;
3393       viewId = (view.getId() == null ? null
3394               : view.getId() + uniqueSetSuffix);
3395     }
3396
3397 //    Platform.timeCheck("Jalview2XML.loadFromObject1", Platform.TIME_MARK);
3398     // ////////////////////////////////
3399     // LOAD SEQUENCES
3400
3401     List<SequenceI> hiddenSeqs = null;
3402
3403     List<SequenceI> tmpseqs = new ArrayList<>();
3404
3405     boolean multipleView = false;
3406     SequenceI referenceseqForView = null;
3407     // JSeq[] jseqs = object.getJalviewModelSequence().getJSeq();
3408     List<JSeq> jseqs = jalviewModel.getJSeq();
3409     int vi = 0; // counter in vamsasSeq array
3410     for (int i = 0; i < jseqs.size(); i++)
3411     {
3412       JSeq jseq = jseqs.get(i);
3413       String seqId = jseq.getId();
3414
3415       SequenceI tmpSeq = seqRefIds.get(seqId);
3416       if (tmpSeq != null)
3417       {
3418         //
3419         if (!incompleteSeqs.containsKey(seqId))
3420         {
3421           // may not need this check, but keep it for at least 2.9,1 release
3422           if (tmpSeq.getStart() != jseq.getStart()
3423                   || tmpSeq.getEnd() != jseq.getEnd())
3424           {
3425             System.err.println(
3426                     "Warning JAL-2154 regression: updating start/end for sequence "
3427                             + tmpSeq.toString() + " to " + jseq);
3428           }
3429         }
3430         else
3431         {
3432           incompleteSeqs.remove(seqId);
3433         }
3434         if (vamsasSeqs.size() > vi
3435                 && vamsasSeqs.get(vi).getId().equals(seqId))
3436         {
3437           // most likely we are reading a dataset XML document so
3438           // update from vamsasSeq section of XML for this sequence
3439           tmpSeq.setName(vamsasSeqs.get(vi).getName());
3440           tmpSeq.setDescription(vamsasSeqs.get(vi).getDescription());
3441           tmpSeq.setSequence(vamsasSeqs.get(vi).getSequence());
3442           vi++;
3443         }
3444         else
3445         {
3446           // reading multiple views, so vamsasSeq set is a subset of JSeq
3447           multipleView = true;
3448         }
3449         tmpSeq.setStart(jseq.getStart());
3450         tmpSeq.setEnd(jseq.getEnd());
3451         tmpseqs.add(tmpSeq);
3452       }
3453       else
3454       {
3455         Sequence vamsasSeq = vamsasSeqs.get(vi);
3456         tmpSeq = new jalview.datamodel.Sequence(vamsasSeq.getName(),
3457                 vamsasSeq.getSequence());
3458         tmpSeq.setDescription(vamsasSeq.getDescription());
3459         tmpSeq.setStart(jseq.getStart());
3460         tmpSeq.setEnd(jseq.getEnd());
3461         tmpSeq.setVamsasId(uniqueSetSuffix + seqId);
3462         seqRefIds.put(vamsasSeq.getId(), tmpSeq);
3463         tmpseqs.add(tmpSeq);
3464         vi++;
3465       }
3466
3467       if (safeBoolean(jseq.isViewreference()))
3468       {
3469         referenceseqForView = tmpseqs.get(tmpseqs.size() - 1);
3470       }
3471
3472       if (jseq.isHidden() != null && jseq.isHidden().booleanValue())
3473       {
3474         if (hiddenSeqs == null)
3475         {
3476           hiddenSeqs = new ArrayList<>();
3477         }
3478
3479         hiddenSeqs.add(tmpSeq);
3480       }
3481     }
3482
3483 //    Platform.timeCheck("Jalview2XML.loadFromObject-seq",
3484 //            Platform.TIME_MARK);
3485     // /
3486     // Create the alignment object from the sequence set
3487     // ///////////////////////////////
3488     SequenceI[] orderedSeqs = tmpseqs
3489             .toArray(new SequenceI[tmpseqs.size()]);
3490
3491     AlignmentI al = null;
3492     // so we must create or recover the dataset alignment before going further
3493     // ///////////////////////////////
3494     if (vamsasSet.getDatasetId() == null || vamsasSet.getDatasetId() == "")
3495     {
3496       // older jalview projects do not have a dataset - so creat alignment and
3497       // dataset
3498       al = new Alignment(orderedSeqs);
3499       al.setDataset(null);
3500     }
3501     else
3502     {
3503       boolean isdsal = jalviewModel.getViewport().isEmpty();
3504       if (isdsal)
3505       {
3506         // we are importing a dataset record, so
3507         // recover reference to an alignment already materialsed as dataset
3508         al = getDatasetFor(vamsasSet.getDatasetId());
3509       }
3510       if (al == null)
3511       {
3512         // materialse the alignment
3513         al = new Alignment(orderedSeqs);
3514       }
3515       if (isdsal)
3516       {
3517         addDatasetRef(vamsasSet.getDatasetId(), al);
3518       }
3519
3520       // finally, verify all data in vamsasSet is actually present in al
3521       // passing on flag indicating if it is actually a stored dataset
3522       recoverDatasetFor(vamsasSet, al, isdsal, uniqueSeqSetId);
3523     }
3524
3525 //    Platform.timeCheck("Jalview2XML.loadFromObject-align",
3526 //            Platform.TIME_MARK);
3527     if (referenceseqForView != null)
3528     {
3529       al.setSeqrep(referenceseqForView);
3530     }
3531     // / Add the alignment properties
3532     for (int i = 0; i < vamsasSet.getSequenceSetProperties().size(); i++)
3533     {
3534       SequenceSetProperties ssp = vamsasSet.getSequenceSetProperties()
3535               .get(i);
3536       al.setProperty(ssp.getKey(), ssp.getValue());
3537     }
3538
3539 //    Platform.timeCheck("Jalview2XML.loadFromObject-setseqprop",
3540 //            Platform.TIME_MARK);
3541     // ///////////////////////////////
3542
3543     Hashtable pdbloaded = new Hashtable(); // TODO nothing writes to this??
3544     if (!multipleView)
3545     {
3546       // load sequence features, database references and any associated PDB
3547       // structures for the alignment
3548       //
3549       // prior to 2.10, this part would only be executed the first time a
3550       // sequence was encountered, but not afterwards.
3551       // now, for 2.10 projects, this is also done if the xml doc includes
3552       // dataset sequences not actually present in any particular view.
3553       //
3554 //      Platform.timeCheck("J2XML features0", Platform.TIME_RESET);
3555       for (int i = 0; i < vamsasSeqs.size(); i++)
3556       {
3557         JSeq jseq = jseqs.get(i);
3558         if (jseq.getFeatures().size() > 0)
3559         {
3560           List<Feature> features = jseq.getFeatures();
3561           for (int f = 0; f < features.size(); f++)
3562           {
3563             Feature feat = features.get(f);
3564             SequenceFeature sf = new SequenceFeature(feat.getType(),
3565                     feat.getDescription(), feat.getBegin(), feat.getEnd(),
3566                     safeFloat(feat.getScore()), feat.getFeatureGroup());
3567             sf.setStatus(feat.getStatus());
3568
3569             /*
3570              * load any feature attributes - include map-valued attributes
3571              */
3572             Map<String, Map<String, String>> mapAttributes = new HashMap<>();
3573             for (int od = 0; od < feat.getOtherData().size(); od++)
3574             {
3575               OtherData keyValue = feat.getOtherData().get(od);
3576               String attributeName = keyValue.getKey();
3577               String attributeValue = keyValue.getValue();
3578               if (attributeName.startsWith("LINK"))
3579               {
3580                 sf.addLink(attributeValue);
3581               }
3582               else
3583               {
3584                 String subAttribute = keyValue.getKey2();
3585                 if (subAttribute == null)
3586                 {
3587                   // simple string-valued attribute
3588                   sf.setValue(attributeName, attributeValue);
3589                 }
3590                 else
3591                 {
3592                   // attribute 'key' has sub-attribute 'key2'
3593                   if (!mapAttributes.containsKey(attributeName))
3594                   {
3595                     mapAttributes.put(attributeName, new HashMap<>());
3596                   }
3597                   mapAttributes.get(attributeName).put(subAttribute,
3598                           attributeValue);
3599                 }
3600               }
3601             }
3602             for (Entry<String, Map<String, String>> mapAttribute : mapAttributes
3603                     .entrySet())
3604             {
3605               sf.setValue(mapAttribute.getKey(), mapAttribute.getValue());
3606             }
3607
3608             // adds feature to datasequence's feature set (since Jalview 2.10)
3609 //            Platform.timeCheck(null, Platform.TIME_SET);
3610             al.getSequenceAt(i).addSequenceFeature(sf);
3611 //            Platform.timeCheck(null, Platform.TIME_MARK);
3612           }
3613         }
3614         if (vamsasSeqs.get(i).getDBRef().size() > 0)
3615         {
3616           // adds dbrefs to datasequence's set (since Jalview 2.10)
3617           addDBRefs(
3618                   al.getSequenceAt(i).getDatasetSequence() == null
3619                           ? al.getSequenceAt(i)
3620                           : al.getSequenceAt(i).getDatasetSequence(),
3621                   vamsasSeqs.get(i));
3622         }
3623         if (jseq.getPdbids().size() > 0)
3624         {
3625           List<Pdbids> ids = jseq.getPdbids();
3626           for (int p = 0; p < ids.size(); p++)
3627           {
3628             Pdbids pdbid = ids.get(p);
3629             jalview.datamodel.PDBEntry entry = new jalview.datamodel.PDBEntry();
3630             entry.setId(pdbid.getId());
3631             if (pdbid.getType() != null)
3632             {
3633               if (PDBEntry.Type.getType(pdbid.getType()) != null)
3634               {
3635                 entry.setType(PDBEntry.Type.getType(pdbid.getType()));
3636               }
3637               else
3638               {
3639                 entry.setType(PDBEntry.Type.FILE);
3640               }
3641             }
3642             // jprovider is null when executing 'New View'
3643             if (pdbid.getFile() != null && jprovider != null)
3644             {
3645               if (!pdbloaded.containsKey(pdbid.getFile()))
3646               {
3647                 entry.setFile(loadPDBFile(jprovider, pdbid.getId(),
3648                         pdbid.getFile()));
3649               }
3650               else
3651               {
3652                 entry.setFile(pdbloaded.get(pdbid.getId()).toString());
3653               }
3654             }
3655             /*
3656             if (pdbid.getPdbentryItem() != null)
3657             {
3658               for (PdbentryItem item : pdbid.getPdbentryItem())
3659               {
3660                 for (Property pr : item.getProperty())
3661                 {
3662                   entry.setProperty(pr.getName(), pr.getValue());
3663                 }
3664               }
3665             }
3666             */
3667             for (Property prop : pdbid.getProperty())
3668             {
3669               entry.setProperty(prop.getName(), prop.getValue());
3670             }
3671             Desktop.getStructureSelectionManager()
3672                     .registerPDBEntry(entry);
3673             // adds PDBEntry to datasequence's set (since Jalview 2.10)
3674             if (al.getSequenceAt(i).getDatasetSequence() != null)
3675             {
3676               al.getSequenceAt(i).getDatasetSequence().addPDBId(entry);
3677             }
3678             else
3679             {
3680               al.getSequenceAt(i).addPDBId(entry);
3681             }
3682           }
3683         }
3684
3685       }
3686
3687 //      Platform.timeCheck("features done", Platform.TIME_GET);
3688 //      Platform.timeCheck("Jalview2XML.loadFromObject-endmultiview",
3689 //              Platform.TIME_MARK);
3690     } // end !multipleview
3691
3692     // ///////////////////////////////
3693     // LOAD SEQUENCE MAPPINGS
3694
3695     if (vamsasSet.getAlcodonFrame().size() > 0)
3696     {
3697       // TODO Potentially this should only be done once for all views of an
3698       // alignment
3699       List<AlcodonFrame> alc = vamsasSet.getAlcodonFrame();
3700       for (int i = 0; i < alc.size(); i++)
3701       {
3702         AlignedCodonFrame cf = new AlignedCodonFrame();
3703         if (alc.get(i).getAlcodMap().size() > 0)
3704         {
3705           List<AlcodMap> maps = alc.get(i).getAlcodMap();
3706           for (int m = 0; m < maps.size(); m++)
3707           {
3708             AlcodMap map = maps.get(m);
3709             SequenceI dnaseq = seqRefIds.get(map.getDnasq());
3710             // Load Mapping
3711             jalview.datamodel.Mapping mapping = null;
3712             // attach to dna sequence reference.
3713             if (map.getMapping() != null)
3714             {
3715               mapping = addMapping(map.getMapping());
3716               if (dnaseq != null && mapping.getTo() != null)
3717               {
3718                 cf.addMap(dnaseq, mapping.getTo(), mapping.getMap());
3719               }
3720               else
3721               {
3722                 // defer to later
3723                 frefedSequence.add(
3724                         newAlcodMapRef(map.getDnasq(), cf, mapping));
3725               }
3726             }
3727           }
3728           al.addCodonFrame(cf);
3729         }
3730       }
3731 //      Platform.timeCheck("Jalview2XML.loadFromObject-seqmap",
3732 //              Platform.TIME_MARK);
3733     }
3734
3735     // ////////////////////////////////
3736     // LOAD ANNOTATIONS
3737     List<JvAnnotRow> autoAlan = new ArrayList<>();
3738
3739     /*
3740      * store any annotations which forward reference a group's ID
3741      */
3742     Map<String, List<AlignmentAnnotation>> groupAnnotRefs = new Hashtable<>();
3743
3744     if (vamsasSet.getAnnotation().size()/*Count()*/ > 0)
3745     {
3746       List<Annotation> an = vamsasSet.getAnnotation();
3747
3748       for (int i = 0; i < an.size(); i++)
3749       {
3750         Annotation annotation = an.get(i);
3751
3752         /**
3753          * test if annotation is automatically calculated for this view only
3754          */
3755         boolean autoForView = false;
3756         if (annotation.getLabel().equals("Quality")
3757                 || annotation.getLabel().equals("Conservation")
3758                 || annotation.getLabel().equals("Consensus"))
3759         {
3760           // Kludge for pre 2.5 projects which lacked the autocalculated flag
3761           autoForView = true;
3762           // JAXB has no has() test; schema defaults value to false
3763           // if (!annotation.hasAutoCalculated())
3764           // {
3765           // annotation.setAutoCalculated(true);
3766           // }
3767         }
3768         if (autoForView || annotation.isAutoCalculated())
3769         {
3770           // remove ID - we don't recover annotation from other views for
3771           // view-specific annotation
3772           annotation.setId(null);
3773         }
3774
3775         // set visibility for other annotation in this view
3776         String annotationId = annotation.getId();
3777         if (annotationId != null && annotationIds.containsKey(annotationId))
3778         {
3779           AlignmentAnnotation jda = annotationIds.get(annotationId);
3780           // in principle Visible should always be true for annotation displayed
3781           // in multiple views
3782           if (annotation.isVisible() != null)
3783           {
3784             jda.visible = annotation.isVisible();
3785           }
3786
3787           al.addAnnotation(jda);
3788
3789           continue;
3790         }
3791         // Construct new annotation from model.
3792         List<AnnotationElement> ae = annotation.getAnnotationElement();
3793 //        System.err.println(
3794 //                "Jalview2XML processing " + ae.size() + " annotations");
3795
3796         jalview.datamodel.Annotation[] anot = null;
3797         java.awt.Color firstColour = null;
3798         int anpos;
3799         if (!annotation.isScoreOnly())
3800         {
3801           anot = new jalview.datamodel.Annotation[al.getWidth()];
3802           for (int aa = 0; aa < ae.size() && aa < anot.length; aa++)
3803           {
3804             AnnotationElement annElement = ae.get(aa);
3805             anpos = annElement.getPosition();
3806
3807             if (anpos >= anot.length)
3808             {
3809               continue;
3810             }
3811
3812             float value = safeFloat(annElement.getValue());
3813             anot[anpos] = new jalview.datamodel.Annotation(
3814                     annElement.getDisplayCharacter(),
3815                     annElement.getDescription(),
3816                     (annElement.getSecondaryStructure() == null
3817                             || annElement.getSecondaryStructure()
3818                                     .length() == 0)
3819                                             ? ' '
3820                                             : annElement
3821                                                     .getSecondaryStructure()
3822                                                     .charAt(0),
3823                     value);
3824             anot[anpos].colour = new Color(safeInt(annElement.getColour()));
3825             if (firstColour == null)
3826             {
3827               firstColour = anot[anpos].colour;
3828             }
3829           }
3830         }
3831         // create the new AlignmentAnnotation
3832         jalview.datamodel.AlignmentAnnotation jaa = null;
3833
3834         if (annotation.isGraph())
3835         {
3836           float llim = 0, hlim = 0;
3837           // if (autoForView || an[i].isAutoCalculated()) {
3838           // hlim=11f;
3839           // }
3840           jaa = new jalview.datamodel.AlignmentAnnotation(
3841                   annotation.getLabel(), annotation.getDescription(), anot,
3842                   llim, hlim, safeInt(annotation.getGraphType()));
3843
3844           jaa.graphGroup = safeInt(annotation.getGraphGroup());
3845           jaa._linecolour = firstColour;
3846           if (annotation.getThresholdLine() != null)
3847           {
3848             jaa.setThreshold(new jalview.datamodel.GraphLine(
3849                     safeFloat(annotation.getThresholdLine().getValue()),
3850                     annotation.getThresholdLine().getLabel(),
3851                     new java.awt.Color(safeInt(
3852                             annotation.getThresholdLine().getColour()))));
3853           }
3854           if (autoForView || annotation.isAutoCalculated())
3855           {
3856             // Hardwire the symbol display line to ensure that labels for
3857             // histograms are displayed
3858             jaa.hasText = true;
3859           }
3860         }
3861         else
3862         {
3863           jaa = new jalview.datamodel.AlignmentAnnotation(
3864                   annotation.getLabel(), annotation.getDescription(), anot);
3865           jaa._linecolour = firstColour;
3866         }
3867         // register new annotation
3868         // Annotation graphs such as Conservation will not have id.
3869         if (annotation.getId() != null)
3870         {
3871           annotationIds.put(annotation.getId(), jaa);
3872           jaa.annotationId = annotation.getId();
3873         }
3874         // recover sequence association
3875         String sequenceRef = annotation.getSequenceRef();
3876         if (sequenceRef != null)
3877         {
3878           // from 2.9 sequenceRef is to sequence id (JAL-1781)
3879           SequenceI sequence = seqRefIds.get(sequenceRef);
3880           if (sequence == null)
3881           {
3882             // in pre-2.9 projects sequence ref is to sequence name
3883             sequence = al.findName(sequenceRef);
3884           }
3885           if (sequence != null)
3886           {
3887             jaa.createSequenceMapping(sequence, 1, true);
3888             sequence.addAlignmentAnnotation(jaa);
3889           }
3890         }
3891         // and make a note of any group association
3892         if (annotation.getGroupRef() != null
3893                 && annotation.getGroupRef().length() > 0)
3894         {
3895           List<jalview.datamodel.AlignmentAnnotation> aal = groupAnnotRefs
3896                   .get(annotation.getGroupRef());
3897           if (aal == null)
3898           {
3899             aal = new ArrayList<>();
3900             groupAnnotRefs.put(annotation.getGroupRef(), aal);
3901           }
3902           aal.add(jaa);
3903         }
3904
3905         if (annotation.getScore() != null)
3906         {
3907           jaa.setScore(annotation.getScore().doubleValue());
3908         }
3909         if (annotation.isVisible() != null)
3910         {
3911           jaa.visible = annotation.isVisible().booleanValue();
3912         }
3913
3914         if (annotation.isCentreColLabels() != null)
3915         {
3916           jaa.centreColLabels = annotation.isCentreColLabels()
3917                   .booleanValue();
3918         }
3919
3920         if (annotation.isScaleColLabels() != null)
3921         {
3922           jaa.scaleColLabel = annotation.isScaleColLabels().booleanValue();
3923         }
3924         if (annotation.isAutoCalculated())
3925         {
3926           // newer files have an 'autoCalculated' flag and store calculation
3927           // state in viewport properties
3928           jaa.autoCalculated = true; // means annotation will be marked for
3929           // update at end of load.
3930         }
3931         if (annotation.getGraphHeight() != null)
3932         {
3933           jaa.graphHeight = annotation.getGraphHeight().intValue();
3934         }
3935         jaa.belowAlignment = annotation.isBelowAlignment();
3936         jaa.setCalcId(annotation.getCalcId());
3937         if (annotation.getProperty().size() > 0)
3938         {
3939           for (Annotation.Property prop : annotation
3940                   .getProperty())
3941           {
3942             jaa.setProperty(prop.getName(), prop.getValue());
3943           }
3944         }
3945         if (jaa.autoCalculated)
3946         {
3947           autoAlan.add(new JvAnnotRow(i, jaa));
3948         }
3949         else
3950         // if (!autoForView)
3951         {
3952           // add autocalculated group annotation and any user created annotation
3953           // for the view
3954           al.addAnnotation(jaa);
3955         }
3956       }
3957 //      Platform.timeCheck("Jalview2XML.loadFromObject-annot",
3958 //              Platform.TIME_MARK);
3959     }
3960     // ///////////////////////
3961     // LOAD GROUPS
3962     // Create alignment markup and styles for this view
3963     if (jalviewModel.getJGroup().size() > 0)
3964     {
3965       List<JGroup> groups = jalviewModel.getJGroup();
3966       boolean addAnnotSchemeGroup = false;
3967       for (int i = 0; i < groups.size(); i++)
3968       {
3969         JGroup jGroup = groups.get(i);
3970         ColourSchemeI cs = null;
3971         if (jGroup.getColour() != null)
3972         {
3973           if (jGroup.getColour().startsWith("ucs"))
3974           {
3975             cs = getUserColourScheme(jalviewModel, jGroup.getColour());
3976           }
3977           else if (jGroup.getColour().equals("AnnotationColourGradient")
3978                   && jGroup.getAnnotationColours() != null)
3979           {
3980             addAnnotSchemeGroup = true;
3981           }
3982           else
3983           {
3984             cs = ColourSchemeProperty.getColourScheme(null, al,
3985                     jGroup.getColour());
3986           }
3987         }
3988         int pidThreshold = safeInt(jGroup.getPidThreshold());
3989
3990         Vector<SequenceI> seqs = new Vector<>();
3991
3992         for (int s = 0; s < jGroup.getSeq().size(); s++)
3993         {
3994           String seqId = jGroup.getSeq().get(s);
3995           SequenceI ts = seqRefIds.get(seqId);
3996
3997           if (ts != null)
3998           {
3999             seqs.addElement(ts);
4000           }
4001         }
4002
4003         if (seqs.size() < 1)
4004         {
4005           continue;
4006         }
4007
4008         SequenceGroup sg = new SequenceGroup(seqs, jGroup.getName(), cs,
4009                 safeBoolean(jGroup.isDisplayBoxes()),
4010                 safeBoolean(jGroup.isDisplayText()),
4011                 safeBoolean(jGroup.isColourText()),
4012                 safeInt(jGroup.getStart()), safeInt(jGroup.getEnd()));
4013         sg.getGroupColourScheme().setThreshold(pidThreshold, true);
4014         sg.getGroupColourScheme()
4015                 .setConservationInc(safeInt(jGroup.getConsThreshold()));
4016         sg.setOutlineColour(new Color(safeInt(jGroup.getOutlineColour())));
4017
4018         sg.textColour = new Color(safeInt(jGroup.getTextCol1()));
4019         sg.textColour2 = new Color(safeInt(jGroup.getTextCol2()));
4020         sg.setShowNonconserved(safeBoolean(jGroup.isShowUnconserved()));
4021         sg.thresholdTextColour = safeInt(jGroup.getTextColThreshold());
4022         // attributes with a default in the schema are never null
4023           sg.setShowConsensusHistogram(jGroup.isShowConsensusHistogram());
4024           sg.setshowSequenceLogo(jGroup.isShowSequenceLogo());
4025           sg.setNormaliseSequenceLogo(jGroup.isNormaliseSequenceLogo());
4026         sg.setIgnoreGapsConsensus(jGroup.isIgnoreGapsinConsensus());
4027         if (jGroup.getConsThreshold() != null
4028                 && jGroup.getConsThreshold().intValue() != 0)
4029         {
4030           Conservation c = new Conservation("All", sg.getSequences(null), 0,
4031                   sg.getWidth() - 1);
4032           c.calculate();
4033           c.verdict(false, 25);
4034           sg.cs.setConservation(c);
4035         }
4036
4037         if (jGroup.getId() != null && groupAnnotRefs.size() > 0)
4038         {
4039           // re-instate unique group/annotation row reference
4040           List<AlignmentAnnotation> jaal = groupAnnotRefs
4041                   .get(jGroup.getId());
4042           if (jaal != null)
4043           {
4044             for (AlignmentAnnotation jaa : jaal)
4045             {
4046               jaa.groupRef = sg;
4047               if (jaa.autoCalculated)
4048               {
4049                 // match up and try to set group autocalc alignment row for this
4050                 // annotation
4051                 if (jaa.label.startsWith("Consensus for "))
4052                 {
4053                   sg.setConsensus(jaa);
4054                 }
4055                 // match up and try to set group autocalc alignment row for this
4056                 // annotation
4057                 if (jaa.label.startsWith("Conservation for "))
4058                 {
4059                   sg.setConservationRow(jaa);
4060                 }
4061               }
4062             }
4063           }
4064         }
4065         al.addGroup(sg);
4066         if (addAnnotSchemeGroup)
4067         {
4068           // reconstruct the annotation colourscheme
4069           sg.setColourScheme(constructAnnotationColour(
4070                   jGroup.getAnnotationColours(), null, al, jalviewModel, false));
4071         }
4072       }
4073 //      Platform.timeCheck("Jalview2XML.loadFromObject-groups",
4074 //              Platform.TIME_MARK);
4075     }
4076     if (view == null)
4077     {
4078       // only dataset in this model, so just return.
4079       return null;
4080     }
4081     // ///////////////////////////////
4082     // LOAD VIEWPORT
4083
4084     // now check to see if we really need to create a new viewport.
4085     if (multipleView && viewportsAdded.size() == 0)
4086     {
4087       // We recovered an alignment for which a viewport already exists.
4088       // TODO: fix up any settings necessary for overlaying stored state onto
4089       // state recovered from another document. (may not be necessary).
4090       // we may need a binding from a viewport in memory to one recovered from
4091       // XML.
4092       // and then recover its containing af to allow the settings to be applied.
4093       // TODO: fix for vamsas demo
4094       System.err.println(
4095               "About to recover a viewport for existing alignment: Sequence set ID is "
4096                       + uniqueSeqSetId);
4097       Object seqsetobj = retrieveExistingObj(uniqueSeqSetId);
4098       if (seqsetobj != null)
4099       {
4100         if (seqsetobj instanceof String)
4101         {
4102           uniqueSeqSetId = (String) seqsetobj;
4103           System.err.println(
4104                   "Recovered extant sequence set ID mapping for ID : New Sequence set ID is "
4105                           + uniqueSeqSetId);
4106         }
4107         else
4108         {
4109           System.err.println(
4110                   "Warning : Collision between sequence set ID string and existing jalview object mapping.");
4111         }
4112
4113       }
4114 //      Platform.timeCheck("Jalview2XML.loadFromObject-viewport",
4115 //              Platform.TIME_MARK);
4116     }
4117     /**
4118      * indicate that annotation colours are applied across all groups (pre
4119      * Jalview 2.8.1 behaviour)
4120      */
4121     boolean doGroupAnnColour = Jalview2XML.isVersionStringLaterThan("2.8.1",
4122             jalviewModel.getVersion());
4123
4124     AlignFrame af = null;
4125     AlignmentPanel ap = null;
4126     AlignViewport av = null;
4127     if (viewId != null)
4128     {
4129       // Check to see if this alignment already has a view id == viewId
4130       jalview.gui.AlignmentPanel views[] = Desktop
4131               .getAlignmentPanels(uniqueSeqSetId);
4132       if (views != null && views.length > 0)
4133       {
4134         for (int v = 0; v < views.length; v++)
4135         {
4136           ap = views[v];
4137           av = ap.av;
4138           if (av.getViewId().equalsIgnoreCase(viewId))
4139           {
4140             // recover the existing alignpanel, alignframe, viewport
4141             af = ap.alignFrame;
4142             break;
4143             // TODO: could even skip resetting view settings if we don't want to
4144             // change the local settings from other jalview processes
4145           }
4146         }
4147       }
4148     }
4149
4150     if (af == null)
4151     {
4152       af = loadViewport(file, jseqs, hiddenSeqs, al, jalviewModel, view,
4153               uniqueSeqSetId, viewId, autoAlan);
4154       av = af.getViewport();
4155       // note that this only retrieves the most recently accessed
4156       // tab of an AlignFrame.
4157       ap = af.alignPanel;
4158     }
4159
4160     /*
4161      * Load any trees, PDB structures and viewers
4162      * 
4163      * Not done if flag is false (when this method is used for New View)
4164      */
4165     final AlignFrame af0 = af;
4166     final AlignViewport av0 = av;
4167     final AlignmentPanel ap0 = ap;
4168 //    Platform.timeCheck("Jalview2XML.loadFromObject-beforetree",
4169 //            Platform.TIME_MARK);
4170     if (loadTreesAndStructures)
4171     {
4172       if (!jalviewModel.getTree().isEmpty())
4173       {
4174         SwingUtilities.invokeLater(new Runnable()
4175         {
4176           @Override
4177           public void run()
4178           {
4179 //            Platform.timeCheck(null, Platform.TIME_MARK);
4180             loadTrees(jalviewModel, view, af0, av0, ap0);
4181 //            Platform.timeCheck("Jalview2XML.loadTrees", Platform.TIME_MARK);
4182           }
4183         });
4184       }
4185       if (!jalviewModel.getPcaViewer().isEmpty())
4186       {
4187         SwingUtilities.invokeLater(new Runnable()
4188         {
4189           @Override
4190           public void run()
4191           {
4192 //            Platform.timeCheck(null, Platform.TIME_MARK);
4193             loadPCAViewers(jalviewModel, ap0);
4194 //            Platform.timeCheck("Jalview2XML.loadPCA", Platform.TIME_MARK);
4195           }
4196         });
4197       }
4198       SwingUtilities.invokeLater(new Runnable()
4199       {
4200         @Override
4201         public void run()
4202         {
4203 //          Platform.timeCheck(null, Platform.TIME_MARK);
4204           loadPDBStructures(jprovider, jseqs, af0, ap0);
4205 //          Platform.timeCheck("Jalview2XML.loadPDB", Platform.TIME_MARK);
4206         }
4207       });
4208       SwingUtilities.invokeLater(new Runnable()
4209       {
4210         @Override
4211         public void run()
4212         {
4213           loadRnaViewers(jprovider, jseqs, ap0);
4214         }
4215       });
4216     }
4217     // and finally return.
4218     // but do not set holdRepaint true just yet, because this could be the
4219     // initial frame with just its dataset.
4220     return af;
4221   }
4222
4223   /**
4224    * Instantiate and link any saved RNA (Varna) viewers. The state of the Varna
4225    * panel is restored from separate jar entries, two (gapped and trimmed) per
4226    * sequence and secondary structure.
4227    * 
4228    * Currently each viewer shows just one sequence and structure (gapped and
4229    * trimmed), however this method is designed to support multiple sequences or
4230    * structures in viewers if wanted in future.
4231    * 
4232    * @param jprovider
4233    * @param jseqs
4234    * @param ap
4235    */
4236   protected void loadRnaViewers(jarInputStreamProvider jprovider,
4237           List<JSeq> jseqs, AlignmentPanel ap)
4238   {
4239     /*
4240      * scan the sequences for references to viewers; create each one the first
4241      * time it is referenced, add Rna models to existing viewers
4242      */
4243     for (JSeq jseq : jseqs)
4244     {
4245       for (int i = 0; i < jseq.getRnaViewer().size(); i++)
4246       {
4247         RnaViewer viewer = jseq.getRnaViewer().get(i);
4248         AppVarna appVarna = findOrCreateVarnaViewer(viewer, uniqueSetSuffix,
4249                 ap);
4250
4251         for (int j = 0; j < viewer.getSecondaryStructure().size(); j++)
4252         {
4253           SecondaryStructure ss = viewer.getSecondaryStructure().get(j);
4254           SequenceI seq = seqRefIds.get(jseq.getId());
4255           AlignmentAnnotation ann = this.annotationIds
4256                   .get(ss.getAnnotationId());
4257
4258           /*
4259            * add the structure to the Varna display (with session state copied
4260            * from the jar to a temporary file)
4261            */
4262           boolean gapped = safeBoolean(ss.isGapped());
4263           String rnaTitle = ss.getTitle();
4264           String sessionState = ss.getViewerState();
4265           String tempStateFile = copyJarEntry(jprovider, sessionState,
4266                   "varna", null);
4267           RnaModel rna = new RnaModel(rnaTitle, ann, seq, null, gapped);
4268           appVarna.addModelSession(rna, rnaTitle, tempStateFile);
4269         }
4270         appVarna.setInitialSelection(safeInt(viewer.getSelectedRna()));
4271       }
4272     }
4273   }
4274
4275   /**
4276    * Locate and return an already instantiated matching AppVarna, or create one
4277    * if not found
4278    * 
4279    * @param viewer
4280    * @param viewIdSuffix
4281    * @param ap
4282    * @return
4283    */
4284   protected AppVarna findOrCreateVarnaViewer(RnaViewer viewer,
4285           String viewIdSuffix, AlignmentPanel ap)
4286   {
4287     /*
4288      * on each load a suffix is appended to the saved viewId, to avoid conflicts
4289      * if load is repeated
4290      */
4291     String postLoadId = viewer.getViewId() + viewIdSuffix;
4292     for (JInternalFrame frame : getAllFrames())
4293     {
4294       if (frame instanceof AppVarna)
4295       {
4296         AppVarna varna = (AppVarna) frame;
4297         if (postLoadId.equals(varna.getViewId()))
4298         {
4299           // this viewer is already instantiated
4300           // could in future here add ap as another 'parent' of the
4301           // AppVarna window; currently just 1-to-many
4302           return varna;
4303         }
4304       }
4305     }
4306
4307     /*
4308      * viewer not found - make it
4309      */
4310     RnaViewerModel model = new RnaViewerModel(postLoadId, viewer.getTitle(),
4311             safeInt(viewer.getXpos()), safeInt(viewer.getYpos()),
4312             safeInt(viewer.getWidth()), safeInt(viewer.getHeight()),
4313             safeInt(viewer.getDividerLocation()));
4314     AppVarna varna = new AppVarna(model, ap);
4315
4316     return varna;
4317   }
4318
4319   /**
4320    * Load any saved trees
4321    * 
4322    * @param jm
4323    * @param view
4324    * @param af
4325    * @param av
4326    * @param ap
4327    */
4328   protected void loadTrees(JalviewModel jm, Viewport view,
4329           AlignFrame af, AlignViewport av, AlignmentPanel ap)
4330   {
4331     // TODO result of automated refactoring - are all these parameters needed?
4332     try
4333     {
4334       for (int t = 0; t < jm.getTree().size(); t++)
4335       {
4336
4337         Tree tree = jm.getTree().get(t);
4338
4339         TreePanel tp = (TreePanel) retrieveExistingObj(tree.getId());
4340         if (tp == null)
4341         {
4342           tp = af.showNewickTree(new NewickFile(tree.getNewick()),
4343                   tree.getTitle(), safeInt(tree.getWidth()),
4344                   safeInt(tree.getHeight()), safeInt(tree.getXpos()),
4345                   safeInt(tree.getYpos()));
4346           if (tree.getId() != null)
4347           {
4348             // perhaps bind the tree id to something ?
4349           }
4350         }
4351         else
4352         {
4353           // update local tree attributes ?
4354           // TODO: should check if tp has been manipulated by user - if so its
4355           // settings shouldn't be modified
4356           tp.setTitle(tree.getTitle());
4357           tp.setBounds(new Rectangle(safeInt(tree.getXpos()),
4358                   safeInt(tree.getYpos()), safeInt(tree.getWidth()),
4359                   safeInt(tree.getHeight())));
4360           tp.setViewport(av); // af.viewport;
4361           // TODO: verify 'associate with all views' works still
4362           tp.getTreeCanvas().setViewport(av); // af.viewport;
4363           tp.getTreeCanvas().setAssociatedPanel(ap); // af.alignPanel;
4364         }
4365         tp.getTreeCanvas().setApplyToAllViews(tree.isLinkToAllViews());
4366         if (tp == null)
4367         {
4368           warn("There was a problem recovering stored Newick tree: \n"
4369                   + tree.getNewick());
4370           continue;
4371         }
4372
4373         tp.fitToWindow.setState(safeBoolean(tree.isFitToWindow()));
4374         tp.fitToWindow_actionPerformed(null);
4375
4376         if (tree.getFontName() != null)
4377         {
4378           tp.setTreeFont(
4379                   new Font(tree.getFontName(), safeInt(tree.getFontStyle()),
4380                           safeInt(tree.getFontSize())));
4381         }
4382         else
4383         {
4384           tp.setTreeFont(
4385                   new Font(view.getFontName(), safeInt(view.getFontStyle()),
4386                           safeInt(view.getFontSize())));
4387         }
4388
4389         tp.showPlaceholders(safeBoolean(tree.isMarkUnlinked()));
4390         tp.showBootstrap(safeBoolean(tree.isShowBootstrap()));
4391         tp.showDistances(safeBoolean(tree.isShowDistances()));
4392
4393         tp.getTreeCanvas().setThreshold(safeFloat(tree.getThreshold()));
4394
4395         if (safeBoolean(tree.isCurrentTree()))
4396         {
4397           af.getViewport().setCurrentTree(tp.getTree());
4398         }
4399       }
4400
4401     } catch (Exception ex)
4402     {
4403       ex.printStackTrace();
4404     }
4405   }
4406
4407   /**
4408    * Load and link any saved structure viewers.
4409    * 
4410    * @param jprovider
4411    * @param jseqs
4412    * @param af
4413    * @param ap
4414    */
4415   protected void loadPDBStructures(jarInputStreamProvider jprovider,
4416           List<JSeq> jseqs, AlignFrame af, AlignmentPanel ap)
4417   {
4418     /*
4419      * Run through all PDB ids on the alignment, and collect mappings between
4420      * distinct view ids and all sequences referring to that view.
4421      */
4422     Map<String, StructureViewerModel> structureViewers = new LinkedHashMap<>();
4423
4424     for (int i = 0; i < jseqs.size(); i++)
4425     {
4426       JSeq jseq = jseqs.get(i);
4427       if (jseq.getPdbids().size() > 0)
4428       {
4429         List<Pdbids> ids = jseq.getPdbids();
4430         for (int p = 0; p < ids.size(); p++)
4431         {
4432           Pdbids pdbid = ids.get(p);
4433           final int structureStateCount = pdbid.getStructureState().size();
4434           for (int s = 0; s < structureStateCount; s++)
4435           {
4436             // check to see if we haven't already created this structure view
4437             final StructureState structureState = pdbid
4438                     .getStructureState().get(s);
4439             String sviewid = (structureState.getViewId() == null) ? null
4440                     : structureState.getViewId() + uniqueSetSuffix;
4441             jalview.datamodel.PDBEntry jpdb = new jalview.datamodel.PDBEntry();
4442             // Originally : pdbid.getFile()
4443             // : TODO: verify external PDB file recovery still works in normal
4444             // jalview project load
4445             jpdb.setFile(
4446                     loadPDBFile(jprovider, pdbid.getId(), pdbid.getFile()));
4447             jpdb.setId(pdbid.getId());
4448
4449             int x = safeInt(structureState.getXpos());
4450             int y = safeInt(structureState.getYpos());
4451             int width = safeInt(structureState.getWidth());
4452             int height = safeInt(structureState.getHeight());
4453
4454             // Probably don't need to do this anymore...
4455             // Desktop.getDesktop().getComponentAt(x, y);
4456             // TODO: NOW: check that this recovers the PDB file correctly.
4457             String pdbFile = loadPDBFile(jprovider, pdbid.getId(),
4458                     pdbid.getFile());
4459             jalview.datamodel.SequenceI seq = seqRefIds
4460                     .get(jseq.getId() + "");
4461             if (sviewid == null)
4462             {
4463               sviewid = "_jalview_pre2_4_" + x + "," + y + "," + width + ","
4464                       + height;
4465             }
4466             if (!structureViewers.containsKey(sviewid))
4467             {
4468               structureViewers.put(sviewid,
4469                       new StructureViewerModel(x, y, width, height, false,
4470                               false, true, structureState.getViewId(),
4471                               structureState.getType()));
4472               // Legacy pre-2.7 conversion JAL-823 :
4473               // do not assume any view has to be linked for colour by
4474               // sequence
4475             }
4476
4477             // assemble String[] { pdb files }, String[] { id for each
4478             // file }, orig_fileloc, SequenceI[][] {{ seqs_file 1 }, {
4479             // seqs_file 2}, boolean[] {
4480             // linkAlignPanel,superposeWithAlignpanel}} from hash
4481             StructureViewerModel jmoldat = structureViewers.get(sviewid);
4482             jmoldat.setAlignWithPanel(jmoldat.isAlignWithPanel()
4483                     || structureState.isAlignwithAlignPanel());
4484
4485             /*
4486              * Default colour by linked panel to false if not specified (e.g.
4487              * for pre-2.7 projects)
4488              */
4489             boolean colourWithAlignPanel = jmoldat.isColourWithAlignPanel();
4490             colourWithAlignPanel |= structureState.isColourwithAlignPanel();
4491             jmoldat.setColourWithAlignPanel(colourWithAlignPanel);
4492
4493             /*
4494              * Default colour by viewer to true if not specified (e.g. for
4495              * pre-2.7 projects)
4496              */
4497             boolean colourByViewer = jmoldat.isColourByViewer();
4498             colourByViewer &= structureState.isColourByJmol();
4499             jmoldat.setColourByViewer(colourByViewer);
4500
4501             if (jmoldat.getStateData().length() < structureState
4502                     .getValue()/*Content()*/.length())
4503             {
4504               jmoldat.setStateData(structureState.getValue());// Content());
4505             }
4506             if (pdbid.getFile() != null)
4507             {
4508               File mapkey = new File(pdbid.getFile());
4509               StructureData seqstrmaps = jmoldat.getFileData().get(mapkey);
4510               if (seqstrmaps == null)
4511               {
4512                 jmoldat.getFileData().put(mapkey,
4513                         seqstrmaps = jmoldat.new StructureData(pdbFile,
4514                                 pdbid.getId()));
4515               }
4516               if (!seqstrmaps.getSeqList().contains(seq))
4517               {
4518                 seqstrmaps.getSeqList().add(seq);
4519                 // TODO and chains?
4520               }
4521             }
4522             else
4523             {
4524               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");
4525               warn(errorMessage);
4526             }
4527           }
4528         }
4529       }
4530     }
4531     // Instantiate the associated structure views
4532     for (Entry<String, StructureViewerModel> entry : structureViewers
4533             .entrySet())
4534     {
4535       try
4536       {
4537         createOrLinkStructureViewer(entry, af, ap, jprovider);
4538       } catch (Exception e)
4539       {
4540         System.err.println(
4541                 "Error loading structure viewer: " + e.getMessage());
4542         // failed - try the next one
4543       }
4544     }
4545   }
4546
4547   /**
4548    * 
4549    * @param viewerData
4550    * @param af
4551    * @param ap
4552    * @param jprovider
4553    */
4554   protected void createOrLinkStructureViewer(
4555           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
4556           AlignmentPanel ap, jarInputStreamProvider jprovider)
4557   {
4558     final StructureViewerModel stateData = viewerData.getValue();
4559
4560     /*
4561      * Search for any viewer windows already open from other alignment views
4562      * that exactly match the stored structure state
4563      */
4564     StructureViewerBase comp = findMatchingViewer(viewerData);
4565
4566     if (comp != null)
4567     {
4568       linkStructureViewer(ap, comp, stateData);
4569       return;
4570     }
4571
4572     /*
4573      * From 2.9: stateData.type contains JMOL or CHIMERA, data is in jar entry
4574      * "viewer_"+stateData.viewId
4575      */
4576     if (ViewerType.CHIMERA.toString().equals(stateData.getType()))
4577     {
4578       createChimeraViewer(viewerData, af, jprovider);
4579     }
4580     else
4581     {
4582       /*
4583        * else Jmol (if pre-2.9, stateData contains JMOL state string)
4584        */
4585       createJmolViewer(viewerData, af, jprovider);
4586     }
4587   }
4588
4589   /**
4590    * Create a new Chimera viewer.
4591    * 
4592    * @param data
4593    * @param af
4594    * @param jprovider
4595    */
4596   protected void createChimeraViewer(
4597           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
4598           jarInputStreamProvider jprovider)
4599   {
4600     StructureViewerModel data = viewerData.getValue();
4601     String chimeraSessionFile = data.getStateData();
4602
4603     /*
4604      * Copy Chimera session from jar entry "viewer_"+viewId to a temporary file
4605      * 
4606      * NB this is the 'saved' viewId as in the project file XML, _not_ the
4607      * 'uniquified' sviewid used to reconstruct the viewer here
4608      */
4609     String viewerJarEntryName = getViewerJarEntryName(data.getViewId());
4610     chimeraSessionFile = copyJarEntry(jprovider, viewerJarEntryName,
4611             "chimera", null);
4612
4613     Set<Entry<File, StructureData>> fileData = data.getFileData()
4614             .entrySet();
4615     List<PDBEntry> pdbs = new ArrayList<>();
4616     List<SequenceI[]> allseqs = new ArrayList<>();
4617     for (Entry<File, StructureData> pdb : fileData)
4618     {
4619       String filePath = pdb.getValue().getFilePath();
4620       String pdbId = pdb.getValue().getPdbId();
4621       // pdbs.add(new PDBEntry(filePath, pdbId));
4622       pdbs.add(new PDBEntry(pdbId, null, PDBEntry.Type.PDB, filePath));
4623       final List<SequenceI> seqList = pdb.getValue().getSeqList();
4624       SequenceI[] seqs = seqList.toArray(new SequenceI[seqList.size()]);
4625       allseqs.add(seqs);
4626     }
4627
4628     boolean colourByChimera = data.isColourByViewer();
4629     boolean colourBySequence = data.isColourWithAlignPanel();
4630
4631     // TODO use StructureViewer as a factory here, see JAL-1761
4632     final PDBEntry[] pdbArray = pdbs.toArray(new PDBEntry[pdbs.size()]);
4633     final SequenceI[][] seqsArray = allseqs
4634             .toArray(new SequenceI[allseqs.size()][]);
4635     String newViewId = viewerData.getKey();
4636
4637     ChimeraViewFrame cvf = new ChimeraViewFrame(chimeraSessionFile,
4638             af.alignPanel, pdbArray, seqsArray, colourByChimera,
4639             colourBySequence, newViewId);
4640     cvf.setSize(data.getWidth(), data.getHeight());
4641     cvf.setLocation(data.getX(), data.getY());
4642   }
4643
4644   /**
4645    * Create a new Jmol window. First parse the Jmol state to translate filenames
4646    * loaded into the view, and record the order in which files are shown in the
4647    * Jmol view, so we can add the sequence mappings in same order.
4648    * 
4649    * @param viewerData
4650    * @param af
4651    * @param jprovider
4652    */
4653   protected void createJmolViewer(
4654           final Entry<String, StructureViewerModel> viewerData,
4655           AlignFrame af, jarInputStreamProvider jprovider)
4656   {
4657     final StructureViewerModel svattrib = viewerData.getValue();
4658     String state = svattrib.getStateData();
4659
4660     /*
4661      * Pre-2.9: state element value is the Jmol state string
4662      * 
4663      * 2.9+: @type is "JMOL", state data is in a Jar file member named "viewer_"
4664      * + viewId
4665      */
4666     if (ViewerType.JMOL.toString().equals(svattrib.getType()))
4667     {
4668       state = readJarEntry(jprovider,
4669               getViewerJarEntryName(svattrib.getViewId()));
4670     }
4671
4672     List<String> pdbfilenames = new ArrayList<>();
4673     List<SequenceI[]> seqmaps = new ArrayList<>();
4674     List<String> pdbids = new ArrayList<>();
4675     StringBuilder newFileLoc = new StringBuilder(64);
4676     int cp = 0, ncp, ecp;
4677     Map<File, StructureData> oldFiles = svattrib.getFileData();
4678     while ((ncp = state.indexOf("load ", cp)) > -1)
4679     {
4680       do
4681       {
4682         // look for next filename in load statement
4683         newFileLoc.append(state.substring(cp,
4684                 ncp = (state.indexOf("\"", ncp + 1) + 1)));
4685         String oldfilenam = state.substring(ncp,
4686                 ecp = state.indexOf("\"", ncp));
4687         // recover the new mapping data for this old filename
4688         // have to normalize filename - since Jmol and jalview do
4689         // filename
4690         // translation differently.
4691         StructureData filedat = oldFiles.get(new File(oldfilenam));
4692         if (filedat == null)
4693         {
4694           String reformatedOldFilename = oldfilenam.replaceAll("/", "\\\\");
4695           filedat = oldFiles.get(new File(reformatedOldFilename));
4696         }
4697         newFileLoc.append(Platform.escapeString(filedat.getFilePath()));
4698         pdbfilenames.add(filedat.getFilePath());
4699         pdbids.add(filedat.getPdbId());
4700         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
4701         newFileLoc.append("\"");
4702         cp = ecp + 1; // advance beyond last \" and set cursor so we can
4703                       // look for next file statement.
4704       } while ((ncp = state.indexOf("/*file*/", cp)) > -1);
4705     }
4706     if (cp > 0)
4707     {
4708       // just append rest of state
4709       newFileLoc.append(state.substring(cp));
4710     }
4711     else
4712     {
4713       System.err.print("Ignoring incomplete Jmol state for PDB ids: ");
4714       newFileLoc = new StringBuilder(state);
4715       newFileLoc.append("; load append ");
4716       for (File id : oldFiles.keySet())
4717       {
4718         // add this and any other pdb files that should be present in
4719         // the viewer
4720         StructureData filedat = oldFiles.get(id);
4721         newFileLoc.append(filedat.getFilePath());
4722         pdbfilenames.add(filedat.getFilePath());
4723         pdbids.add(filedat.getPdbId());
4724         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
4725         newFileLoc.append(" \"");
4726         newFileLoc.append(filedat.getFilePath());
4727         newFileLoc.append("\"");
4728
4729       }
4730       newFileLoc.append(";");
4731     }
4732
4733     if (newFileLoc.length() == 0)
4734     {
4735       return;
4736     }
4737     int histbug = newFileLoc.indexOf("history = ");
4738     if (histbug > -1)
4739     {
4740       /*
4741        * change "history = [true|false];" to "history = [1|0];"
4742        */
4743       histbug += 10;
4744       int diff = histbug == -1 ? -1 : newFileLoc.indexOf(";", histbug);
4745       String val = (diff == -1) ? null
4746               : newFileLoc.substring(histbug, diff);
4747       if (val != null && val.length() >= 4)
4748       {
4749         if (val.contains("e")) // eh? what can it be?
4750         {
4751           if (val.trim().equals("true"))
4752           {
4753             val = "1";
4754           }
4755           else
4756           {
4757             val = "0";
4758           }
4759           newFileLoc.replace(histbug, diff, val);
4760         }
4761       }
4762     }
4763
4764     final String[] pdbf = pdbfilenames
4765             .toArray(new String[pdbfilenames.size()]);
4766     final String[] id = pdbids.toArray(new String[pdbids.size()]);
4767     final SequenceI[][] sq = seqmaps
4768             .toArray(new SequenceI[seqmaps.size()][]);
4769     final String fileloc = newFileLoc.toString();
4770     final String sviewid = viewerData.getKey();
4771     final AlignFrame alf = af;
4772     final Rectangle rect = new Rectangle(svattrib.getX(), svattrib.getY(),
4773             svattrib.getWidth(), svattrib.getHeight());
4774     // try
4775     // {
4776       javax.swing.SwingUtilities.invokeLater(new Runnable()
4777       {
4778         @Override
4779         public void run()
4780         {
4781           JalviewStructureDisplayI sview = null;
4782           try
4783           {
4784             sview = new StructureViewer(
4785                     alf.alignPanel.getStructureSelectionManager())
4786                             .createView(StructureViewer.ViewerType.JMOL,
4787                                     pdbf, id, sq, alf.alignPanel, svattrib,
4788                                     fileloc, rect, sviewid);
4789             addNewStructureViewer(sview);
4790           } catch (OutOfMemoryError ex)
4791           {
4792             new OOMWarning("restoring structure view for PDB id " + id,
4793                     (OutOfMemoryError) ex.getCause());
4794             if (sview != null && sview.isVisible())
4795             {
4796               sview.closeViewer(false);
4797               sview.setVisible(false);
4798               sview.dispose();
4799             }
4800           }
4801         }
4802       });
4803     // } catch (InvocationTargetException ex)
4804     // {
4805     // warn("Unexpected error when opening Jmol view.", ex);
4806     //
4807     // } catch (InterruptedException e)
4808     // {
4809     // // e.printStackTrace();
4810     // }
4811
4812   }
4813
4814   /**
4815    * Generates a name for the entry in the project jar file to hold state
4816    * information for a structure viewer
4817    * 
4818    * @param viewId
4819    * @return
4820    */
4821   protected String getViewerJarEntryName(String viewId)
4822   {
4823     return VIEWER_PREFIX + viewId;
4824   }
4825
4826   /**
4827    * Returns any open frame that matches given structure viewer data. The match
4828    * is based on the unique viewId, or (for older project versions) the frame's
4829    * geometry.
4830    * 
4831    * @param viewerData
4832    * @return
4833    */
4834   protected StructureViewerBase findMatchingViewer(
4835           Entry<String, StructureViewerModel> viewerData)
4836   {
4837     final String sviewid = viewerData.getKey();
4838     final StructureViewerModel svattrib = viewerData.getValue();
4839     StructureViewerBase comp = null;
4840     JInternalFrame[] frames = getAllFrames();
4841     for (JInternalFrame frame : frames)
4842     {
4843       if (frame instanceof StructureViewerBase)
4844       {
4845         /*
4846          * Post jalview 2.4 schema includes structure view id
4847          */
4848         if (sviewid != null && ((StructureViewerBase) frame).getViewId()
4849                 .equals(sviewid))
4850         {
4851           comp = (StructureViewerBase) frame;
4852           break; // break added in 2.9
4853         }
4854         /*
4855          * Otherwise test for matching position and size of viewer frame
4856          */
4857         else if (frame.getX() == svattrib.getX()
4858                 && frame.getY() == svattrib.getY()
4859                 && frame.getHeight() == svattrib.getHeight()
4860                 && frame.getWidth() == svattrib.getWidth())
4861         {
4862           comp = (StructureViewerBase) frame;
4863           // no break in faint hope of an exact match on viewId
4864         }
4865       }
4866     }
4867     return comp;
4868   }
4869
4870   /**
4871    * Link an AlignmentPanel to an existing structure viewer.
4872    * 
4873    * @param ap
4874    * @param viewer
4875    * @param oldFiles
4876    * @param useinViewerSuperpos
4877    * @param usetoColourbyseq
4878    * @param viewerColouring
4879    */
4880   protected void linkStructureViewer(AlignmentPanel ap,
4881           StructureViewerBase viewer, StructureViewerModel stateData)
4882   {
4883     // NOTE: if the jalview project is part of a shared session then
4884     // view synchronization should/could be done here.
4885
4886     final boolean useinViewerSuperpos = stateData.isAlignWithPanel();
4887     final boolean usetoColourbyseq = stateData.isColourWithAlignPanel();
4888     final boolean viewerColouring = stateData.isColourByViewer();
4889     Map<File, StructureData> oldFiles = stateData.getFileData();
4890
4891     /*
4892      * Add mapping for sequences in this view to an already open viewer
4893      */
4894     final AAStructureBindingModel binding = viewer.getBinding();
4895     for (File id : oldFiles.keySet())
4896     {
4897       // add this and any other pdb files that should be present in the
4898       // viewer
4899       StructureData filedat = oldFiles.get(id);
4900       String pdbFile = filedat.getFilePath();
4901       SequenceI[] seq = filedat.getSeqList().toArray(new SequenceI[0]);
4902       binding.getSsm().setMapping(seq, null, pdbFile, DataSourceType.FILE,
4903               null);
4904       binding.addSequenceForStructFile(pdbFile, seq);
4905     }
4906     // and add the AlignmentPanel's reference to the view panel
4907     viewer.addAlignmentPanel(ap);
4908     if (useinViewerSuperpos)
4909     {
4910       viewer.useAlignmentPanelForSuperposition(ap);
4911     }
4912     else
4913     {
4914       viewer.excludeAlignmentPanelForSuperposition(ap);
4915     }
4916     if (usetoColourbyseq)
4917     {
4918       viewer.useAlignmentPanelForColourbyseq(ap, !viewerColouring);
4919     }
4920     else
4921     {
4922       viewer.excludeAlignmentPanelForColourbyseq(ap);
4923     }
4924   }
4925
4926   /**
4927    * Get all frames within the Desktop.
4928    * 
4929    * @return
4930    */
4931   protected JInternalFrame[] getAllFrames()
4932   {
4933     JInternalFrame[] frames = null;
4934     // TODO is this necessary - is it safe - risk of hanging?
4935     do
4936     {
4937       try
4938       {
4939         frames = Desktop.getDesktopPane().getAllFrames();
4940       } catch (ArrayIndexOutOfBoundsException e)
4941       {
4942         // occasional No such child exceptions are thrown here...
4943         try
4944         {
4945           Thread.sleep(10);
4946         } catch (InterruptedException f)
4947         {
4948         }
4949       }
4950     } while (frames == null);
4951     return frames;
4952   }
4953
4954   /**
4955    * Answers true if 'version' is equal to or later than 'supported', where each
4956    * is formatted as major/minor versions like "2.8.3" or "2.3.4b1" for bugfix
4957    * changes. Development and test values for 'version' are leniently treated
4958    * i.e. answer true.
4959    * 
4960    * @param supported
4961    *          - minimum version we are comparing against
4962    * @param version
4963    *          - version of data being processsed
4964    * @return
4965    */
4966   public static boolean isVersionStringLaterThan(String supported,
4967           String version)
4968   {
4969     if (supported == null || version == null
4970             || version.equalsIgnoreCase("DEVELOPMENT BUILD")
4971             || version.equalsIgnoreCase("Test")
4972             || version.equalsIgnoreCase("AUTOMATED BUILD"))
4973     {
4974       System.err.println("Assuming project file with "
4975               + (version == null ? "null" : version)
4976               + " is compatible with Jalview version " + supported);
4977       return true;
4978     }
4979     else
4980     {
4981       return StringUtils.compareVersions(version, supported, "b") >= 0;
4982     }
4983   }
4984
4985   Vector<JalviewStructureDisplayI> newStructureViewers = null;
4986
4987   protected void addNewStructureViewer(JalviewStructureDisplayI sview)
4988   {
4989     if (newStructureViewers != null)
4990     {
4991       sview.getBinding().setFinishedLoadingFromArchive(false);
4992       newStructureViewers.add(sview);
4993     }
4994   }
4995
4996   protected void setLoadingFinishedForNewStructureViewers()
4997   {
4998     if (newStructureViewers != null)
4999     {
5000       for (JalviewStructureDisplayI sview : newStructureViewers)
5001       {
5002         sview.getBinding().setFinishedLoadingFromArchive(true);
5003       }
5004       newStructureViewers.clear();
5005       newStructureViewers = null;
5006     }
5007   }
5008
5009   AlignFrame loadViewport(String fileName, List<JSeq> JSEQ,
5010           List<SequenceI> hiddenSeqs, AlignmentI al, JalviewModel jm,
5011           Viewport view, String uniqueSeqSetId, String viewId,
5012           List<JvAnnotRow> autoAlan)
5013   {
5014     AlignFrame af = null;
5015     af = new AlignFrame(al, safeInt(view.getWidth()),
5016             safeInt(view.getHeight()), uniqueSeqSetId, viewId)
5017     // {
5018     //
5019     // @Override
5020     // protected void processKeyEvent(java.awt.event.KeyEvent e) {
5021     // System.out.println("Jalview2XML AF " + e);
5022     // super.processKeyEvent(e);
5023     //
5024     // }
5025     //
5026     // }
5027     ;
5028     af.alignPanel.setHoldRepaint(true);
5029     af.setFileName(fileName, FileFormat.Jalview);
5030     af.setFileObject(jarFile); // BH 2019 JAL-3436
5031
5032     final AlignViewport viewport = af.getViewport();
5033     for (int i = 0; i < JSEQ.size(); i++)
5034     {
5035       int colour = safeInt(JSEQ.get(i).getColour());
5036       viewport.setSequenceColour(viewport.getAlignment().getSequenceAt(i),
5037               new Color(colour));
5038     }
5039
5040     if (al.hasSeqrep())
5041     {
5042       viewport.setColourByReferenceSeq(true);
5043       viewport.setDisplayReferenceSeq(true);
5044     }
5045
5046     viewport.setGatherViewsHere(safeBoolean(view.isGatheredViews()));
5047
5048     if (view.getSequenceSetId() != null)
5049     {
5050       AlignmentViewport av = viewportsAdded.get(uniqueSeqSetId);
5051
5052       viewport.setSequenceSetId(uniqueSeqSetId);
5053       if (av != null)
5054       {
5055         // propagate shared settings to this new view
5056         viewport.setHistoryList(av.getHistoryList());
5057         viewport.setRedoList(av.getRedoList());
5058       }
5059       else
5060       {
5061         viewportsAdded.put(uniqueSeqSetId, viewport);
5062       }
5063       // TODO: check if this method can be called repeatedly without
5064       // side-effects if alignpanel already registered.
5065       PaintRefresher.Register(af.alignPanel, uniqueSeqSetId);
5066     }
5067     // apply Hidden regions to view.
5068     if (hiddenSeqs != null)
5069     {
5070       for (int s = 0; s < JSEQ.size(); s++)
5071       {
5072         SequenceGroup hidden = new SequenceGroup();
5073         boolean isRepresentative = false;
5074         for (int r = 0; r < JSEQ.get(s).getHiddenSequences().size(); r++)
5075         {
5076           isRepresentative = true;
5077           SequenceI sequenceToHide = al
5078                   .getSequenceAt(JSEQ.get(s).getHiddenSequences().get(r));
5079           hidden.addSequence(sequenceToHide, false);
5080           // remove from hiddenSeqs list so we don't try to hide it twice
5081           hiddenSeqs.remove(sequenceToHide);
5082         }
5083         if (isRepresentative)
5084         {
5085           SequenceI representativeSequence = al.getSequenceAt(s);
5086           hidden.addSequence(representativeSequence, false);
5087           viewport.hideRepSequences(representativeSequence, hidden);
5088         }
5089       }
5090
5091       SequenceI[] hseqs = hiddenSeqs
5092               .toArray(new SequenceI[hiddenSeqs.size()]);
5093       viewport.hideSequence(hseqs);
5094
5095     }
5096     // recover view properties and display parameters
5097
5098     viewport.setShowAnnotation(safeBoolean(view.isShowAnnotation()));
5099     viewport.setAbovePIDThreshold(safeBoolean(view.isPidSelected()));
5100     final int pidThreshold = safeInt(view.getPidThreshold());
5101     viewport.setThreshold(pidThreshold);
5102
5103     viewport.setColourText(safeBoolean(view.isShowColourText()));
5104
5105     viewport.setConservationSelected(
5106             safeBoolean(view.isConservationSelected()));
5107     viewport.setIncrement(safeInt(view.getConsThreshold()));
5108     viewport.setShowJVSuffix(safeBoolean(view.isShowFullId()));
5109     viewport.setRightAlignIds(safeBoolean(view.isRightAlignIds()));
5110     viewport.setFont(new Font(view.getFontName(),
5111             safeInt(view.getFontStyle()), safeInt(view.getFontSize())),
5112             true);
5113     ViewStyleI vs = viewport.getViewStyle();
5114     vs.setScaleProteinAsCdna(view.isScaleProteinAsCdna());
5115     viewport.setViewStyle(vs);
5116     // TODO: allow custom charWidth/Heights to be restored by updating them
5117     // after setting font - which means set above to false
5118     viewport.setRenderGaps(safeBoolean(view.isRenderGaps()));
5119     viewport.setWrapAlignment(safeBoolean(view.isWrapAlignment()));
5120     viewport.setShowAnnotation(safeBoolean(view.isShowAnnotation()));
5121
5122     viewport.setShowBoxes(safeBoolean(view.isShowBoxes()));
5123
5124     viewport.setShowText(safeBoolean(view.isShowText()));
5125
5126     viewport.setTextColour(new Color(safeInt(view.getTextCol1())));
5127     viewport.setTextColour2(new Color(safeInt(view.getTextCol2())));
5128     viewport.setThresholdTextColour(safeInt(view.getTextColThreshold()));
5129     viewport.setShowUnconserved(view.isShowUnconserved());
5130     viewport.getRanges().setStartRes(safeInt(view.getStartRes()));
5131
5132     if (view.getViewName() != null)
5133     {
5134       viewport.setViewName(view.getViewName());
5135       af.setInitialTabVisible();
5136     }
5137     int x = safeInt(view.getXpos());
5138     int y = safeInt(view.getYpos());
5139     int w = safeInt(view.getWidth());
5140     int h = safeInt(view.getHeight());
5141     // // BH we cannot let the title bar go off the top
5142     // if (Platform.isJS())
5143     // {
5144     // x = Math.max(50 - w, x);
5145     // y = Math.max(0, y);
5146     // }
5147
5148     af.setBounds(x, y, w, h);
5149     // startSeq set in af.alignPanel.updateLayout below
5150     af.alignPanel.updateLayout();
5151     ColourSchemeI cs = null;
5152     // apply colourschemes
5153     if (view.getBgColour() != null)
5154     {
5155       if (view.getBgColour().startsWith("ucs"))
5156       {
5157         cs = getUserColourScheme(jm, view.getBgColour());
5158       }
5159       else if (view.getBgColour().startsWith("Annotation"))
5160       {
5161         AnnotationColourScheme viewAnnColour = view.getAnnotationColours();
5162         cs = constructAnnotationColour(viewAnnColour, af, al, jm, true);
5163
5164         // annpos
5165
5166       }
5167       else
5168       {
5169         cs = ColourSchemeProperty.getColourScheme(af.getViewport(), al,
5170                 view.getBgColour());
5171       }
5172     }
5173
5174     /*
5175      * turn off 'alignment colour applies to all groups'
5176      * while restoring global colour scheme
5177      */
5178     viewport.setColourAppliesToAllGroups(false);
5179     viewport.setGlobalColourScheme(cs);
5180     viewport.getResidueShading().setThreshold(pidThreshold,
5181             view.isIgnoreGapsinConsensus());
5182     viewport.getResidueShading()
5183             .setConsensus(viewport.getSequenceConsensusHash());
5184     if (safeBoolean(view.isConservationSelected()) && cs != null)
5185     {
5186       viewport.getResidueShading()
5187               .setConservationInc(safeInt(view.getConsThreshold()));
5188     }
5189     af.changeColour(cs);
5190     viewport.setColourAppliesToAllGroups(true);
5191
5192     viewport.setShowSequenceFeatures(
5193             safeBoolean(view.isShowSequenceFeatures()));
5194
5195     viewport.setCentreColumnLabels(view.isCentreColumnLabels());
5196     viewport.setIgnoreGapsConsensus(view.isIgnoreGapsinConsensus(), null);
5197     viewport.setFollowHighlight(view.isFollowHighlight());
5198     viewport.followSelection = view.isFollowSelection();
5199     viewport.setShowConsensusHistogram(view.isShowConsensusHistogram());
5200     viewport.setShowSequenceLogo(view.isShowSequenceLogo());
5201     viewport.setNormaliseSequenceLogo(view.isNormaliseSequenceLogo());
5202     viewport.setShowDBRefs(safeBoolean(view.isShowDbRefTooltip()));
5203     viewport.setShowNPFeats(safeBoolean(view.isShowNPfeatureTooltip()));
5204     viewport.setShowGroupConsensus(view.isShowGroupConsensus());
5205     viewport.setShowGroupConservation(view.isShowGroupConservation());
5206
5207     // recover feature settings
5208     if (jm.getFeatureSettings() != null)
5209     {
5210       FeatureRenderer fr = af.alignPanel.getSeqPanel().seqCanvas
5211               .getFeatureRenderer();
5212       FeaturesDisplayed fdi;
5213       viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
5214       String[] renderOrder = new String[jm.getFeatureSettings().getSetting()
5215               .size()];
5216       Map<String, FeatureColourI> featureColours = new Hashtable<>();
5217       Map<String, Float> featureOrder = new Hashtable<>();
5218
5219       for (int fs = 0; fs < jm.getFeatureSettings().getSetting()
5220               .size(); fs++)
5221       {
5222         Setting setting = jm.getFeatureSettings().getSetting().get(fs);
5223         String featureType = setting.getType();
5224
5225         /*
5226          * restore feature filters (if any)
5227          */
5228         jalview.xml.binding.jalview.FeatureMatcherSet filters = setting
5229                 .getMatcherSet();
5230         if (filters != null)
5231         {
5232           FeatureMatcherSetI filter = Jalview2XML.parseFilter(featureType,
5233                   filters);
5234           if (!filter.isEmpty())
5235           {
5236             fr.setFeatureFilter(featureType, filter);
5237           }
5238         }
5239
5240         /*
5241          * restore feature colour scheme
5242          */
5243         Color maxColour = new Color(setting.getColour());
5244         if (setting.getMincolour() != null)
5245         {
5246           /*
5247            * minColour is always set unless a simple colour
5248            * (including for colour by label though it doesn't use it)
5249            */
5250           Color minColour = new Color(setting.getMincolour().intValue());
5251           Color noValueColour = minColour;
5252           NoValueColour noColour = setting.getNoValueColour();
5253           if (noColour == NoValueColour.NONE)
5254           {
5255             noValueColour = null;
5256           }
5257           else if (noColour == NoValueColour.MAX)
5258           {
5259             noValueColour = maxColour;
5260           }
5261           float min = safeFloat(safeFloat(setting.getMin()));
5262           float max = setting.getMax() == null ? 1f
5263                   : setting.getMax().floatValue();
5264           FeatureColourI gc = new FeatureColour(maxColour, minColour,
5265                   maxColour, noValueColour, min, max);
5266           if (setting.getAttributeName().size() > 0)
5267           {
5268             gc.setAttributeName(setting.getAttributeName().toArray(
5269                     new String[setting.getAttributeName().size()]));
5270           }
5271           if (setting.getThreshold() != null)
5272           {
5273             gc.setThreshold(setting.getThreshold().floatValue());
5274             int threshstate = safeInt(setting.getThreshstate());
5275             // -1 = None, 0 = Below, 1 = Above threshold
5276             if (threshstate == 0)
5277             {
5278               gc.setBelowThreshold(true);
5279             }
5280             else if (threshstate == 1)
5281             {
5282               gc.setAboveThreshold(true);
5283             }
5284           }
5285           gc.setAutoScaled(true); // default
5286           if (setting.isAutoScale() != null)
5287           {
5288             gc.setAutoScaled(setting.isAutoScale());
5289           }
5290           if (setting.isColourByLabel() != null)
5291           {
5292             gc.setColourByLabel(setting.isColourByLabel());
5293           }
5294           // and put in the feature colour table.
5295           featureColours.put(featureType, gc);
5296         }
5297         else
5298         {
5299           featureColours.put(featureType, new FeatureColour(maxColour));
5300         }
5301         renderOrder[fs] = featureType;
5302         if (setting.getOrder() != null)
5303         {
5304           featureOrder.put(featureType, setting.getOrder().floatValue());
5305         }
5306         else
5307         {
5308           featureOrder.put(featureType, new Float(
5309                   fs / jm.getFeatureSettings().getSetting().size()));
5310         }
5311         if (safeBoolean(setting.isDisplay()))
5312         {
5313           fdi.setVisible(featureType);
5314         }
5315       }
5316       Map<String, Boolean> fgtable = new Hashtable<>();
5317       for (int gs = 0; gs < jm.getFeatureSettings().getGroup().size(); gs++)
5318       {
5319         Group grp = jm.getFeatureSettings().getGroup().get(gs);
5320         fgtable.put(grp.getName(), new Boolean(grp.isDisplay()));
5321       }
5322       // FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
5323       // fgtable, featureColours, jms.getFeatureSettings().hasTransparency() ?
5324       // jms.getFeatureSettings().getTransparency() : 0.0, featureOrder);
5325       FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
5326               fgtable, featureColours, 1.0f, featureOrder);
5327       fr.transferSettings(frs);
5328     }
5329
5330     if (view.getHiddenColumns().size() > 0)
5331     {
5332       for (int c = 0; c < view.getHiddenColumns().size(); c++)
5333       {
5334         final HiddenColumns hc = view.getHiddenColumns().get(c);
5335         viewport.hideColumns(safeInt(hc.getStart()),
5336                 safeInt(hc.getEnd()) /* +1 */);
5337       }
5338     }
5339     if (view.getCalcIdParam() != null)
5340     {
5341       for (CalcIdParam calcIdParam : view.getCalcIdParam())
5342       {
5343         if (calcIdParam != null)
5344         {
5345           if (recoverCalcIdParam(calcIdParam, viewport))
5346           {
5347           }
5348           else
5349           {
5350             warn("Couldn't recover parameters for "
5351                     + calcIdParam.getCalcId());
5352           }
5353         }
5354       }
5355     }
5356     af.setMenusFromViewport(viewport);
5357     af.setTitle(view.getTitle());
5358     // TODO: we don't need to do this if the viewport is aready visible.
5359     /*
5360      * Add the AlignFrame to the desktop (it may be 'gathered' later), unless it
5361      * has a 'cdna/protein complement' view, in which case save it in order to
5362      * populate a SplitFrame once all views have been read in.
5363      */
5364     String complementaryViewId = view.getComplementId();
5365     if (complementaryViewId == null)
5366     {
5367       Dimension dim = Platform.getDimIfEmbedded(af,
5368               safeInt(view.getWidth()), safeInt(view.getHeight()));
5369       Desktop.addInternalFrame(af, view.getTitle(), dim.width, dim.height);
5370       // recompute any autoannotation
5371       af.alignPanel.updateAnnotation(false, true);
5372       reorderAutoannotation(af, al, autoAlan);
5373       af.alignPanel.alignmentChanged();
5374     }
5375     else
5376     {
5377       splitFrameCandidates.put(view, af);
5378     }
5379     return af;
5380   }
5381
5382   /**
5383    * Reads saved data to restore Colour by Annotation settings
5384    * 
5385    * @param viewAnnColour
5386    * @param af
5387    * @param al
5388    * @param model
5389    * @param checkGroupAnnColour
5390    * @return
5391    */
5392   private ColourSchemeI constructAnnotationColour(
5393           AnnotationColourScheme viewAnnColour, AlignFrame af,
5394           AlignmentI al, JalviewModel model, boolean checkGroupAnnColour)
5395   {
5396     boolean propagateAnnColour = false;
5397     AlignmentI annAlignment = af != null ? af.getViewport().getAlignment()
5398             : al;
5399     if (checkGroupAnnColour && al.getGroups() != null
5400             && al.getGroups().size() > 0)
5401     {
5402       // pre 2.8.1 behaviour
5403       // check to see if we should transfer annotation colours
5404       propagateAnnColour = true;
5405       for (SequenceGroup sg : al.getGroups())
5406       {
5407         if (sg.getColourScheme() instanceof AnnotationColourGradient)
5408         {
5409           propagateAnnColour = false;
5410         }
5411       }
5412     }
5413
5414     /*
5415      * 2.10.2- : saved annotationId is AlignmentAnnotation.annotationId
5416      */
5417     String annotationId = viewAnnColour.getAnnotation();
5418     AlignmentAnnotation matchedAnnotation = annotationIds.get(annotationId);
5419
5420     /*
5421      * pre 2.10.2: saved annotationId is AlignmentAnnotation.label
5422      */
5423     if (matchedAnnotation == null
5424             && annAlignment.getAlignmentAnnotation() != null)
5425     {
5426       for (int i = 0; i < annAlignment.getAlignmentAnnotation().length; i++)
5427       {
5428         if (annotationId
5429                 .equals(annAlignment.getAlignmentAnnotation()[i].label))
5430         {
5431           matchedAnnotation = annAlignment.getAlignmentAnnotation()[i];
5432           break;
5433         }
5434       }
5435     }
5436     if (matchedAnnotation == null)
5437     {
5438       System.err.println("Failed to match annotation colour scheme for "
5439               + annotationId);
5440       return null;
5441     }
5442     if (matchedAnnotation.getThreshold() == null)
5443     {
5444       matchedAnnotation.setThreshold(
5445               new GraphLine(safeFloat(viewAnnColour.getThreshold()),
5446                       "Threshold", Color.black));
5447     }
5448
5449     AnnotationColourGradient cs = null;
5450     if (viewAnnColour.getColourScheme().equals("None"))
5451     {
5452       cs = new AnnotationColourGradient(matchedAnnotation,
5453               new Color(safeInt(viewAnnColour.getMinColour())),
5454               new Color(safeInt(viewAnnColour.getMaxColour())),
5455               safeInt(viewAnnColour.getAboveThreshold()));
5456     }
5457     else if (viewAnnColour.getColourScheme().startsWith("ucs"))
5458     {
5459       cs = new AnnotationColourGradient(matchedAnnotation,
5460               getUserColourScheme(model, viewAnnColour.getColourScheme()),
5461               safeInt(viewAnnColour.getAboveThreshold()));
5462     }
5463     else
5464     {
5465       cs = new AnnotationColourGradient(matchedAnnotation,
5466               ColourSchemeProperty.getColourScheme(af.getViewport(), al,
5467                       viewAnnColour.getColourScheme()),
5468               safeInt(viewAnnColour.getAboveThreshold()));
5469     }
5470
5471     boolean perSequenceOnly = safeBoolean(viewAnnColour.isPerSequence());
5472     boolean useOriginalColours = safeBoolean(
5473             viewAnnColour.isPredefinedColours());
5474     cs.setSeqAssociated(perSequenceOnly);
5475     cs.setPredefinedColours(useOriginalColours);
5476
5477     if (propagateAnnColour && al.getGroups() != null)
5478     {
5479       // Also use these settings for all the groups
5480       for (int g = 0; g < al.getGroups().size(); g++)
5481       {
5482         SequenceGroup sg = al.getGroups().get(g);
5483         if (sg.getGroupColourScheme() == null)
5484         {
5485           continue;
5486         }
5487
5488         AnnotationColourGradient groupScheme = new AnnotationColourGradient(
5489                 matchedAnnotation, sg.getColourScheme(),
5490                 safeInt(viewAnnColour.getAboveThreshold()));
5491         sg.setColourScheme(groupScheme);
5492         groupScheme.setSeqAssociated(perSequenceOnly);
5493         groupScheme.setPredefinedColours(useOriginalColours);
5494       }
5495     }
5496     return cs;
5497   }
5498
5499   private void reorderAutoannotation(AlignFrame af, AlignmentI al,
5500           List<JvAnnotRow> autoAlan)
5501   {
5502     // copy over visualization settings for autocalculated annotation in the
5503     // view
5504     if (al.getAlignmentAnnotation() != null)
5505     {
5506       /**
5507        * Kludge for magic autoannotation names (see JAL-811)
5508        */
5509       String[] magicNames = new String[] { "Consensus", "Quality",
5510           "Conservation" };
5511       JvAnnotRow nullAnnot = new JvAnnotRow(-1, null);
5512       Hashtable<String, JvAnnotRow> visan = new Hashtable<>();
5513       for (String nm : magicNames)
5514       {
5515         visan.put(nm, nullAnnot);
5516       }
5517       for (JvAnnotRow auan : autoAlan)
5518       {
5519         visan.put(auan.template.label
5520                 + (auan.template.getCalcId() == null ? ""
5521                         : "\t" + auan.template.getCalcId()),
5522                 auan);
5523       }
5524       int hSize = al.getAlignmentAnnotation().length;
5525       List<JvAnnotRow> reorder = new ArrayList<>();
5526       // work through any autoCalculated annotation already on the view
5527       // removing it if it should be placed in a different location on the
5528       // annotation panel.
5529       List<String> remains = new ArrayList<>(visan.keySet());
5530       for (int h = 0; h < hSize; h++)
5531       {
5532         jalview.datamodel.AlignmentAnnotation jalan = al
5533                 .getAlignmentAnnotation()[h];
5534         if (jalan.autoCalculated)
5535         {
5536           String k;
5537           JvAnnotRow valan = visan.get(k = jalan.label);
5538           if (jalan.getCalcId() != null)
5539           {
5540             valan = visan.get(k = jalan.label + "\t" + jalan.getCalcId());
5541           }
5542
5543           if (valan != null)
5544           {
5545             // delete the auto calculated row from the alignment
5546             al.deleteAnnotation(jalan, false);
5547             remains.remove(k);
5548             hSize--;
5549             h--;
5550             if (valan != nullAnnot)
5551             {
5552               if (jalan != valan.template)
5553               {
5554                 // newly created autoannotation row instance
5555                 // so keep a reference to the visible annotation row
5556                 // and copy over all relevant attributes
5557                 if (valan.template.graphHeight >= 0)
5558
5559                 {
5560                   jalan.graphHeight = valan.template.graphHeight;
5561                 }
5562                 jalan.visible = valan.template.visible;
5563               }
5564               reorder.add(new JvAnnotRow(valan.order, jalan));
5565             }
5566           }
5567         }
5568       }
5569       // Add any (possibly stale) autocalculated rows that were not appended to
5570       // the view during construction
5571       for (String other : remains)
5572       {
5573         JvAnnotRow othera = visan.get(other);
5574         if (othera != nullAnnot && othera.template.getCalcId() != null
5575                 && othera.template.getCalcId().length() > 0)
5576         {
5577           reorder.add(othera);
5578         }
5579       }
5580       // now put the automatic annotation in its correct place
5581       int s = 0, srt[] = new int[reorder.size()];
5582       JvAnnotRow[] rws = new JvAnnotRow[reorder.size()];
5583       for (JvAnnotRow jvar : reorder)
5584       {
5585         rws[s] = jvar;
5586         srt[s++] = jvar.order;
5587       }
5588       reorder.clear();
5589       jalview.util.QuickSort.sort(srt, rws);
5590       // and re-insert the annotation at its correct position
5591       for (JvAnnotRow jvar : rws)
5592       {
5593         al.addAnnotation(jvar.template, jvar.order);
5594       }
5595       af.alignPanel.adjustAnnotationHeight();
5596     }
5597   }
5598
5599   Hashtable skipList = null;
5600
5601   /**
5602    * TODO remove this method
5603    * 
5604    * @param view
5605    * @return AlignFrame bound to sequenceSetId from view, if one exists. private
5606    *         AlignFrame getSkippedFrame(Viewport view) { if (skipList==null) {
5607    *         throw new Error("Implementation Error. No skipList defined for this
5608    *         Jalview2XML instance."); } return (AlignFrame)
5609    *         skipList.get(view.getSequenceSetId()); }
5610    */
5611
5612   /**
5613    * Check if the Jalview view contained in object should be skipped or not.
5614    * 
5615    * @param object
5616    * @return true if view's sequenceSetId is a key in skipList
5617    */
5618   private boolean skipViewport(JalviewModel object)
5619   {
5620     if (skipList == null)
5621     {
5622       return false;
5623     }
5624     String id = object.getViewport().get(0).getSequenceSetId();
5625     if (skipList.containsKey(id))
5626     {
5627       if (Cache.log != null && Cache.log.isDebugEnabled())
5628       {
5629         Cache.log.debug("Skipping seuqence set id " + id);
5630       }
5631       return true;
5632     }
5633     return false;
5634   }
5635
5636   protected void addToSkipList(AlignFrame af)
5637   {
5638     if (skipList == null)
5639     {
5640       skipList = new Hashtable();
5641     }
5642     skipList.put(af.getViewport().getSequenceSetId(), af);
5643   }
5644
5645   protected void clearSkipList()
5646   {
5647     if (skipList != null)
5648     {
5649       skipList.clear();
5650       skipList = null;
5651     }
5652   }
5653
5654   private void recoverDatasetFor(SequenceSet vamsasSet, AlignmentI al,
5655           boolean ignoreUnrefed, String uniqueSeqSetId)
5656   {
5657     jalview.datamodel.AlignmentI ds = getDatasetFor(
5658             vamsasSet.getDatasetId());
5659     AlignmentI xtant_ds = ds;
5660     if (xtant_ds == null)
5661     {
5662       // good chance we are about to create a new dataset, but check if we've
5663       // seen some of the dataset sequence IDs before.
5664       // TODO: skip this check if we are working with project generated by
5665       // version 2.11 or later
5666       xtant_ds = checkIfHasDataset(vamsasSet.getSequence());
5667       if (xtant_ds != null)
5668       {
5669         ds = xtant_ds;
5670         addDatasetRef(vamsasSet.getDatasetId(), ds);
5671       }
5672     }
5673     Vector dseqs = null;
5674     if (!ignoreUnrefed)
5675     {
5676       // recovering an alignment View
5677       AlignmentI seqSetDS = getDatasetFor(UNIQSEQSETID + uniqueSeqSetId);
5678       if (seqSetDS != null)
5679       {
5680         if (ds != null && ds != seqSetDS)
5681         {
5682           warn("JAL-3171 regression: Overwriting a dataset reference for an alignment"
5683                   + " - CDS/Protein crossreference data may be lost");
5684           if (xtant_ds != null)
5685           {
5686             // This can only happen if the unique sequence set ID was bound to a
5687             // dataset that did not contain any of the sequences in the view
5688             // currently being restored.
5689             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.");
5690           }
5691         }
5692         ds = seqSetDS;
5693         addDatasetRef(vamsasSet.getDatasetId(), ds);
5694       }
5695     }
5696     if (ds == null)
5697     {
5698       // try even harder to restore dataset
5699       AlignmentI xtantDS = checkIfHasDataset(vamsasSet.getSequence());
5700       // create a list of new dataset sequences
5701       dseqs = new Vector();
5702     }
5703     for (int i = 0, iSize = vamsasSet.getSequence().size(); i < iSize; i++)
5704     {
5705       Sequence vamsasSeq = vamsasSet.getSequence().get(i);
5706       ensureJalviewDatasetSequence(vamsasSeq, ds, dseqs, ignoreUnrefed, i);
5707     }
5708     // create a new dataset
5709     if (ds == null)
5710     {
5711       SequenceI[] dsseqs = new SequenceI[dseqs.size()];
5712       dseqs.copyInto(dsseqs);
5713       ds = new jalview.datamodel.Alignment(dsseqs);
5714 //      debug("Jalview2XML Created new dataset " + vamsasSet.getDatasetId()
5715 //              + " for alignment " + System.identityHashCode(al));
5716       addDatasetRef(vamsasSet.getDatasetId(), ds);
5717     }
5718     // set the dataset for the newly imported alignment.
5719     if (al.getDataset() == null && !ignoreUnrefed)
5720     {
5721       al.setDataset(ds);
5722       // register dataset for the alignment's uniqueSeqSetId for legacy projects
5723       addDatasetRef(UNIQSEQSETID + uniqueSeqSetId, ds);
5724     }
5725     updateSeqDatasetBinding(vamsasSet.getSequence(), ds);
5726   }
5727
5728   /**
5729    * XML dataset sequence ID to materialised dataset reference
5730    */
5731   HashMap<String, AlignmentI> seqToDataset = new HashMap<>();
5732
5733   /**
5734    * @return the first materialised dataset reference containing a dataset
5735    *         sequence referenced in the given view
5736    * @param list
5737    *          - sequences from the view
5738    */
5739   AlignmentI checkIfHasDataset(List<Sequence> list)
5740   {
5741     for (Sequence restoredSeq : list)
5742     {
5743       AlignmentI datasetFor = seqToDataset.get(restoredSeq.getDsseqid());
5744       if (datasetFor != null)
5745       {
5746         return datasetFor;
5747       }
5748     }
5749     return null;
5750   }
5751
5752   /**
5753    * Register ds as the containing dataset for the dataset sequences referenced
5754    * by sequences in list
5755    * 
5756    * @param list
5757    *          - sequences in a view
5758    * @param ds
5759    */
5760   void updateSeqDatasetBinding(List<Sequence> list, AlignmentI ds)
5761   {
5762     for (Sequence restoredSeq : list)
5763     {
5764       AlignmentI prevDS = seqToDataset.put(restoredSeq.getDsseqid(), ds);
5765       if (prevDS != null && prevDS != ds)
5766       {
5767         warn("Dataset sequence appears in many datasets: "
5768                 + restoredSeq.getDsseqid());
5769         // TODO: try to merge!
5770       }
5771     }
5772   }
5773   /**
5774    * 
5775    * @param vamsasSeq
5776    *          sequence definition to create/merge dataset sequence for
5777    * @param ds
5778    *          dataset alignment
5779    * @param dseqs
5780    *          vector to add new dataset sequence to
5781    * @param ignoreUnrefed
5782    *          - when true, don't create new sequences from vamsasSeq if it's id
5783    *          doesn't already have an asssociated Jalview sequence.
5784    * @param vseqpos
5785    *          - used to reorder the sequence in the alignment according to the
5786    *          vamsasSeq array ordering, to preserve ordering of dataset
5787    */
5788   private void ensureJalviewDatasetSequence(Sequence vamsasSeq,
5789           AlignmentI ds, Vector dseqs, boolean ignoreUnrefed, int vseqpos)
5790   {
5791     // JBP TODO: Check this is called for AlCodonFrames to support recovery of
5792     // xRef Codon Maps
5793     SequenceI sq = seqRefIds.get(vamsasSeq.getId());
5794     boolean reorder = false;
5795     SequenceI dsq = null;
5796     if (sq != null && sq.getDatasetSequence() != null)
5797     {
5798       dsq = sq.getDatasetSequence();
5799     }
5800     else
5801     {
5802       reorder = true;
5803     }
5804     if (sq == null && ignoreUnrefed)
5805     {
5806       return;
5807     }
5808     String sqid = vamsasSeq.getDsseqid();
5809     if (dsq == null)
5810     {
5811       // need to create or add a new dataset sequence reference to this sequence
5812       if (sqid != null)
5813       {
5814         dsq = seqRefIds.get(sqid);
5815       }
5816       // check again
5817       if (dsq == null)
5818       {
5819         // make a new dataset sequence
5820         dsq = sq.createDatasetSequence();
5821         if (sqid == null)
5822         {
5823           // make up a new dataset reference for this sequence
5824           sqid = seqHash(dsq);
5825         }
5826         dsq.setVamsasId(uniqueSetSuffix + sqid);
5827         seqRefIds.put(sqid, dsq);
5828         if (ds == null)
5829         {
5830           if (dseqs != null)
5831           {
5832             dseqs.addElement(dsq);
5833           }
5834         }
5835         else
5836         {
5837           ds.addSequence(dsq);
5838         }
5839       }
5840       else
5841       {
5842         if (sq != dsq)
5843         { // make this dataset sequence sq's dataset sequence
5844           sq.setDatasetSequence(dsq);
5845           // and update the current dataset alignment
5846           if (ds == null)
5847           {
5848             if (dseqs != null)
5849             {
5850               if (!dseqs.contains(dsq))
5851               {
5852                 dseqs.add(dsq);
5853               }
5854             }
5855             else
5856             {
5857               if (ds.findIndex(dsq) < 0)
5858               {
5859                 ds.addSequence(dsq);
5860               }
5861             }
5862           }
5863         }
5864       }
5865     }
5866     // TODO: refactor this as a merge dataset sequence function
5867     // now check that sq (the dataset sequence) sequence really is the union of
5868     // all references to it
5869     // boolean pre = sq.getStart() < dsq.getStart();
5870     // boolean post = sq.getEnd() > dsq.getEnd();
5871     // if (pre || post)
5872     if (sq != dsq)
5873     {
5874       // StringBuffer sb = new StringBuffer();
5875       String newres = jalview.analysis.AlignSeq.extractGaps(
5876               jalview.util.Comparison.GapChars, sq.getSequenceAsString());
5877       if (!newres.equalsIgnoreCase(dsq.getSequenceAsString())
5878               && newres.length() > dsq.getLength())
5879       {
5880         // Update with the longer sequence.
5881         synchronized (dsq)
5882         {
5883           /*
5884            * if (pre) { sb.insert(0, newres .substring(0, dsq.getStart() -
5885            * sq.getStart())); dsq.setStart(sq.getStart()); } if (post) {
5886            * sb.append(newres.substring(newres.length() - sq.getEnd() -
5887            * dsq.getEnd())); dsq.setEnd(sq.getEnd()); }
5888            */
5889           dsq.setSequence(newres);
5890         }
5891         // TODO: merges will never happen if we 'know' we have the real dataset
5892         // sequence - this should be detected when id==dssid
5893         System.err.println(
5894                 "DEBUG Notice:  Merged dataset sequence (if you see this often, post at http://issues.jalview.org/browse/JAL-1474)"); // ("
5895         // + (pre ? "prepended" : "") + " "
5896         // + (post ? "appended" : ""));
5897       }
5898     }
5899     else
5900     {
5901       // sequence refs are identical. We may need to update the existing dataset
5902       // alignment with this one, though.
5903       if (ds != null && dseqs == null)
5904       {
5905         int opos = ds.findIndex(dsq);
5906         SequenceI tseq = null;
5907         if (opos != -1 && vseqpos != opos)
5908         {
5909           // remove from old position
5910           ds.deleteSequence(dsq);
5911         }
5912         if (vseqpos < ds.getHeight())
5913         {
5914           if (vseqpos != opos)
5915           {
5916             // save sequence at destination position
5917             tseq = ds.getSequenceAt(vseqpos);
5918             ds.replaceSequenceAt(vseqpos, dsq);
5919             ds.addSequence(tseq);
5920           }
5921         }
5922         else
5923         {
5924           ds.addSequence(dsq);
5925         }
5926       }
5927     }
5928   }
5929
5930   /*
5931    * TODO use AlignmentI here and in related methods - needs
5932    * AlignmentI.getDataset() changed to return AlignmentI instead of Alignment
5933    */
5934   Hashtable<String, AlignmentI> datasetIds = null;
5935
5936   IdentityHashMap<AlignmentI, String> dataset2Ids = null;
5937
5938   private AlignmentI getDatasetFor(String datasetId)
5939   {
5940     if (datasetIds == null)
5941     {
5942       datasetIds = new Hashtable<>();
5943       return null;
5944     }
5945     if (datasetIds.containsKey(datasetId))
5946     {
5947       return datasetIds.get(datasetId);
5948     }
5949     return null;
5950   }
5951
5952   private void addDatasetRef(String datasetId, AlignmentI dataset)
5953   {
5954     if (datasetIds == null)
5955     {
5956       datasetIds = new Hashtable<>();
5957     }
5958     datasetIds.put(datasetId, dataset);
5959   }
5960
5961   /**
5962    * make a new dataset ID for this jalview dataset alignment
5963    * 
5964    * @param dataset
5965    * @return
5966    */
5967   private String getDatasetIdRef(AlignmentI dataset)
5968   {
5969     if (dataset.getDataset() != null)
5970     {
5971       warn("Serious issue!  Dataset Object passed to getDatasetIdRef is not a Jalview DATASET alignment...");
5972     }
5973     String datasetId = makeHashCode(dataset, null);
5974     if (datasetId == null)
5975     {
5976       // make a new datasetId and record it
5977       if (dataset2Ids == null)
5978       {
5979         dataset2Ids = new IdentityHashMap<>();
5980       }
5981       else
5982       {
5983         datasetId = dataset2Ids.get(dataset);
5984       }
5985       if (datasetId == null)
5986       {
5987         datasetId = "ds" + dataset2Ids.size() + 1;
5988         dataset2Ids.put(dataset, datasetId);
5989       }
5990     }
5991     return datasetId;
5992   }
5993
5994   private void addDBRefs(SequenceI datasetSequence, Sequence sequence)
5995   {
5996     for (int d = 0; d < sequence.getDBRef().size(); d++)
5997     {
5998       DBRef dr = sequence.getDBRef().get(d);
5999       jalview.datamodel.DBRefEntry entry = new jalview.datamodel.DBRefEntry(
6000               dr.getSource(), dr.getVersion(), dr.getAccessionId());
6001       if (dr.getMapping() != null)
6002       {
6003         entry.setMap(addMapping(dr.getMapping()));
6004       }
6005       datasetSequence.addDBRef(entry);
6006     }
6007   }
6008
6009   private jalview.datamodel.Mapping addMapping(Mapping m)
6010   {
6011     SequenceI dsto = null;
6012     // Mapping m = dr.getMapping();
6013     int fr[] = new int[m.getMapListFrom().size() * 2];
6014     Iterator<MapListFrom> from = m.getMapListFrom().iterator();// enumerateMapListFrom();
6015     for (int _i = 0; from.hasNext(); _i += 2)
6016     {
6017       MapListFrom mf = from.next();
6018       fr[_i] = mf.getStart();
6019       fr[_i + 1] = mf.getEnd();
6020     }
6021     int fto[] = new int[m.getMapListTo().size() * 2];
6022     Iterator<MapListTo> to = m.getMapListTo().iterator();// enumerateMapListTo();
6023     for (int _i = 0; to.hasNext(); _i += 2)
6024     {
6025       MapListTo mf = to.next();
6026       fto[_i] = mf.getStart();
6027       fto[_i + 1] = mf.getEnd();
6028     }
6029     jalview.datamodel.Mapping jmap = new jalview.datamodel.Mapping(dsto, fr,
6030             fto, m.getMapFromUnit().intValue(),
6031             m.getMapToUnit().intValue());
6032
6033     /*
6034      * (optional) choice of dseqFor or Sequence
6035      */
6036     if (m.getDseqFor() != null)
6037     {
6038       String dsfor = m.getDseqFor();
6039       if (seqRefIds.containsKey(dsfor))
6040       {
6041         /*
6042          * recover from hash
6043          */
6044         jmap.setTo(seqRefIds.get(dsfor));
6045       }
6046       else
6047       {
6048         frefedSequence.add(newMappingRef(dsfor, jmap));
6049       }
6050     }
6051     else if (m.getSequence() != null)
6052     {
6053       /*
6054        * local sequence definition
6055        */
6056       Sequence ms = m.getSequence();
6057       SequenceI djs = null;
6058       String sqid = ms.getDsseqid();
6059       if (sqid != null && sqid.length() > 0)
6060       {
6061         /*
6062          * recover dataset sequence
6063          */
6064         djs = seqRefIds.get(sqid);
6065       }
6066       else
6067       {
6068         System.err.println(
6069                 "Warning - making up dataset sequence id for DbRef sequence map reference");
6070         sqid = ((Object) ms).toString(); // make up a new hascode for
6071         // undefined dataset sequence hash
6072         // (unlikely to happen)
6073       }
6074
6075       if (djs == null)
6076       {
6077         /**
6078          * make a new dataset sequence and add it to refIds hash
6079          */
6080         djs = new jalview.datamodel.Sequence(ms.getName(),
6081                 ms.getSequence());
6082         djs.setStart(jmap.getMap().getToLowest());
6083         djs.setEnd(jmap.getMap().getToHighest());
6084         djs.setVamsasId(uniqueSetSuffix + sqid);
6085         jmap.setTo(djs);
6086         incompleteSeqs.put(sqid, djs);
6087         seqRefIds.put(sqid, djs);
6088
6089       }
6090       jalview.bin.Cache.log.debug("about to recurse on addDBRefs.");
6091       addDBRefs(djs, ms);
6092
6093     }
6094
6095     return jmap;
6096   }
6097
6098   /**
6099    * Provides a 'copy' of an alignment view (on action New View) by 'saving' the
6100    * view as XML (but not to file), and then reloading it
6101    * 
6102    * @param ap
6103    * @return
6104    */
6105   public AlignmentPanel copyAlignPanel(AlignmentPanel ap)
6106   {
6107     initSeqRefs();
6108     JalviewModel jm = saveState(ap, null, null, null);
6109
6110     addDatasetRef(
6111             jm.getVamsasModel().getSequenceSet().get(0).getDatasetId(),
6112             ap.getAlignment().getDataset());
6113
6114     uniqueSetSuffix = "";
6115     // jm.getJalviewModelSequence().getViewport(0).setId(null);
6116     jm.getViewport().get(0).setId(null);
6117     // we don't overwrite the view we just copied
6118
6119     if (this.frefedSequence == null)
6120     {
6121       frefedSequence = new Vector<>();
6122     }
6123
6124     viewportsAdded.clear();
6125
6126     AlignFrame af = loadFromObject(jm, null, false, null);
6127     af.getAlignPanels().clear();
6128     af.closeMenuItem_actionPerformed(true);
6129     af.alignPanel.setHoldRepaint(false);
6130     this.jarFile = null;
6131
6132     return af.alignPanel;
6133   }
6134
6135   private Hashtable jvids2vobj;
6136
6137   private void warn(String msg)
6138   {
6139     warn(msg, null);
6140   }
6141
6142   private void warn(String msg, Exception e)
6143   {
6144     if (Cache.log != null)
6145     {
6146       if (e != null)
6147       {
6148         Cache.log.warn(msg, e);
6149       }
6150       else
6151       {
6152         Cache.log.warn(msg);
6153       }
6154     }
6155     else
6156     {
6157       System.err.println("Warning: " + msg);
6158       if (e != null)
6159       {
6160         e.printStackTrace();
6161       }
6162     }
6163   }
6164
6165   private void debug(String string)
6166   {
6167     debug(string, null);
6168   }
6169
6170   private void debug(String msg, Exception e)
6171   {
6172     if (Cache.log != null)
6173     {
6174       if (e != null)
6175       {
6176         Cache.log.debug(msg, e);
6177       }
6178       else
6179       {
6180         Cache.log.debug(msg);
6181       }
6182     }
6183     else
6184     {
6185       System.err.println("Warning: " + msg);
6186       if (e != null)
6187       {
6188         e.printStackTrace();
6189       }
6190     }
6191   }
6192
6193   /**
6194    * set the object to ID mapping tables used to write/recover objects and XML
6195    * ID strings for the jalview project. If external tables are provided then
6196    * finalize and clearSeqRefs will not clear the tables when the Jalview2XML
6197    * object goes out of scope. - also populates the datasetIds hashtable with
6198    * alignment objects containing dataset sequences
6199    * 
6200    * @param vobj2jv
6201    *          Map from ID strings to jalview datamodel
6202    * @param jv2vobj
6203    *          Map from jalview datamodel to ID strings
6204    * 
6205    * 
6206    */
6207   public void setObjectMappingTables(Hashtable vobj2jv,
6208           IdentityHashMap jv2vobj)
6209   {
6210     this.jv2vobj = jv2vobj;
6211     this.vobj2jv = vobj2jv;
6212     Iterator ds = jv2vobj.keySet().iterator();
6213     String id;
6214     while (ds.hasNext())
6215     {
6216       Object jvobj = ds.next();
6217       id = jv2vobj.get(jvobj).toString();
6218       if (jvobj instanceof jalview.datamodel.Alignment)
6219       {
6220         if (((jalview.datamodel.Alignment) jvobj).getDataset() == null)
6221         {
6222           addDatasetRef(id, (jalview.datamodel.Alignment) jvobj);
6223         }
6224       }
6225       else if (jvobj instanceof jalview.datamodel.Sequence)
6226       {
6227         // register sequence object so the XML parser can recover it.
6228         if (seqRefIds == null)
6229         {
6230           seqRefIds = new HashMap<>();
6231         }
6232         if (seqsToIds == null)
6233         {
6234           seqsToIds = new IdentityHashMap<>();
6235         }
6236         seqRefIds.put(jv2vobj.get(jvobj).toString(), (SequenceI) jvobj);
6237         seqsToIds.put((SequenceI) jvobj, id);
6238       }
6239       else if (jvobj instanceof jalview.datamodel.AlignmentAnnotation)
6240       {
6241         String anid;
6242         AlignmentAnnotation jvann = (AlignmentAnnotation) jvobj;
6243         annotationIds.put(anid = jv2vobj.get(jvobj).toString(), jvann);
6244         if (jvann.annotationId == null)
6245         {
6246           jvann.annotationId = anid;
6247         }
6248         if (!jvann.annotationId.equals(anid))
6249         {
6250           // TODO verify that this is the correct behaviour
6251           this.warn("Overriding Annotation ID for " + anid
6252                   + " from different id : " + jvann.annotationId);
6253           jvann.annotationId = anid;
6254         }
6255       }
6256       else if (jvobj instanceof String)
6257       {
6258         if (jvids2vobj == null)
6259         {
6260           jvids2vobj = new Hashtable();
6261           jvids2vobj.put(jvobj, jv2vobj.get(jvobj).toString());
6262         }
6263       }
6264       else
6265       {
6266         Cache.log.debug("Ignoring " + jvobj.getClass() + " (ID = " + id);
6267       }
6268     }
6269   }
6270
6271   /**
6272    * set the uniqueSetSuffix used to prefix/suffix object IDs for jalview
6273    * objects created from the project archive. If string is null (default for
6274    * construction) then suffix will be set automatically.
6275    * 
6276    * @param string
6277    */
6278   public void setUniqueSetSuffix(String string)
6279   {
6280     uniqueSetSuffix = string;
6281
6282   }
6283
6284   /**
6285    * uses skipList2 as the skipList for skipping views on sequence sets
6286    * associated with keys in the skipList
6287    * 
6288    * @param skipList2
6289    */
6290   public void setSkipList(Hashtable skipList2)
6291   {
6292     skipList = skipList2;
6293   }
6294
6295   /**
6296    * Reads the jar entry of given name and returns its contents, or null if the
6297    * entry is not found.
6298    * 
6299    * @param jprovider
6300    * @param jarEntryName
6301    * @return
6302    */
6303   protected String readJarEntry(jarInputStreamProvider jprovider,
6304           String jarEntryName)
6305   {
6306     String result = null;
6307     BufferedReader in = null;
6308
6309     try
6310     {
6311       /*
6312        * Reopen the jar input stream and traverse its entries to find a matching
6313        * name
6314        */
6315       JarInputStream jin = jprovider.getJarInputStream();
6316       JarEntry entry = null;
6317       do
6318       {
6319         entry = jin.getNextJarEntry();
6320       } while (entry != null && !entry.getName().equals(jarEntryName));
6321
6322       if (entry != null)
6323       {
6324         StringBuilder out = new StringBuilder(256);
6325         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
6326         String data;
6327
6328         while ((data = in.readLine()) != null)
6329         {
6330           out.append(data);
6331         }
6332         result = out.toString();
6333       }
6334       else
6335       {
6336         warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
6337       }
6338     } catch (Exception ex)
6339     {
6340       ex.printStackTrace();
6341     } finally
6342     {
6343       if (in != null)
6344       {
6345         try
6346         {
6347           in.close();
6348         } catch (IOException e)
6349         {
6350           // ignore
6351         }
6352       }
6353     }
6354
6355     return result;
6356   }
6357
6358   /**
6359    * Returns an incrementing counter (0, 1, 2...)
6360    * 
6361    * @return
6362    */
6363   private synchronized int nextCounter()
6364   {
6365     return counter++;
6366   }
6367
6368   /**
6369    * Loads any saved PCA viewers
6370    * 
6371    * @param jms
6372    * @param ap
6373    */
6374   protected void loadPCAViewers(JalviewModel model, AlignmentPanel ap)
6375   {
6376     try
6377     {
6378       List<PcaViewer> pcaviewers = model.getPcaViewer();
6379       for (PcaViewer viewer : pcaviewers)
6380       {
6381         String modelName = viewer.getScoreModelName();
6382         SimilarityParamsI params = new SimilarityParams(
6383                 viewer.isIncludeGappedColumns(), viewer.isMatchGaps(),
6384                 viewer.isIncludeGaps(),
6385                 viewer.isDenominateByShortestLength());
6386
6387         /*
6388          * create the panel (without computing the PCA)
6389          */
6390         PCAPanel panel = new PCAPanel(ap, modelName, params);
6391
6392         panel.setTitle(viewer.getTitle());
6393         panel.setBounds(new Rectangle(viewer.getXpos(), viewer.getYpos(),
6394                 viewer.getWidth(), viewer.getHeight()));
6395
6396         boolean showLabels = viewer.isShowLabels();
6397         panel.setShowLabels(showLabels);
6398         panel.getRotatableCanvas().setShowLabels(showLabels);
6399         panel.getRotatableCanvas()
6400                 .setBgColour(new Color(viewer.getBgColour()));
6401         panel.getRotatableCanvas()
6402                 .setApplyToAllViews(viewer.isLinkToAllViews());
6403
6404         /*
6405          * load PCA output data
6406          */
6407         ScoreModelI scoreModel = ScoreModels.getInstance()
6408                 .getScoreModel(modelName, ap);
6409         PCA pca = new PCA(null, scoreModel, params);
6410         PcaDataType pcaData = viewer.getPcaData();
6411
6412         MatrixI pairwise = loadDoubleMatrix(pcaData.getPairwiseMatrix());
6413         pca.setPairwiseScores(pairwise);
6414
6415         MatrixI triDiag = loadDoubleMatrix(pcaData.getTridiagonalMatrix());
6416         pca.setTridiagonal(triDiag);
6417
6418         MatrixI result = loadDoubleMatrix(pcaData.getEigenMatrix());
6419         pca.setEigenmatrix(result);
6420
6421         panel.getPcaModel().setPCA(pca);
6422
6423         /*
6424          * we haven't saved the input data! (JAL-2647 to do)
6425          */
6426         panel.setInputData(null);
6427
6428         /*
6429          * add the sequence points for the PCA display
6430          */
6431         List<jalview.datamodel.SequencePoint> seqPoints = new ArrayList<>();
6432         for (SequencePoint sp : viewer.getSequencePoint())
6433         {
6434           String seqId = sp.getSequenceRef();
6435           SequenceI seq = seqRefIds.get(seqId);
6436           if (seq == null)
6437           {
6438             throw new IllegalStateException(
6439                     "Unmatched seqref for PCA: " + seqId);
6440           }
6441           Point pt = new Point(sp.getXPos(), sp.getYPos(), sp.getZPos());
6442           jalview.datamodel.SequencePoint seqPoint = new jalview.datamodel.SequencePoint(
6443                   seq, pt);
6444           seqPoints.add(seqPoint);
6445         }
6446         panel.getRotatableCanvas().setPoints(seqPoints, seqPoints.size());
6447
6448         /*
6449          * set min-max ranges and scale after setPoints (which recomputes them)
6450          */
6451         panel.getRotatableCanvas().setScaleFactor(viewer.getScaleFactor());
6452         SeqPointMin spMin = viewer.getSeqPointMin();
6453         float[] min = new float[] { spMin.getXPos(), spMin.getYPos(),
6454             spMin.getZPos() };
6455         SeqPointMax spMax = viewer.getSeqPointMax();
6456         float[] max = new float[] { spMax.getXPos(), spMax.getYPos(),
6457             spMax.getZPos() };
6458         panel.getRotatableCanvas().setSeqMinMax(min, max);
6459
6460         // todo: hold points list in PCAModel only
6461         panel.getPcaModel().setSequencePoints(seqPoints);
6462
6463         panel.setSelectedDimensionIndex(viewer.getXDim(), X);
6464         panel.setSelectedDimensionIndex(viewer.getYDim(), Y);
6465         panel.setSelectedDimensionIndex(viewer.getZDim(), Z);
6466
6467         // is this duplication needed?
6468         panel.setTop(seqPoints.size() - 1);
6469         panel.getPcaModel().setTop(seqPoints.size() - 1);
6470
6471         /*
6472          * add the axes' end points for the display
6473          */
6474         for (int i = 0; i < 3; i++)
6475         {
6476           Axis axis = viewer.getAxis().get(i);
6477           panel.getRotatableCanvas().getAxisEndPoints()[i] = new Point(
6478                   axis.getXPos(), axis.getYPos(), axis.getZPos());
6479         }
6480
6481         Dimension dim = Platform.getDimIfEmbedded(panel, 475, 450);
6482         Desktop.addInternalFrame(panel, MessageManager.formatMessage(
6483                 "label.calc_title", "PCA", modelName), dim.width,
6484                 dim.height);
6485       }
6486     } catch (Exception ex)
6487     {
6488       Cache.log.error("Error loading PCA: " + ex.toString());
6489     }
6490   }
6491
6492   /**
6493    * Populates an XML model of the feature colour scheme for one feature type
6494    * 
6495    * @param featureType
6496    * @param fcol
6497    * @return
6498    */
6499   public static Colour marshalColour(
6500           String featureType, FeatureColourI fcol)
6501   {
6502     Colour col = new Colour();
6503     if (fcol.isSimpleColour())
6504     {
6505       col.setRGB(Format.getHexString(fcol.getColour()));
6506     }
6507     else
6508     {
6509       col.setRGB(Format.getHexString(fcol.getMaxColour()));
6510       col.setMin(fcol.getMin());
6511       col.setMax(fcol.getMax());
6512       col.setMinRGB(jalview.util.Format.getHexString(fcol.getMinColour()));
6513       col.setAutoScale(fcol.isAutoScaled());
6514       col.setThreshold(fcol.getThreshold());
6515       col.setColourByLabel(fcol.isColourByLabel());
6516       col.setThreshType(fcol.isAboveThreshold() ? ThresholdType.ABOVE
6517               : (fcol.isBelowThreshold() ? ThresholdType.BELOW
6518                       : ThresholdType.NONE));
6519       if (fcol.isColourByAttribute())
6520       {
6521         final String[] attName = fcol.getAttributeName();
6522         col.getAttributeName().add(attName[0]);
6523         if (attName.length > 1)
6524         {
6525           col.getAttributeName().add(attName[1]);
6526         }
6527       }
6528       Color noColour = fcol.getNoColour();
6529       if (noColour == null)
6530       {
6531         col.setNoValueColour(NoValueColour.NONE);
6532       }
6533       else if (noColour == fcol.getMaxColour())
6534       {
6535         col.setNoValueColour(NoValueColour.MAX);
6536       }
6537       else
6538       {
6539         col.setNoValueColour(NoValueColour.MIN);
6540       }
6541     }
6542     col.setName(featureType);
6543     return col;
6544   }
6545
6546   /**
6547    * Populates an XML model of the feature filter(s) for one feature type
6548    * 
6549    * @param firstMatcher
6550    *          the first (or only) match condition)
6551    * @param filter
6552    *          remaining match conditions (if any)
6553    * @param and
6554    *          if true, conditions are and-ed, else or-ed
6555    */
6556   public static jalview.xml.binding.jalview.FeatureMatcherSet marshalFilter(
6557           FeatureMatcherI firstMatcher, Iterator<FeatureMatcherI> filters,
6558           boolean and)
6559   {
6560     jalview.xml.binding.jalview.FeatureMatcherSet result = new jalview.xml.binding.jalview.FeatureMatcherSet();
6561   
6562     if (filters.hasNext())
6563     {
6564       /*
6565        * compound matcher
6566        */
6567       CompoundMatcher compound = new CompoundMatcher();
6568       compound.setAnd(and);
6569       jalview.xml.binding.jalview.FeatureMatcherSet matcher1 = marshalFilter(
6570               firstMatcher, Collections.emptyIterator(), and);
6571       // compound.addMatcherSet(matcher1);
6572       compound.getMatcherSet().add(matcher1);
6573       FeatureMatcherI nextMatcher = filters.next();
6574       jalview.xml.binding.jalview.FeatureMatcherSet matcher2 = marshalFilter(
6575               nextMatcher, filters, and);
6576       // compound.addMatcherSet(matcher2);
6577       compound.getMatcherSet().add(matcher2);
6578       result.setCompoundMatcher(compound);
6579     }
6580     else
6581     {
6582       /*
6583        * single condition matcher
6584        */
6585       // MatchCondition matcherModel = new MatchCondition();
6586       jalview.xml.binding.jalview.FeatureMatcher matcherModel = new jalview.xml.binding.jalview.FeatureMatcher();
6587       matcherModel.setCondition(
6588               firstMatcher.getMatcher().getCondition().getStableName());
6589       matcherModel.setValue(firstMatcher.getMatcher().getPattern());
6590       if (firstMatcher.isByAttribute())
6591       {
6592         matcherModel.setBy(FilterBy.BY_ATTRIBUTE);
6593         // matcherModel.setAttributeName(firstMatcher.getAttribute());
6594         String[] attName = firstMatcher.getAttribute();
6595         matcherModel.getAttributeName().add(attName[0]); // attribute
6596         if (attName.length > 1)
6597         {
6598           matcherModel.getAttributeName().add(attName[1]); // sub-attribute
6599         }
6600       }
6601       else if (firstMatcher.isByLabel())
6602       {
6603         matcherModel.setBy(FilterBy.BY_LABEL);
6604       }
6605       else if (firstMatcher.isByScore())
6606       {
6607         matcherModel.setBy(FilterBy.BY_SCORE);
6608       }
6609       result.setMatchCondition(matcherModel);
6610     }
6611   
6612     return result;
6613   }
6614
6615   /**
6616    * Loads one XML model of a feature filter to a Jalview object
6617    * 
6618    * @param featureType
6619    * @param matcherSetModel
6620    * @return
6621    */
6622   public static FeatureMatcherSetI parseFilter(
6623           String featureType,
6624           jalview.xml.binding.jalview.FeatureMatcherSet matcherSetModel)
6625   {
6626     FeatureMatcherSetI result = new FeatureMatcherSet();
6627     try
6628     {
6629       parseFilterConditions(result, matcherSetModel, true);
6630     } catch (IllegalStateException e)
6631     {
6632       // mixing AND and OR conditions perhaps
6633       System.err.println(
6634               String.format("Error reading filter conditions for '%s': %s",
6635                       featureType, e.getMessage()));
6636       // return as much as was parsed up to the error
6637     }
6638   
6639     return result;
6640   }
6641
6642   /**
6643    * Adds feature match conditions to matcherSet as unmarshalled from XML
6644    * (possibly recursively for compound conditions)
6645    * 
6646    * @param matcherSet
6647    * @param matcherSetModel
6648    * @param and
6649    *          if true, multiple conditions are AND-ed, else they are OR-ed
6650    * @throws IllegalStateException
6651    *           if AND and OR conditions are mixed
6652    */
6653   protected static void parseFilterConditions(
6654           FeatureMatcherSetI matcherSet,
6655           jalview.xml.binding.jalview.FeatureMatcherSet matcherSetModel,
6656           boolean and)
6657   {
6658     jalview.xml.binding.jalview.FeatureMatcher mc = matcherSetModel
6659             .getMatchCondition();
6660     if (mc != null)
6661     {
6662       /*
6663        * single condition
6664        */
6665       FilterBy filterBy = mc.getBy();
6666       Condition cond = Condition.fromString(mc.getCondition());
6667       String pattern = mc.getValue();
6668       FeatureMatcherI matchCondition = null;
6669       if (filterBy == FilterBy.BY_LABEL)
6670       {
6671         matchCondition = FeatureMatcher.byLabel(cond, pattern);
6672       }
6673       else if (filterBy == FilterBy.BY_SCORE)
6674       {
6675         matchCondition = FeatureMatcher.byScore(cond, pattern);
6676   
6677       }
6678       else if (filterBy == FilterBy.BY_ATTRIBUTE)
6679       {
6680         final List<String> attributeName = mc.getAttributeName();
6681         String[] attNames = attributeName
6682                 .toArray(new String[attributeName.size()]);
6683         matchCondition = FeatureMatcher.byAttribute(cond, pattern,
6684                 attNames);
6685       }
6686   
6687       /*
6688        * note this throws IllegalStateException if AND-ing to a 
6689        * previously OR-ed compound condition, or vice versa
6690        */
6691       if (and)
6692       {
6693         matcherSet.and(matchCondition);
6694       }
6695       else
6696       {
6697         matcherSet.or(matchCondition);
6698       }
6699     }
6700     else
6701     {
6702       /*
6703        * compound condition
6704        */
6705       List<jalview.xml.binding.jalview.FeatureMatcherSet> matchers = matcherSetModel
6706               .getCompoundMatcher().getMatcherSet();
6707       boolean anded = matcherSetModel.getCompoundMatcher().isAnd();
6708       if (matchers.size() == 2)
6709       {
6710         parseFilterConditions(matcherSet, matchers.get(0), anded);
6711         parseFilterConditions(matcherSet, matchers.get(1), anded);
6712       }
6713       else
6714       {
6715         System.err.println("Malformed compound filter condition");
6716       }
6717     }
6718   }
6719
6720   /**
6721    * Loads one XML model of a feature colour to a Jalview object
6722    * 
6723    * @param colourModel
6724    * @return
6725    */
6726   public static FeatureColourI parseColour(Colour colourModel)
6727   {
6728     FeatureColourI colour = null;
6729   
6730     if (colourModel.getMax() != null)
6731     {
6732       Color mincol = null;
6733       Color maxcol = null;
6734       Color noValueColour = null;
6735   
6736       try
6737       {
6738         mincol = new Color(Integer.parseInt(colourModel.getMinRGB(), 16));
6739         maxcol = new Color(Integer.parseInt(colourModel.getRGB(), 16));
6740       } catch (Exception e)
6741       {
6742         Cache.log.warn("Couldn't parse out graduated feature color.", e);
6743       }
6744   
6745       NoValueColour noCol = colourModel.getNoValueColour();
6746       if (noCol == NoValueColour.MIN)
6747       {
6748         noValueColour = mincol;
6749       }
6750       else if (noCol == NoValueColour.MAX)
6751       {
6752         noValueColour = maxcol;
6753       }
6754   
6755       colour = new FeatureColour(maxcol, mincol, maxcol, noValueColour,
6756               safeFloat(colourModel.getMin()),
6757               safeFloat(colourModel.getMax()));
6758       final List<String> attributeName = colourModel.getAttributeName();
6759       String[] attributes = attributeName
6760               .toArray(new String[attributeName.size()]);
6761       if (attributes != null && attributes.length > 0)
6762       {
6763         colour.setAttributeName(attributes);
6764       }
6765       if (colourModel.isAutoScale() != null)
6766       {
6767         colour.setAutoScaled(colourModel.isAutoScale().booleanValue());
6768       }
6769       if (colourModel.isColourByLabel() != null)
6770       {
6771         colour.setColourByLabel(
6772                 colourModel.isColourByLabel().booleanValue());
6773       }
6774       if (colourModel.getThreshold() != null)
6775       {
6776         colour.setThreshold(colourModel.getThreshold().floatValue());
6777       }
6778       ThresholdType ttyp = colourModel.getThreshType();
6779       if (ttyp == ThresholdType.ABOVE)
6780       {
6781         colour.setAboveThreshold(true);
6782       }
6783       else if (ttyp == ThresholdType.BELOW)
6784       {
6785         colour.setBelowThreshold(true);
6786       }
6787     }
6788     else
6789     {
6790       Color color = new Color(Integer.parseInt(colourModel.getRGB(), 16));
6791       colour = new FeatureColour(color);
6792     }
6793   
6794     return colour;
6795   }
6796 }