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