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