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