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