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