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