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