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