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