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