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