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