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