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