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