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