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