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