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