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