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