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