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