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