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