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