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