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