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