JAL-892 tidy Varna panel names; JAL-1789 simplified mouseover
[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 java.awt.Rectangle;
24 import java.io.BufferedReader;
25 import java.io.DataInputStream;
26 import java.io.DataOutputStream;
27 import java.io.File;
28 import java.io.FileInputStream;
29 import java.io.FileOutputStream;
30 import java.io.IOException;
31 import java.io.InputStreamReader;
32 import java.io.OutputStreamWriter;
33 import java.io.PrintWriter;
34 import java.lang.reflect.InvocationTargetException;
35 import java.net.MalformedURLException;
36 import java.net.URL;
37 import java.util.ArrayList;
38 import java.util.Enumeration;
39 import java.util.HashMap;
40 import java.util.HashSet;
41 import java.util.Hashtable;
42 import java.util.IdentityHashMap;
43 import java.util.Iterator;
44 import java.util.LinkedHashMap;
45 import java.util.List;
46 import java.util.Map;
47 import java.util.Map.Entry;
48 import java.util.Set;
49 import java.util.StringTokenizer;
50 import java.util.Vector;
51 import java.util.jar.JarEntry;
52 import java.util.jar.JarInputStream;
53 import java.util.jar.JarOutputStream;
54
55 import javax.swing.JInternalFrame;
56 import javax.swing.JOptionPane;
57 import javax.swing.SwingUtilities;
58
59 import org.exolab.castor.xml.Marshaller;
60 import org.exolab.castor.xml.Unmarshaller;
61
62 import jalview.api.structures.JalviewStructureDisplayI;
63 import jalview.bin.Cache;
64 import jalview.datamodel.AlignedCodonFrame;
65 import jalview.datamodel.Alignment;
66 import jalview.datamodel.AlignmentAnnotation;
67 import jalview.datamodel.AlignmentI;
68 import jalview.datamodel.PDBEntry;
69 import jalview.datamodel.RnaViewerModel;
70 import jalview.datamodel.SequenceGroup;
71 import jalview.datamodel.SequenceI;
72 import jalview.datamodel.StructureViewerModel;
73 import jalview.datamodel.StructureViewerModel.StructureData;
74 import jalview.ext.varna.RnaModel;
75 import jalview.gui.StructureViewer.ViewerType;
76 import jalview.schemabinding.version2.AlcodMap;
77 import jalview.schemabinding.version2.AlcodonFrame;
78 import jalview.schemabinding.version2.Annotation;
79 import jalview.schemabinding.version2.AnnotationColours;
80 import jalview.schemabinding.version2.AnnotationElement;
81 import jalview.schemabinding.version2.CalcIdParam;
82 import jalview.schemabinding.version2.DBRef;
83 import jalview.schemabinding.version2.Features;
84 import jalview.schemabinding.version2.Group;
85 import jalview.schemabinding.version2.HiddenColumns;
86 import jalview.schemabinding.version2.JGroup;
87 import jalview.schemabinding.version2.JSeq;
88 import jalview.schemabinding.version2.JalviewModel;
89 import jalview.schemabinding.version2.JalviewModelSequence;
90 import jalview.schemabinding.version2.MapListFrom;
91 import jalview.schemabinding.version2.MapListTo;
92 import jalview.schemabinding.version2.Mapping;
93 import jalview.schemabinding.version2.MappingChoice;
94 import jalview.schemabinding.version2.OtherData;
95 import jalview.schemabinding.version2.PdbentryItem;
96 import jalview.schemabinding.version2.Pdbids;
97 import jalview.schemabinding.version2.Property;
98 import jalview.schemabinding.version2.RnaViewer;
99 import jalview.schemabinding.version2.SecondaryStructure;
100 import jalview.schemabinding.version2.Sequence;
101 import jalview.schemabinding.version2.SequenceSet;
102 import jalview.schemabinding.version2.SequenceSetProperties;
103 import jalview.schemabinding.version2.Setting;
104 import jalview.schemabinding.version2.StructureState;
105 import jalview.schemabinding.version2.ThresholdLine;
106 import jalview.schemabinding.version2.Tree;
107 import jalview.schemabinding.version2.UserColours;
108 import jalview.schemabinding.version2.Viewport;
109 import jalview.schemes.AnnotationColourGradient;
110 import jalview.schemes.ColourSchemeI;
111 import jalview.schemes.ColourSchemeProperty;
112 import jalview.schemes.GraduatedColor;
113 import jalview.schemes.ResidueColourScheme;
114 import jalview.schemes.ResidueProperties;
115 import jalview.schemes.UserColourScheme;
116 import jalview.structure.StructureSelectionManager;
117 import jalview.structures.models.AAStructureBindingModel;
118 import jalview.util.MessageManager;
119 import jalview.util.Platform;
120 import jalview.util.jarInputStreamProvider;
121 import jalview.viewmodel.AlignmentViewport;
122 import jalview.viewmodel.seqfeatures.FeatureRendererSettings;
123 import jalview.viewmodel.seqfeatures.FeaturesDisplayed;
124 import jalview.ws.jws2.Jws2Discoverer;
125 import jalview.ws.jws2.dm.AAConSettings;
126 import jalview.ws.jws2.jabaws2.Jws2Instance;
127 import jalview.ws.params.ArgumentI;
128 import jalview.ws.params.AutoCalcSetting;
129 import jalview.ws.params.WsParamSetI;
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     JInternalFrame[] frames = Desktop.desktop.getAllFrames();
1376     for (int f = frames.length - 1; f > -1; f--)
1377     {
1378       if (frames[f] instanceof AppVarna)
1379       {
1380         AppVarna varna = (AppVarna) frames[f];
1381         /*
1382          * link the sequence to every viewer that is showing it and is linked to
1383          * its alignment panel
1384          */
1385         if (varna.isListeningFor(jds) && ap == varna.getAlignmentPanel())
1386         {
1387           String viewId = varna.getViewId();
1388           RnaViewer rna = new RnaViewer();
1389           rna.setViewId(viewId);
1390           rna.setTitle(varna.getTitle());
1391           rna.setXpos(varna.getX());
1392           rna.setYpos(varna.getY());
1393           rna.setWidth(varna.getWidth());
1394           rna.setHeight(varna.getHeight());
1395           rna.setDividerLocation(varna.getDividerLocation());
1396           rna.setSelectedRna(varna.getSelectedIndex());
1397           jseq.addRnaViewer(rna);
1398
1399           /*
1400            * Store each Varna panel's state once in the project per sequence.
1401            * First time through only (storeDataset==false)
1402            */
1403           // boolean storeSessions = false;
1404           // String sequenceViewId = viewId + seqsToIds.get(jds);
1405           // if (!storeDataset && !viewIds.contains(sequenceViewId))
1406           // {
1407           // viewIds.add(sequenceViewId);
1408           // storeSessions = true;
1409           // }
1410           for (RnaModel model : varna.getModels())
1411           {
1412             if (model.seq == jds)
1413             {
1414               /*
1415                * VARNA saves each view (sequence or alignment secondary
1416                * structure, gapped or trimmed) as a separate XML file
1417                */
1418               String jarEntryName = rnaSessions.get(model);
1419               if (jarEntryName == null)
1420               {
1421
1422                 String varnaStateFile = varna.getStateInfo(model.rna);
1423                 jarEntryName = RNA_PREFIX + viewId + "_"
1424                       + nextCounter();
1425                 copyFileToJar(jout, varnaStateFile, jarEntryName);
1426                 rnaSessions.put(model, jarEntryName);
1427               }
1428               SecondaryStructure ss = new SecondaryStructure();
1429               String annotationId = varna.getAnnotation(jds).annotationId;
1430               ss.setAnnotationId(annotationId);
1431               ss.setViewerState(jarEntryName);
1432               ss.setGapped(model.gapped);
1433               ss.setTitle(model.title);
1434               rna.addSecondaryStructure(ss);
1435             }
1436           }
1437         }
1438       }
1439     }
1440   }
1441
1442   /**
1443    * Copy the contents of a file to a new entry added to the output jar
1444    * 
1445    * @param jout
1446    * @param infilePath
1447    * @param jarEntryName
1448    */
1449   protected void copyFileToJar(JarOutputStream jout, String infilePath,
1450           String jarEntryName)
1451   {
1452     DataInputStream dis = null;
1453     try
1454     {
1455       File file = new File(infilePath);
1456       if (file.exists() && jout != null)
1457       {
1458         dis = new DataInputStream(new FileInputStream(file));
1459         byte[] data = new byte[(int) file.length()];
1460         dis.readFully(data);
1461         writeJarEntry(jout, jarEntryName, data);
1462       }
1463     } catch (Exception ex)
1464     {
1465       ex.printStackTrace();
1466     } finally
1467     {
1468       if (dis != null)
1469       {
1470         try
1471         {
1472           dis.close();
1473         } catch (IOException e)
1474         {
1475           // ignore
1476         }
1477       }
1478     }
1479   }
1480
1481   /**
1482    * Write the data to a new entry of given name in the output jar file
1483    * 
1484    * @param jout
1485    * @param jarEntryName
1486    * @param data
1487    * @throws IOException
1488    */
1489   protected void writeJarEntry(JarOutputStream jout, String jarEntryName,
1490           byte[] data) throws IOException
1491   {
1492     if (jout != null)
1493     {
1494       System.out.println("Writing jar entry " + jarEntryName);
1495       jout.putNextEntry(new JarEntry(jarEntryName));
1496       DataOutputStream dout = new DataOutputStream(jout);
1497       dout.write(data, 0, data.length);
1498       dout.flush();
1499       jout.closeEntry();
1500     }
1501   }
1502
1503   /**
1504    * Save the state of a structure viewer
1505    * 
1506    * @param ap
1507    * @param jds
1508    * @param pdb
1509    *          the archive XML element under which to save the state
1510    * @param entry
1511    * @param viewIds
1512    * @param matchedFile
1513    * @param viewFrame
1514    * @return
1515    */
1516   protected String saveStructureState(AlignmentPanel ap, SequenceI jds,
1517           Pdbids pdb, PDBEntry entry, List<String> viewIds,
1518           String matchedFile, StructureViewerBase viewFrame)
1519   {
1520     final AAStructureBindingModel bindingModel = viewFrame.getBinding();
1521
1522     /*
1523      * Look for any bindings for this viewer to the PDB file of interest
1524      * (including part matches excluding chain id)
1525      */
1526     for (int peid = 0; peid < bindingModel.getPdbCount(); peid++)
1527     {
1528       final PDBEntry pdbentry = bindingModel.getPdbEntry(peid);
1529       final String pdbId = pdbentry.getId();
1530       if (!pdbId.equals(entry.getId())
1531               && !(entry.getId().length() > 4 && entry.getId()
1532                       .toLowerCase().startsWith(pdbId.toLowerCase())))
1533       {
1534         /*
1535          * not interested in a binding to a different PDB entry here
1536          */
1537         continue;
1538       }
1539       if (matchedFile == null)
1540       {
1541         matchedFile = pdbentry.getFile();
1542       }
1543       else if (!matchedFile.equals(pdbentry.getFile()))
1544       {
1545         Cache.log
1546                 .warn("Probably lost some PDB-Sequence mappings for this structure file (which apparently has same PDB Entry code): "
1547                         + pdbentry.getFile());
1548       }
1549       // record the
1550       // file so we
1551       // can get at it if the ID
1552       // match is ambiguous (e.g.
1553       // 1QIP==1qipA)
1554
1555       for (int smap = 0; smap < viewFrame.getBinding().getSequence()[peid].length; smap++)
1556       {
1557         // if (jal.findIndex(jmol.jmb.sequence[peid][smap]) > -1)
1558         if (jds == viewFrame.getBinding().getSequence()[peid][smap])
1559         {
1560           StructureState state = new StructureState();
1561           state.setVisible(true);
1562           state.setXpos(viewFrame.getX());
1563           state.setYpos(viewFrame.getY());
1564           state.setWidth(viewFrame.getWidth());
1565           state.setHeight(viewFrame.getHeight());
1566           final String viewId = viewFrame.getViewId();
1567           state.setViewId(viewId);
1568           state.setAlignwithAlignPanel(viewFrame.isUsedforaligment(ap));
1569           state.setColourwithAlignPanel(viewFrame.isUsedforcolourby(ap));
1570           state.setColourByJmol(viewFrame.isColouredByViewer());
1571           state.setType(viewFrame.getViewerType().toString());
1572           pdb.addStructureState(state);
1573         }
1574       }
1575     }
1576     return matchedFile;
1577   }
1578
1579   private AnnotationColours constructAnnotationColours(
1580           AnnotationColourGradient acg, List<UserColourScheme> userColours,
1581           JalviewModelSequence jms)
1582   {
1583     AnnotationColours ac = new AnnotationColours();
1584     ac.setAboveThreshold(acg.getAboveThreshold());
1585     ac.setThreshold(acg.getAnnotationThreshold());
1586     ac.setAnnotation(acg.getAnnotation());
1587     if (acg.getBaseColour() instanceof jalview.schemes.UserColourScheme)
1588     {
1589       ac.setColourScheme(setUserColourScheme(acg.getBaseColour(),
1590               userColours, jms));
1591     }
1592     else
1593     {
1594       ac.setColourScheme(ColourSchemeProperty.getColourName(acg
1595               .getBaseColour()));
1596     }
1597
1598     ac.setMaxColour(acg.getMaxColour().getRGB());
1599     ac.setMinColour(acg.getMinColour().getRGB());
1600     ac.setPerSequence(acg.isSeqAssociated());
1601     ac.setPredefinedColours(acg.isPredefinedColours());
1602     return ac;
1603   }
1604
1605   private void storeAlignmentAnnotation(AlignmentAnnotation[] aa,
1606           IdentityHashMap<SequenceGroup, String> groupRefs,
1607           AlignmentViewport av,
1608           Set<String> calcIdSet, boolean storeDS, SequenceSet vamsasSet)
1609   {
1610
1611     for (int i = 0; i < aa.length; i++)
1612     {
1613       Annotation an = new Annotation();
1614
1615       AlignmentAnnotation annotation = aa[i];
1616       if (annotation.annotationId != null)
1617       {
1618         annotationIds.put(annotation.annotationId, annotation);
1619       }
1620
1621       an.setId(annotation.annotationId);
1622
1623       an.setVisible(annotation.visible);
1624
1625       an.setDescription(annotation.description);
1626
1627       if (annotation.sequenceRef != null)
1628       {
1629         // 2.9 JAL-1781 xref on sequence id rather than name
1630         an.setSequenceRef(seqsToIds.get(annotation.sequenceRef));
1631       }
1632       if (annotation.groupRef != null)
1633       {
1634         String groupIdr = groupRefs.get(annotation.groupRef);
1635         if (groupIdr == null)
1636         {
1637           // make a locally unique String
1638           groupRefs.put(annotation.groupRef,
1639                   groupIdr = ("" + System.currentTimeMillis()
1640                           + annotation.groupRef.getName() + groupRefs.size()));
1641         }
1642         an.setGroupRef(groupIdr.toString());
1643       }
1644
1645       // store all visualization attributes for annotation
1646       an.setGraphHeight(annotation.graphHeight);
1647       an.setCentreColLabels(annotation.centreColLabels);
1648       an.setScaleColLabels(annotation.scaleColLabel);
1649       an.setShowAllColLabels(annotation.showAllColLabels);
1650       an.setBelowAlignment(annotation.belowAlignment);
1651
1652       if (annotation.graph > 0)
1653       {
1654         an.setGraph(true);
1655         an.setGraphType(annotation.graph);
1656         an.setGraphGroup(annotation.graphGroup);
1657         if (annotation.getThreshold() != null)
1658         {
1659           ThresholdLine line = new ThresholdLine();
1660           line.setLabel(annotation.getThreshold().label);
1661           line.setValue(annotation.getThreshold().value);
1662           line.setColour(annotation.getThreshold().colour.getRGB());
1663           an.setThresholdLine(line);
1664         }
1665       }
1666       else
1667       {
1668         an.setGraph(false);
1669       }
1670
1671       an.setLabel(annotation.label);
1672
1673       if (annotation == av.getAlignmentQualityAnnot()
1674               || annotation == av.getAlignmentConservationAnnotation()
1675               || annotation == av.getAlignmentConsensusAnnotation()
1676               || annotation.autoCalculated)
1677       {
1678         // new way of indicating autocalculated annotation -
1679         an.setAutoCalculated(annotation.autoCalculated);
1680       }
1681       if (annotation.hasScore())
1682       {
1683         an.setScore(annotation.getScore());
1684       }
1685
1686       if (annotation.getCalcId() != null)
1687       {
1688         calcIdSet.add(annotation.getCalcId());
1689         an.setCalcId(annotation.getCalcId());
1690       }
1691       if (annotation.hasProperties())
1692       {
1693         for (String pr : annotation.getProperties())
1694         {
1695           Property prop = new Property();
1696           prop.setName(pr);
1697           prop.setValue(annotation.getProperty(pr));
1698           an.addProperty(prop);
1699         }
1700       }
1701
1702       AnnotationElement ae;
1703       if (annotation.annotations != null)
1704       {
1705         an.setScoreOnly(false);
1706         for (int a = 0; a < annotation.annotations.length; a++)
1707         {
1708           if ((annotation == null) || (annotation.annotations[a] == null))
1709           {
1710             continue;
1711           }
1712
1713           ae = new AnnotationElement();
1714           if (annotation.annotations[a].description != null)
1715           {
1716             ae.setDescription(annotation.annotations[a].description);
1717           }
1718           if (annotation.annotations[a].displayCharacter != null)
1719           {
1720             ae.setDisplayCharacter(annotation.annotations[a].displayCharacter);
1721           }
1722
1723           if (!Float.isNaN(annotation.annotations[a].value))
1724           {
1725             ae.setValue(annotation.annotations[a].value);
1726           }
1727
1728           ae.setPosition(a);
1729           if (annotation.annotations[a].secondaryStructure > ' ')
1730           {
1731             ae.setSecondaryStructure(annotation.annotations[a].secondaryStructure
1732                     + "");
1733           }
1734
1735           if (annotation.annotations[a].colour != null
1736                   && annotation.annotations[a].colour != java.awt.Color.black)
1737           {
1738             ae.setColour(annotation.annotations[a].colour.getRGB());
1739           }
1740
1741           an.addAnnotationElement(ae);
1742           if (annotation.autoCalculated)
1743           {
1744             // only write one non-null entry into the annotation row -
1745             // sufficient to get the visualization attributes necessary to
1746             // display data
1747             continue;
1748           }
1749         }
1750       }
1751       else
1752       {
1753         an.setScoreOnly(true);
1754       }
1755       if (!storeDS || (storeDS && !annotation.autoCalculated))
1756       {
1757         // skip autocalculated annotation - these are only provided for
1758         // alignments
1759         vamsasSet.addAnnotation(an);
1760       }
1761     }
1762
1763   }
1764
1765   private CalcIdParam createCalcIdParam(String calcId, AlignViewport av)
1766   {
1767     AutoCalcSetting settings = av.getCalcIdSettingsFor(calcId);
1768     if (settings != null)
1769     {
1770       CalcIdParam vCalcIdParam = new CalcIdParam();
1771       vCalcIdParam.setCalcId(calcId);
1772       vCalcIdParam.addServiceURL(settings.getServiceURI());
1773       // generic URI allowing a third party to resolve another instance of the
1774       // service used for this calculation
1775       for (String urls : settings.getServiceURLs())
1776       {
1777         vCalcIdParam.addServiceURL(urls);
1778       }
1779       vCalcIdParam.setVersion("1.0");
1780       if (settings.getPreset() != null)
1781       {
1782         WsParamSetI setting = settings.getPreset();
1783         vCalcIdParam.setName(setting.getName());
1784         vCalcIdParam.setDescription(setting.getDescription());
1785       }
1786       else
1787       {
1788         vCalcIdParam.setName("");
1789         vCalcIdParam.setDescription("Last used parameters");
1790       }
1791       // need to be able to recover 1) settings 2) user-defined presets or
1792       // recreate settings from preset 3) predefined settings provided by
1793       // service - or settings that can be transferred (or discarded)
1794       vCalcIdParam.setParameters(settings.getWsParamFile().replace("\n",
1795               "|\\n|"));
1796       vCalcIdParam.setAutoUpdate(settings.isAutoUpdate());
1797       // todo - decide if updateImmediately is needed for any projects.
1798
1799       return vCalcIdParam;
1800     }
1801     return null;
1802   }
1803
1804   private boolean recoverCalcIdParam(CalcIdParam calcIdParam,
1805           AlignViewport av)
1806   {
1807     if (calcIdParam.getVersion().equals("1.0"))
1808     {
1809       Jws2Instance service = Jws2Discoverer.getDiscoverer()
1810               .getPreferredServiceFor(calcIdParam.getServiceURL());
1811       if (service != null)
1812       {
1813         WsParamSetI parmSet = null;
1814         try
1815         {
1816           parmSet = service.getParamStore().parseServiceParameterFile(
1817                   calcIdParam.getName(), calcIdParam.getDescription(),
1818                   calcIdParam.getServiceURL(),
1819                   calcIdParam.getParameters().replace("|\\n|", "\n"));
1820         } catch (IOException x)
1821         {
1822           warn("Couldn't parse parameter data for "
1823                   + calcIdParam.getCalcId(), x);
1824           return false;
1825         }
1826         List<ArgumentI> argList = null;
1827         if (calcIdParam.getName().length() > 0)
1828         {
1829           parmSet = service.getParamStore()
1830                   .getPreset(calcIdParam.getName());
1831           if (parmSet != null)
1832           {
1833             // TODO : check we have a good match with settings in AACon -
1834             // otherwise we'll need to create a new preset
1835           }
1836         }
1837         else
1838         {
1839           argList = parmSet.getArguments();
1840           parmSet = null;
1841         }
1842         AAConSettings settings = new AAConSettings(
1843                 calcIdParam.isAutoUpdate(), service, parmSet, argList);
1844         av.setCalcIdSettingsFor(calcIdParam.getCalcId(), settings,
1845                 calcIdParam.isNeedsUpdate());
1846         return true;
1847       }
1848       else
1849       {
1850         warn("Cannot resolve a service for the parameters used in this project. Try configuring a JABAWS server.");
1851         return false;
1852       }
1853     }
1854     throw new Error(MessageManager.formatMessage(
1855             "error.unsupported_version_calcIdparam", new Object[]
1856             { calcIdParam.toString() }));
1857   }
1858
1859   /**
1860    * External mapping between jalview objects and objects yielding a valid and
1861    * unique object ID string. This is null for normal Jalview project IO, but
1862    * non-null when a jalview project is being read or written as part of a
1863    * vamsas session.
1864    */
1865   IdentityHashMap jv2vobj = null;
1866
1867   /**
1868    * Construct a unique ID for jvobj using either existing bindings or if none
1869    * exist, the result of the hashcode call for the object.
1870    * 
1871    * @param jvobj
1872    *          jalview data object
1873    * @return unique ID for referring to jvobj
1874    */
1875   private String makeHashCode(Object jvobj, String altCode)
1876   {
1877     if (jv2vobj != null)
1878     {
1879       Object id = jv2vobj.get(jvobj);
1880       if (id != null)
1881       {
1882         return id.toString();
1883       }
1884       // check string ID mappings
1885       if (jvids2vobj != null && jvobj instanceof String)
1886       {
1887         id = jvids2vobj.get(jvobj);
1888       }
1889       if (id != null)
1890       {
1891         return id.toString();
1892       }
1893       // give up and warn that something has gone wrong
1894       warn("Cannot find ID for object in external mapping : " + jvobj);
1895     }
1896     return altCode;
1897   }
1898
1899   /**
1900    * return local jalview object mapped to ID, if it exists
1901    * 
1902    * @param idcode
1903    *          (may be null)
1904    * @return null or object bound to idcode
1905    */
1906   private Object retrieveExistingObj(String idcode)
1907   {
1908     if (idcode != null && vobj2jv != null)
1909     {
1910       return vobj2jv.get(idcode);
1911     }
1912     return null;
1913   }
1914
1915   /**
1916    * binding from ID strings from external mapping table to jalview data model
1917    * objects.
1918    */
1919   private Hashtable vobj2jv;
1920
1921   private Sequence createVamsasSequence(String id, SequenceI jds)
1922   {
1923     return createVamsasSequence(true, id, jds, null);
1924   }
1925
1926   private Sequence createVamsasSequence(boolean recurse, String id,
1927           SequenceI jds, SequenceI parentseq)
1928   {
1929     Sequence vamsasSeq = new Sequence();
1930     vamsasSeq.setId(id);
1931     vamsasSeq.setName(jds.getName());
1932     vamsasSeq.setSequence(jds.getSequenceAsString());
1933     vamsasSeq.setDescription(jds.getDescription());
1934     jalview.datamodel.DBRefEntry[] dbrefs = null;
1935     if (jds.getDatasetSequence() != null)
1936     {
1937       vamsasSeq.setDsseqid(seqHash(jds.getDatasetSequence()));
1938       if (jds.getDatasetSequence().getDBRef() != null)
1939       {
1940         dbrefs = jds.getDatasetSequence().getDBRef();
1941       }
1942     }
1943     else
1944     {
1945       vamsasSeq.setDsseqid(id); // so we can tell which sequences really are
1946       // dataset sequences only
1947       dbrefs = jds.getDBRef();
1948     }
1949     if (dbrefs != null)
1950     {
1951       for (int d = 0; d < dbrefs.length; d++)
1952       {
1953         DBRef dbref = new DBRef();
1954         dbref.setSource(dbrefs[d].getSource());
1955         dbref.setVersion(dbrefs[d].getVersion());
1956         dbref.setAccessionId(dbrefs[d].getAccessionId());
1957         if (dbrefs[d].hasMap())
1958         {
1959           Mapping mp = createVamsasMapping(dbrefs[d].getMap(), parentseq,
1960                   jds, recurse);
1961           dbref.setMapping(mp);
1962         }
1963         vamsasSeq.addDBRef(dbref);
1964       }
1965     }
1966     return vamsasSeq;
1967   }
1968
1969   private Mapping createVamsasMapping(jalview.datamodel.Mapping jmp,
1970           SequenceI parentseq, SequenceI jds, boolean recurse)
1971   {
1972     Mapping mp = null;
1973     if (jmp.getMap() != null)
1974     {
1975       mp = new Mapping();
1976
1977       jalview.util.MapList mlst = jmp.getMap();
1978       List<int[]> r = mlst.getFromRanges();
1979       for (int[] range : r)
1980       {
1981         MapListFrom mfrom = new MapListFrom();
1982         mfrom.setStart(range[0]);
1983         mfrom.setEnd(range[1]);
1984         mp.addMapListFrom(mfrom);
1985       }
1986       r = mlst.getToRanges();
1987       for (int[] range : r)
1988       {
1989         MapListTo mto = new MapListTo();
1990         mto.setStart(range[0]);
1991         mto.setEnd(range[1]);
1992         mp.addMapListTo(mto);
1993       }
1994       mp.setMapFromUnit(mlst.getFromRatio());
1995       mp.setMapToUnit(mlst.getToRatio());
1996       if (jmp.getTo() != null)
1997       {
1998         MappingChoice mpc = new MappingChoice();
1999         if (recurse
2000                 && (parentseq != jmp.getTo() || parentseq
2001                         .getDatasetSequence() != jmp.getTo()))
2002         {
2003           mpc.setSequence(createVamsasSequence(false, seqHash(jmp.getTo()),
2004                   jmp.getTo(), jds));
2005         }
2006         else
2007         {
2008           String jmpid = "";
2009           SequenceI ps = null;
2010           if (parentseq != jmp.getTo()
2011                   && parentseq.getDatasetSequence() != jmp.getTo())
2012           {
2013             // chaining dbref rather than a handshaking one
2014             jmpid = seqHash(ps = jmp.getTo());
2015           }
2016           else
2017           {
2018             jmpid = seqHash(ps = parentseq);
2019           }
2020           mpc.setDseqFor(jmpid);
2021           if (!seqRefIds.containsKey(mpc.getDseqFor()))
2022           {
2023             jalview.bin.Cache.log.debug("creatign new DseqFor ID");
2024             seqRefIds.put(mpc.getDseqFor(), ps);
2025           }
2026           else
2027           {
2028             jalview.bin.Cache.log.debug("reusing DseqFor ID");
2029           }
2030         }
2031         mp.setMappingChoice(mpc);
2032       }
2033     }
2034     return mp;
2035   }
2036
2037   String setUserColourScheme(jalview.schemes.ColourSchemeI cs,
2038           List<UserColourScheme> userColours, JalviewModelSequence jms)
2039   {
2040     String id = null;
2041     jalview.schemes.UserColourScheme ucs = (jalview.schemes.UserColourScheme) cs;
2042     boolean newucs = false;
2043     if (!userColours.contains(ucs))
2044     {
2045       userColours.add(ucs);
2046       newucs = true;
2047     }
2048     id = "ucs" + userColours.indexOf(ucs);
2049     if (newucs)
2050     {
2051       // actually create the scheme's entry in the XML model
2052       java.awt.Color[] colours = ucs.getColours();
2053       jalview.schemabinding.version2.UserColours uc = new jalview.schemabinding.version2.UserColours();
2054       jalview.schemabinding.version2.UserColourScheme jbucs = new jalview.schemabinding.version2.UserColourScheme();
2055
2056       for (int i = 0; i < colours.length; i++)
2057       {
2058         jalview.schemabinding.version2.Colour col = new jalview.schemabinding.version2.Colour();
2059         col.setName(ResidueProperties.aa[i]);
2060         col.setRGB(jalview.util.Format.getHexString(colours[i]));
2061         jbucs.addColour(col);
2062       }
2063       if (ucs.getLowerCaseColours() != null)
2064       {
2065         colours = ucs.getLowerCaseColours();
2066         for (int i = 0; i < colours.length; i++)
2067         {
2068           jalview.schemabinding.version2.Colour col = new jalview.schemabinding.version2.Colour();
2069           col.setName(ResidueProperties.aa[i].toLowerCase());
2070           col.setRGB(jalview.util.Format.getHexString(colours[i]));
2071           jbucs.addColour(col);
2072         }
2073       }
2074
2075       uc.setId(id);
2076       uc.setUserColourScheme(jbucs);
2077       jms.addUserColours(uc);
2078     }
2079
2080     return id;
2081   }
2082
2083   jalview.schemes.UserColourScheme getUserColourScheme(
2084           JalviewModelSequence jms, String id)
2085   {
2086     UserColours[] uc = jms.getUserColours();
2087     UserColours colours = null;
2088
2089     for (int i = 0; i < uc.length; i++)
2090     {
2091       if (uc[i].getId().equals(id))
2092       {
2093         colours = uc[i];
2094
2095         break;
2096       }
2097     }
2098
2099     java.awt.Color[] newColours = new java.awt.Color[24];
2100
2101     for (int i = 0; i < 24; i++)
2102     {
2103       newColours[i] = new java.awt.Color(Integer.parseInt(colours
2104               .getUserColourScheme().getColour(i).getRGB(), 16));
2105     }
2106
2107     jalview.schemes.UserColourScheme ucs = new jalview.schemes.UserColourScheme(
2108             newColours);
2109
2110     if (colours.getUserColourScheme().getColourCount() > 24)
2111     {
2112       newColours = new java.awt.Color[23];
2113       for (int i = 0; i < 23; i++)
2114       {
2115         newColours[i] = new java.awt.Color(Integer.parseInt(colours
2116                 .getUserColourScheme().getColour(i + 24).getRGB(), 16));
2117       }
2118       ucs.setLowerCaseColours(newColours);
2119     }
2120
2121     return ucs;
2122   }
2123
2124   /**
2125    * contains last error message (if any) encountered by XML loader.
2126    */
2127   String errorMessage = null;
2128
2129   /**
2130    * flag to control whether the Jalview2XML_V1 parser should be deferred to if
2131    * exceptions are raised during project XML parsing
2132    */
2133   public boolean attemptversion1parse = true;
2134
2135   /**
2136    * Load a jalview project archive from a jar file
2137    * 
2138    * @param file
2139    *          - HTTP URL or filename
2140    */
2141   public AlignFrame loadJalviewAlign(final String file)
2142   {
2143
2144     jalview.gui.AlignFrame af = null;
2145
2146     try
2147     {
2148       // create list to store references for any new Jmol viewers created
2149       newStructureViewers = new Vector<JalviewStructureDisplayI>();
2150       // UNMARSHALLER SEEMS TO CLOSE JARINPUTSTREAM, MOST ANNOYING
2151       // Workaround is to make sure caller implements the JarInputStreamProvider
2152       // interface
2153       // so we can re-open the jar input stream for each entry.
2154
2155       jarInputStreamProvider jprovider = createjarInputStreamProvider(file);
2156       af = loadJalviewAlign(jprovider);
2157
2158     } catch (MalformedURLException e)
2159     {
2160       errorMessage = "Invalid URL format for '" + file + "'";
2161       reportErrors();
2162     } finally
2163     {
2164       try
2165       {
2166         SwingUtilities.invokeAndWait(new Runnable()
2167         {
2168           public void run()
2169           {
2170             setLoadingFinishedForNewStructureViewers();
2171           };
2172         });
2173       } catch (Exception x)
2174       {
2175         System.err.println("Error loading alignment: " + x.getMessage());
2176       }
2177     }
2178     return af;
2179   }
2180
2181   private jarInputStreamProvider createjarInputStreamProvider(
2182           final String file) throws MalformedURLException
2183   {
2184     URL url = null;
2185     errorMessage = null;
2186     uniqueSetSuffix = null;
2187     seqRefIds = null;
2188     viewportsAdded.clear();
2189     frefedSequence = null;
2190
2191     if (file.startsWith("http://"))
2192     {
2193       url = new URL(file);
2194     }
2195     final URL _url = url;
2196     return new jarInputStreamProvider()
2197     {
2198
2199       @Override
2200       public JarInputStream getJarInputStream() throws IOException
2201       {
2202         if (_url != null)
2203         {
2204           return new JarInputStream(_url.openStream());
2205         }
2206         else
2207         {
2208           return new JarInputStream(new FileInputStream(file));
2209         }
2210       }
2211
2212       @Override
2213       public String getFilename()
2214       {
2215         return file;
2216       }
2217     };
2218   }
2219
2220   /**
2221    * Recover jalview session from a jalview project archive. Caller may
2222    * initialise uniqueSetSuffix, seqRefIds, viewportsAdded and frefedSequence
2223    * themselves. Any null fields will be initialised with default values,
2224    * non-null fields are left alone.
2225    * 
2226    * @param jprovider
2227    * @return
2228    */
2229   public AlignFrame loadJalviewAlign(final jarInputStreamProvider jprovider)
2230   {
2231     errorMessage = null;
2232     if (uniqueSetSuffix == null)
2233     {
2234       uniqueSetSuffix = System.currentTimeMillis() % 100000 + "";
2235     }
2236     if (seqRefIds == null)
2237     {
2238       seqRefIds = new HashMap<String, SequenceI>();
2239     }
2240     if (frefedSequence == null)
2241     {
2242       frefedSequence = new Vector();
2243     }
2244
2245     AlignFrame af = null, _af = null;
2246     Map<String, AlignFrame> gatherToThisFrame = new HashMap<String, AlignFrame>();
2247     final String file = jprovider.getFilename();
2248     try
2249     {
2250       JarInputStream jin = null;
2251       JarEntry jarentry = null;
2252       int entryCount = 1;
2253
2254       do
2255       {
2256         jin = jprovider.getJarInputStream();
2257         for (int i = 0; i < entryCount; i++)
2258         {
2259           jarentry = jin.getNextJarEntry();
2260         }
2261
2262         if (jarentry != null && jarentry.getName().endsWith(".xml"))
2263         {
2264           InputStreamReader in = new InputStreamReader(jin, UTF_8);
2265           JalviewModel object = new JalviewModel();
2266
2267           Unmarshaller unmar = new Unmarshaller(object);
2268           unmar.setValidation(false);
2269           object = (JalviewModel) unmar.unmarshal(in);
2270           if (true) // !skipViewport(object))
2271           {
2272             _af = loadFromObject(object, file, true, jprovider);
2273             if (object.getJalviewModelSequence().getViewportCount() > 0)
2274             {
2275               af = _af;
2276               if (af.viewport.isGatherViewsHere())
2277               {
2278                 gatherToThisFrame.put(af.viewport.getSequenceSetId(), af);
2279               }
2280             }
2281           }
2282           entryCount++;
2283         }
2284         else if (jarentry != null)
2285         {
2286           // Some other file here.
2287           entryCount++;
2288         }
2289       } while (jarentry != null);
2290       resolveFrefedSequences();
2291     } catch (IOException ex)
2292     {
2293       ex.printStackTrace();
2294       errorMessage = "Couldn't locate Jalview XML file : " + file;
2295       System.err.println("Exception whilst loading jalview XML file : "
2296               + ex + "\n");
2297     } catch (Exception ex)
2298     {
2299       System.err.println("Parsing as Jalview Version 2 file failed.");
2300       ex.printStackTrace(System.err);
2301       if (attemptversion1parse)
2302       {
2303         // Is Version 1 Jar file?
2304         try
2305         {
2306           af = new Jalview2XML_V1(raiseGUI).LoadJalviewAlign(jprovider);
2307         } catch (Exception ex2)
2308         {
2309           System.err.println("Exception whilst loading as jalviewXMLV1:");
2310           ex2.printStackTrace();
2311           af = null;
2312         }
2313       }
2314       if (Desktop.instance != null)
2315       {
2316         Desktop.instance.stopLoading();
2317       }
2318       if (af != null)
2319       {
2320         System.out.println("Successfully loaded archive file");
2321         return af;
2322       }
2323       ex.printStackTrace();
2324
2325       System.err.println("Exception whilst loading jalview XML file : "
2326               + ex + "\n");
2327     } catch (OutOfMemoryError e)
2328     {
2329       // Don't use the OOM Window here
2330       errorMessage = "Out of memory loading jalview XML file";
2331       System.err.println("Out of memory whilst loading jalview XML file");
2332       e.printStackTrace();
2333     }
2334
2335     if (Desktop.instance != null)
2336     {
2337       Desktop.instance.stopLoading();
2338     }
2339
2340     /*
2341      * Regather multiple views (with the same sequence set id) to the frame (if
2342      * any) that is flagged as the one to gather to, i.e. convert them to tabbed
2343      * views instead of separate frames. Note this doesn't restore a state where
2344      * some expanded views in turn have tabbed views - the last "first tab" read
2345      * in will play the role of gatherer for all.
2346      */
2347     for (AlignFrame fr : gatherToThisFrame.values())
2348     {
2349       Desktop.instance.gatherViews(fr);
2350     }
2351
2352     restoreSplitFrames();
2353
2354     if (errorMessage != null)
2355     {
2356       reportErrors();
2357     }
2358     return af;
2359   }
2360
2361   /**
2362    * Try to reconstruct and display SplitFrame windows, where each contains
2363    * complementary dna and protein alignments. Done by pairing up AlignFrame
2364    * objects (created earlier) which have complementary viewport ids associated.
2365    */
2366   protected void restoreSplitFrames()
2367   {
2368     List<SplitFrame> gatherTo = new ArrayList<SplitFrame>();
2369     List<AlignFrame> addedToSplitFrames = new ArrayList<AlignFrame>();
2370     Map<String, AlignFrame> dna = new HashMap<String, AlignFrame>();
2371
2372     /*
2373      * Identify the DNA alignments
2374      */
2375     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
2376             .entrySet())
2377     {
2378       AlignFrame af = candidate.getValue();
2379       if (af.getViewport().getAlignment().isNucleotide())
2380       {
2381         dna.put(candidate.getKey().getId(), af);
2382       }
2383     }
2384
2385     /*
2386      * Try to match up the protein complements
2387      */
2388     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
2389             .entrySet())
2390     {
2391       AlignFrame af = candidate.getValue();
2392       if (!af.getViewport().getAlignment().isNucleotide())
2393       {
2394         String complementId = candidate.getKey().getComplementId();
2395         // only non-null complements should be in the Map
2396         if (complementId != null && dna.containsKey(complementId))
2397         {
2398           final AlignFrame dnaFrame = dna.get(complementId);
2399           SplitFrame sf = createSplitFrame(dnaFrame, af);
2400           addedToSplitFrames.add(dnaFrame);
2401           addedToSplitFrames.add(af);
2402           if (af.viewport.isGatherViewsHere())
2403           {
2404             gatherTo.add(sf);
2405           }
2406         }
2407       }
2408     }
2409
2410     /*
2411      * Open any that we failed to pair up (which shouldn't happen!) as
2412      * standalone AlignFrame's.
2413      */
2414     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
2415             .entrySet())
2416     {
2417       AlignFrame af = candidate.getValue();
2418       if (!addedToSplitFrames.contains(af)) {
2419         Viewport view = candidate.getKey();
2420         Desktop.addInternalFrame(af, view.getTitle(), view.getWidth(),
2421                 view.getHeight());
2422         System.err.println("Failed to restore view " + view.getTitle()
2423                 + " to split frame");
2424       }
2425     }
2426
2427     /*
2428      * Gather back into tabbed views as flagged.
2429      */
2430     for (SplitFrame sf : gatherTo)
2431     {
2432       Desktop.instance.gatherViews(sf);
2433     }
2434
2435     splitFrameCandidates.clear();
2436   }
2437
2438   /**
2439    * Construct and display one SplitFrame holding DNA and protein alignments.
2440    * 
2441    * @param dnaFrame
2442    * @param proteinFrame
2443    * @return
2444    */
2445   protected SplitFrame createSplitFrame(AlignFrame dnaFrame,
2446           AlignFrame proteinFrame)
2447   {
2448     SplitFrame splitFrame = new SplitFrame(dnaFrame, proteinFrame);
2449     String title = MessageManager.getString("label.linked_view_title");
2450     int width = (int) dnaFrame.getBounds().getWidth();
2451     int height = (int) (dnaFrame.getBounds().getHeight()
2452             + proteinFrame.getBounds().getHeight() + 50);
2453     Desktop.addInternalFrame(splitFrame, title, width, height);
2454
2455     /*
2456      * And compute cDNA consensus (couldn't do earlier with consensus as
2457      * mappings were not yet present)
2458      */
2459     proteinFrame.viewport.alignmentChanged(proteinFrame.alignPanel);
2460
2461     return splitFrame;
2462   }
2463
2464   /**
2465    * check errorMessage for a valid error message and raise an error box in the
2466    * GUI or write the current errorMessage to stderr and then clear the error
2467    * state.
2468    */
2469   protected void reportErrors()
2470   {
2471     reportErrors(false);
2472   }
2473
2474   protected void reportErrors(final boolean saving)
2475   {
2476     if (errorMessage != null)
2477     {
2478       final String finalErrorMessage = errorMessage;
2479       if (raiseGUI)
2480       {
2481         javax.swing.SwingUtilities.invokeLater(new Runnable()
2482         {
2483           @Override
2484           public void run()
2485           {
2486             JOptionPane.showInternalMessageDialog(Desktop.desktop,
2487                     finalErrorMessage, "Error "
2488                             + (saving ? "saving" : "loading")
2489                             + " Jalview file", JOptionPane.WARNING_MESSAGE);
2490           }
2491         });
2492       }
2493       else
2494       {
2495         System.err.println("Problem loading Jalview file: " + errorMessage);
2496       }
2497     }
2498     errorMessage = null;
2499   }
2500
2501   Map<String, String> alreadyLoadedPDB = new HashMap<String, String>();
2502
2503   /**
2504    * when set, local views will be updated from view stored in JalviewXML
2505    * Currently (28th Sep 2008) things will go horribly wrong in vamsas document
2506    * sync if this is set to true.
2507    */
2508   private final boolean updateLocalViews = false;
2509
2510   /**
2511    * Returns the path to a temporary file holding the PDB file for the given PDB
2512    * id. The first time of asking, searches for a file of that name in the
2513    * Jalview project jar, and copies it to a new temporary file. Any repeat
2514    * requests just return the path to the file previously created.
2515    * 
2516    * @param jprovider
2517    * @param pdbId
2518    * @return
2519    */
2520   String loadPDBFile(jarInputStreamProvider jprovider, String pdbId)
2521   {
2522     if (alreadyLoadedPDB.containsKey(pdbId))
2523     {
2524       return alreadyLoadedPDB.get(pdbId).toString();
2525     }
2526
2527     String tempFile = copyJarEntry(jprovider, pdbId, "jalview_pdb");
2528     if (tempFile != null)
2529     {
2530       alreadyLoadedPDB.put(pdbId, tempFile);
2531     }
2532     return tempFile;
2533   }
2534
2535   /**
2536    * Copies the jar entry of given name to a new temporary file and returns the
2537    * path to the file, or null if the entry is not found.
2538    * 
2539    * @param jprovider
2540    * @param jarEntryName
2541    * @param prefix
2542    *          a prefix for the temporary file name, must be at least three
2543    *          characters long
2544    * @return
2545    */
2546   protected String copyJarEntry(jarInputStreamProvider jprovider,
2547           String jarEntryName, String prefix)
2548   {
2549     BufferedReader in = null;
2550     PrintWriter out = null;
2551
2552     try
2553     {
2554       JarInputStream jin = jprovider.getJarInputStream();
2555       /*
2556        * if (jprovider.startsWith("http://")) { jin = new JarInputStream(new
2557        * URL(jprovider).openStream()); } else { jin = new JarInputStream(new
2558        * FileInputStream(jprovider)); }
2559        */
2560
2561       JarEntry entry = null;
2562       do
2563       {
2564         entry = jin.getNextJarEntry();
2565       } while (entry != null && !entry.getName().equals(jarEntryName));
2566       if (entry != null)
2567       {
2568         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
2569         File outFile = File.createTempFile(prefix, ".tmp");
2570         outFile.deleteOnExit();
2571         out = new PrintWriter(new FileOutputStream(outFile));
2572         String data;
2573
2574         while ((data = in.readLine()) != null)
2575         {
2576           out.println(data);
2577         }
2578         out.flush();
2579         String t = outFile.getAbsolutePath();
2580         return t;
2581       }
2582       else
2583       {
2584         warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
2585       }
2586     } catch (Exception ex)
2587     {
2588       ex.printStackTrace();
2589     } finally
2590     {
2591       if (in != null)
2592       {
2593         try
2594         {
2595           in.close();
2596         } catch (IOException e)
2597         {
2598           // ignore
2599         }
2600       }
2601       if (out != null)
2602       {
2603         out.close();
2604       }
2605     }
2606
2607     return null;
2608   }
2609
2610   private class JvAnnotRow
2611   {
2612     public JvAnnotRow(int i, AlignmentAnnotation jaa)
2613     {
2614       order = i;
2615       template = jaa;
2616     }
2617
2618     /**
2619      * persisted version of annotation row from which to take vis properties
2620      */
2621     public jalview.datamodel.AlignmentAnnotation template;
2622
2623     /**
2624      * original position of the annotation row in the alignment
2625      */
2626     public int order;
2627   }
2628
2629   /**
2630    * Load alignment frame from jalview XML DOM object
2631    * 
2632    * @param object
2633    *          DOM
2634    * @param file
2635    *          filename source string
2636    * @param loadTreesAndStructures
2637    *          when false only create Viewport
2638    * @param jprovider
2639    *          data source provider
2640    * @return alignment frame created from view stored in DOM
2641    */
2642   AlignFrame loadFromObject(JalviewModel object, String file,
2643           boolean loadTreesAndStructures, jarInputStreamProvider jprovider)
2644   {
2645     SequenceSet vamsasSet = object.getVamsasModel().getSequenceSet(0);
2646     Sequence[] vamsasSeq = vamsasSet.getSequence();
2647
2648     JalviewModelSequence jms = object.getJalviewModelSequence();
2649
2650     Viewport view = (jms.getViewportCount() > 0) ? jms.getViewport(0)
2651             : null;
2652
2653     // ////////////////////////////////
2654     // LOAD SEQUENCES
2655
2656     List<SequenceI> hiddenSeqs = null;
2657     jalview.datamodel.Sequence jseq;
2658
2659     List<SequenceI> tmpseqs = new ArrayList<SequenceI>();
2660
2661     boolean multipleView = false;
2662
2663     JSeq[] jseqs = object.getJalviewModelSequence().getJSeq();
2664     int vi = 0; // counter in vamsasSeq array
2665     for (int i = 0; i < jseqs.length; i++)
2666     {
2667       String seqId = jseqs[i].getId();
2668
2669       if (seqRefIds.get(seqId) != null)
2670       {
2671         tmpseqs.add(seqRefIds.get(seqId));
2672         multipleView = true;
2673       }
2674       else
2675       {
2676         jseq = new jalview.datamodel.Sequence(vamsasSeq[vi].getName(),
2677                 vamsasSeq[vi].getSequence());
2678         jseq.setDescription(vamsasSeq[vi].getDescription());
2679         jseq.setStart(jseqs[i].getStart());
2680         jseq.setEnd(jseqs[i].getEnd());
2681         jseq.setVamsasId(uniqueSetSuffix + seqId);
2682         seqRefIds.put(vamsasSeq[vi].getId(), jseq);
2683         tmpseqs.add(jseq);
2684         vi++;
2685       }
2686
2687       if (jseqs[i].getHidden())
2688       {
2689         if (hiddenSeqs == null)
2690         {
2691           hiddenSeqs = new ArrayList<SequenceI>();
2692         }
2693
2694         hiddenSeqs.add(seqRefIds.get(seqId));
2695       }
2696
2697     }
2698
2699     // /
2700     // Create the alignment object from the sequence set
2701     // ///////////////////////////////
2702     SequenceI[] orderedSeqs = tmpseqs
2703             .toArray(new SequenceI[tmpseqs.size()]);
2704
2705     Alignment al = new Alignment(orderedSeqs);
2706
2707     // / Add the alignment properties
2708     for (int i = 0; i < vamsasSet.getSequenceSetPropertiesCount(); i++)
2709     {
2710       SequenceSetProperties ssp = vamsasSet.getSequenceSetProperties(i);
2711       al.setProperty(ssp.getKey(), ssp.getValue());
2712     }
2713
2714     // /
2715     // SequenceFeatures are added to the DatasetSequence,
2716     // so we must create or recover the dataset before loading features
2717     // ///////////////////////////////
2718     if (vamsasSet.getDatasetId() == null || vamsasSet.getDatasetId() == "")
2719     {
2720       // older jalview projects do not have a dataset id.
2721       al.setDataset(null);
2722     }
2723     else
2724     {
2725       // recover dataset - passing on flag indicating if this a 'viewless'
2726       // sequence set (a.k.a. a stored dataset for the project)
2727       recoverDatasetFor(vamsasSet, al, object.getJalviewModelSequence()
2728               .getViewportCount() == 0);
2729     }
2730     // ///////////////////////////////
2731
2732     Hashtable pdbloaded = new Hashtable(); // TODO nothing writes to this??
2733     if (!multipleView)
2734     {
2735       // load sequence features, database references and any associated PDB
2736       // structures for the alignment
2737       for (int i = 0; i < vamsasSeq.length; i++)
2738       {
2739         if (jseqs[i].getFeaturesCount() > 0)
2740         {
2741           Features[] features = jseqs[i].getFeatures();
2742           for (int f = 0; f < features.length; f++)
2743           {
2744             jalview.datamodel.SequenceFeature sf = new jalview.datamodel.SequenceFeature(
2745                     features[f].getType(), features[f].getDescription(),
2746                     features[f].getStatus(), features[f].getBegin(),
2747                     features[f].getEnd(), features[f].getFeatureGroup());
2748
2749             sf.setScore(features[f].getScore());
2750             for (int od = 0; od < features[f].getOtherDataCount(); od++)
2751             {
2752               OtherData keyValue = features[f].getOtherData(od);
2753               if (keyValue.getKey().startsWith("LINK"))
2754               {
2755                 sf.addLink(keyValue.getValue());
2756               }
2757               else
2758               {
2759                 sf.setValue(keyValue.getKey(), keyValue.getValue());
2760               }
2761
2762             }
2763
2764             al.getSequenceAt(i).getDatasetSequence().addSequenceFeature(sf);
2765           }
2766         }
2767         if (vamsasSeq[i].getDBRefCount() > 0)
2768         {
2769           addDBRefs(al.getSequenceAt(i).getDatasetSequence(), vamsasSeq[i]);
2770         }
2771         if (jseqs[i].getPdbidsCount() > 0)
2772         {
2773           Pdbids[] ids = jseqs[i].getPdbids();
2774           for (int p = 0; p < ids.length; p++)
2775           {
2776             jalview.datamodel.PDBEntry entry = new jalview.datamodel.PDBEntry();
2777             entry.setId(ids[p].getId());
2778             if (ids[p].getType() != null)
2779             {
2780               if (ids[p].getType().equalsIgnoreCase("PDB"))
2781               {
2782                 entry.setType(PDBEntry.Type.PDB);
2783               }
2784               else
2785               {
2786                 entry.setType(PDBEntry.Type.FILE);
2787               }
2788             }
2789             if (ids[p].getFile() != null)
2790             {
2791               if (!pdbloaded.containsKey(ids[p].getFile()))
2792               {
2793                 entry.setFile(loadPDBFile(jprovider, ids[p].getId()));
2794               }
2795               else
2796               {
2797                 entry.setFile(pdbloaded.get(ids[p].getId()).toString());
2798               }
2799             }
2800             StructureSelectionManager.getStructureSelectionManager(
2801                     Desktop.instance).registerPDBEntry(entry);
2802             al.getSequenceAt(i).getDatasetSequence().addPDBId(entry);
2803           }
2804         }
2805       }
2806     } // end !multipleview
2807
2808     // ///////////////////////////////
2809     // LOAD SEQUENCE MAPPINGS
2810
2811     if (vamsasSet.getAlcodonFrameCount() > 0)
2812     {
2813       // TODO Potentially this should only be done once for all views of an
2814       // alignment
2815       AlcodonFrame[] alc = vamsasSet.getAlcodonFrame();
2816       for (int i = 0; i < alc.length; i++)
2817       {
2818         AlignedCodonFrame cf = new AlignedCodonFrame();
2819         if (alc[i].getAlcodMapCount() > 0)
2820         {
2821           AlcodMap[] maps = alc[i].getAlcodMap();
2822           for (int m = 0; m < maps.length; m++)
2823           {
2824             SequenceI dnaseq = seqRefIds.get(maps[m].getDnasq());
2825             // Load Mapping
2826             jalview.datamodel.Mapping mapping = null;
2827             // attach to dna sequence reference.
2828             if (maps[m].getMapping() != null)
2829             {
2830               mapping = addMapping(maps[m].getMapping());
2831             }
2832             if (dnaseq != null)
2833             {
2834               cf.addMap(dnaseq, mapping.getTo(), mapping.getMap());
2835             }
2836             else
2837             {
2838               // defer to later
2839               frefedSequence.add(new Object[]
2840               { maps[m].getDnasq(), cf, mapping });
2841             }
2842           }
2843         }
2844         al.addCodonFrame(cf);
2845       }
2846     }
2847
2848     // ////////////////////////////////
2849     // LOAD ANNOTATIONS
2850     List<JvAnnotRow> autoAlan = new ArrayList<JvAnnotRow>();
2851
2852     /*
2853      * store any annotations which forward reference a group's ID
2854      */
2855     Map<String, List<AlignmentAnnotation>> groupAnnotRefs = new Hashtable<String, List<AlignmentAnnotation>>();
2856
2857     if (vamsasSet.getAnnotationCount() > 0)
2858     {
2859       Annotation[] an = vamsasSet.getAnnotation();
2860
2861       for (int i = 0; i < an.length; i++)
2862       {
2863         Annotation annotation = an[i];
2864
2865         /**
2866          * test if annotation is automatically calculated for this view only
2867          */
2868         boolean autoForView = false;
2869         if (annotation.getLabel().equals("Quality")
2870                 || annotation.getLabel().equals("Conservation")
2871                 || annotation.getLabel().equals("Consensus"))
2872         {
2873           // Kludge for pre 2.5 projects which lacked the autocalculated flag
2874           autoForView = true;
2875           if (!annotation.hasAutoCalculated())
2876           {
2877             annotation.setAutoCalculated(true);
2878           }
2879         }
2880         if (autoForView
2881                 || (annotation.hasAutoCalculated() && annotation
2882                         .isAutoCalculated()))
2883         {
2884           // remove ID - we don't recover annotation from other views for
2885           // view-specific annotation
2886           annotation.setId(null);
2887         }
2888
2889         // set visiblity for other annotation in this view
2890         String annotationId = annotation.getId();
2891         if (annotationId != null
2892                 && annotationIds.containsKey(annotationId))
2893         {
2894           AlignmentAnnotation jda = annotationIds.get(annotationId);
2895           // in principle Visible should always be true for annotation displayed
2896           // in multiple views
2897           if (annotation.hasVisible())
2898           {
2899             jda.visible = annotation.getVisible();
2900           }
2901
2902           al.addAnnotation(jda);
2903
2904           continue;
2905         }
2906         // Construct new annotation from model.
2907         AnnotationElement[] ae = annotation.getAnnotationElement();
2908         jalview.datamodel.Annotation[] anot = null;
2909         java.awt.Color firstColour = null;
2910         int anpos;
2911         if (!annotation.getScoreOnly())
2912         {
2913           anot = new jalview.datamodel.Annotation[al.getWidth()];
2914           for (int aa = 0; aa < ae.length && aa < anot.length; aa++)
2915           {
2916             anpos = ae[aa].getPosition();
2917
2918             if (anpos >= anot.length)
2919             {
2920               continue;
2921             }
2922
2923             anot[anpos] = new jalview.datamodel.Annotation(
2924
2925             ae[aa].getDisplayCharacter(), ae[aa].getDescription(),
2926                     (ae[aa].getSecondaryStructure() == null || ae[aa]
2927                             .getSecondaryStructure().length() == 0) ? ' '
2928                             : ae[aa].getSecondaryStructure().charAt(0),
2929                     ae[aa].getValue()
2930
2931             );
2932             // JBPNote: Consider verifying dataflow for IO of secondary
2933             // structure annotation read from Stockholm files
2934             // this was added to try to ensure that
2935             // if (anot[ae[aa].getPosition()].secondaryStructure>' ')
2936             // {
2937             // anot[ae[aa].getPosition()].displayCharacter = "";
2938             // }
2939             anot[anpos].colour = new java.awt.Color(ae[aa].getColour());
2940             if (firstColour == null)
2941             {
2942               firstColour = anot[anpos].colour;
2943             }
2944           }
2945         }
2946         jalview.datamodel.AlignmentAnnotation jaa = null;
2947
2948         if (annotation.getGraph())
2949         {
2950           float llim = 0, hlim = 0;
2951           // if (autoForView || an[i].isAutoCalculated()) {
2952           // hlim=11f;
2953           // }
2954           jaa = new jalview.datamodel.AlignmentAnnotation(
2955                   annotation.getLabel(), annotation.getDescription(), anot,
2956                   llim, hlim, annotation.getGraphType());
2957
2958           jaa.graphGroup = annotation.getGraphGroup();
2959           jaa._linecolour = firstColour;
2960           if (annotation.getThresholdLine() != null)
2961           {
2962             jaa.setThreshold(new jalview.datamodel.GraphLine(annotation
2963                     .getThresholdLine().getValue(), annotation
2964                     .getThresholdLine().getLabel(), new java.awt.Color(
2965                     annotation.getThresholdLine().getColour())));
2966
2967           }
2968           if (autoForView || annotation.isAutoCalculated())
2969           {
2970             // Hardwire the symbol display line to ensure that labels for
2971             // histograms are displayed
2972             jaa.hasText = true;
2973           }
2974         }
2975         else
2976         {
2977           jaa = new jalview.datamodel.AlignmentAnnotation(an[i].getLabel(),
2978                   an[i].getDescription(), anot);
2979           jaa._linecolour = firstColour;
2980         }
2981         // register new annotation
2982         if (an[i].getId() != null)
2983         {
2984           annotationIds.put(an[i].getId(), jaa);
2985           jaa.annotationId = an[i].getId();
2986         }
2987         // recover sequence association
2988         String sequenceRef = an[i].getSequenceRef();
2989         if (sequenceRef != null)
2990         {
2991           // from 2.9 sequenceRef is to sequence id (JAL-1781)
2992           SequenceI sequence = seqRefIds.get(sequenceRef);
2993           if (sequence == null)
2994           {
2995             // in pre-2.9 projects sequence ref is to sequence name
2996             sequence = al.findName(sequenceRef);
2997           }
2998           if (sequence != null)
2999           {
3000             jaa.createSequenceMapping(sequence, 1, true);
3001             sequence.addAlignmentAnnotation(jaa);
3002           }
3003         }
3004         // and make a note of any group association
3005         if (an[i].getGroupRef() != null && an[i].getGroupRef().length() > 0)
3006         {
3007           List<jalview.datamodel.AlignmentAnnotation> aal = groupAnnotRefs
3008                   .get(an[i].getGroupRef());
3009           if (aal == null)
3010           {
3011             aal = new ArrayList<jalview.datamodel.AlignmentAnnotation>();
3012             groupAnnotRefs.put(an[i].getGroupRef(), aal);
3013           }
3014           aal.add(jaa);
3015         }
3016
3017         if (an[i].hasScore())
3018         {
3019           jaa.setScore(an[i].getScore());
3020         }
3021         if (an[i].hasVisible())
3022         {
3023           jaa.visible = an[i].getVisible();
3024         }
3025
3026         if (an[i].hasCentreColLabels())
3027         {
3028           jaa.centreColLabels = an[i].getCentreColLabels();
3029         }
3030
3031         if (an[i].hasScaleColLabels())
3032         {
3033           jaa.scaleColLabel = an[i].getScaleColLabels();
3034         }
3035         if (an[i].hasAutoCalculated() && an[i].isAutoCalculated())
3036         {
3037           // newer files have an 'autoCalculated' flag and store calculation
3038           // state in viewport properties
3039           jaa.autoCalculated = true; // means annotation will be marked for
3040           // update at end of load.
3041         }
3042         if (an[i].hasGraphHeight())
3043         {
3044           jaa.graphHeight = an[i].getGraphHeight();
3045         }
3046         if (an[i].hasBelowAlignment())
3047         {
3048           jaa.belowAlignment = an[i].isBelowAlignment();
3049         }
3050         jaa.setCalcId(an[i].getCalcId());
3051         if (an[i].getPropertyCount() > 0)
3052         {
3053           for (jalview.schemabinding.version2.Property prop : an[i]
3054                   .getProperty())
3055           {
3056             jaa.setProperty(prop.getName(), prop.getValue());
3057           }
3058         }
3059         if (jaa.autoCalculated)
3060         {
3061           autoAlan.add(new JvAnnotRow(i, jaa));
3062         }
3063         else
3064         // if (!autoForView)
3065         {
3066           // add autocalculated group annotation and any user created annotation
3067           // for the view
3068           al.addAnnotation(jaa);
3069         }
3070       }
3071     }
3072     // ///////////////////////
3073     // LOAD GROUPS
3074     // Create alignment markup and styles for this view
3075     if (jms.getJGroupCount() > 0)
3076     {
3077       JGroup[] groups = jms.getJGroup();
3078       boolean addAnnotSchemeGroup = false;
3079       for (int i = 0; i < groups.length; i++)
3080       {
3081         JGroup jGroup = groups[i];
3082         ColourSchemeI cs = null;
3083         if (jGroup.getColour() != null)
3084         {
3085           if (jGroup.getColour().startsWith("ucs"))
3086           {
3087             cs = getUserColourScheme(jms, jGroup.getColour());
3088           }
3089           else if (jGroup.getColour().equals("AnnotationColourGradient")
3090                   && jGroup.getAnnotationColours() != null)
3091           {
3092             addAnnotSchemeGroup = true;
3093             cs = null;
3094           }
3095           else
3096           {
3097             cs = ColourSchemeProperty.getColour(al, jGroup.getColour());
3098           }
3099
3100           if (cs != null)
3101           {
3102             cs.setThreshold(jGroup.getPidThreshold(), true);
3103           }
3104         }
3105
3106         Vector<SequenceI> seqs = new Vector<SequenceI>();
3107
3108         for (int s = 0; s < jGroup.getSeqCount(); s++)
3109         {
3110           String seqId = jGroup.getSeq(s) + "";
3111           SequenceI ts = seqRefIds.get(seqId);
3112
3113           if (ts != null)
3114           {
3115             seqs.addElement(ts);
3116           }
3117         }
3118
3119         if (seqs.size() < 1)
3120         {
3121           continue;
3122         }
3123
3124         SequenceGroup sg = new SequenceGroup(seqs, jGroup.getName(), cs,
3125                 jGroup.getDisplayBoxes(), jGroup.getDisplayText(),
3126                 jGroup.getColourText(), jGroup.getStart(),
3127                 jGroup.getEnd());
3128
3129         sg.setOutlineColour(new java.awt.Color(jGroup.getOutlineColour()));
3130
3131         sg.textColour = new java.awt.Color(jGroup.getTextCol1());
3132         sg.textColour2 = new java.awt.Color(jGroup.getTextCol2());
3133         sg.setShowNonconserved(jGroup.hasShowUnconserved() ? jGroup
3134                 .isShowUnconserved() : false);
3135         sg.thresholdTextColour = jGroup.getTextColThreshold();
3136         if (jGroup.hasShowConsensusHistogram())
3137         {
3138           sg.setShowConsensusHistogram(jGroup.isShowConsensusHistogram());
3139         }
3140         ;
3141         if (jGroup.hasShowSequenceLogo())
3142         {
3143           sg.setshowSequenceLogo(jGroup.isShowSequenceLogo());
3144         }
3145         if (jGroup.hasNormaliseSequenceLogo())
3146         {
3147           sg.setNormaliseSequenceLogo(jGroup.isNormaliseSequenceLogo());
3148         }
3149         if (jGroup.hasIgnoreGapsinConsensus())
3150         {
3151           sg.setIgnoreGapsConsensus(jGroup.getIgnoreGapsinConsensus());
3152         }
3153         if (jGroup.getConsThreshold() != 0)
3154         {
3155           jalview.analysis.Conservation c = new jalview.analysis.Conservation(
3156                   "All", ResidueProperties.propHash, 3,
3157                   sg.getSequences(null), 0, sg.getWidth() - 1);
3158           c.calculate();
3159           c.verdict(false, 25);
3160           sg.cs.setConservation(c);
3161         }
3162
3163         if (jGroup.getId() != null && groupAnnotRefs.size() > 0)
3164         {
3165           // re-instate unique group/annotation row reference
3166           List<AlignmentAnnotation> jaal = groupAnnotRefs
3167                   .get(jGroup.getId());
3168           if (jaal != null)
3169           {
3170             for (AlignmentAnnotation jaa : jaal)
3171             {
3172               jaa.groupRef = sg;
3173               if (jaa.autoCalculated)
3174               {
3175                 // match up and try to set group autocalc alignment row for this
3176                 // annotation
3177                 if (jaa.label.startsWith("Consensus for "))
3178                 {
3179                   sg.setConsensus(jaa);
3180                 }
3181                 // match up and try to set group autocalc alignment row for this
3182                 // annotation
3183                 if (jaa.label.startsWith("Conservation for "))
3184                 {
3185                   sg.setConservationRow(jaa);
3186                 }
3187               }
3188             }
3189           }
3190         }
3191         al.addGroup(sg);
3192         if (addAnnotSchemeGroup)
3193         {
3194           // reconstruct the annotation colourscheme
3195           sg.cs = constructAnnotationColour(
3196                   jGroup.getAnnotationColours(), null, al, jms, false);
3197         }
3198       }
3199     }
3200     if (view == null)
3201     {
3202       // only dataset in this model, so just return.
3203       return null;
3204     }
3205     // ///////////////////////////////
3206     // LOAD VIEWPORT
3207
3208     // If we just load in the same jar file again, the sequenceSetId
3209     // will be the same, and we end up with multiple references
3210     // to the same sequenceSet. We must modify this id on load
3211     // so that each load of the file gives a unique id
3212     String uniqueSeqSetId = view.getSequenceSetId() + uniqueSetSuffix;
3213     String viewId = (view.getId() == null ? null : view.getId()
3214             + uniqueSetSuffix);
3215     AlignFrame af = null;
3216     AlignViewport av = null;
3217     // now check to see if we really need to create a new viewport.
3218     if (multipleView && viewportsAdded.size() == 0)
3219     {
3220       // We recovered an alignment for which a viewport already exists.
3221       // TODO: fix up any settings necessary for overlaying stored state onto
3222       // state recovered from another document. (may not be necessary).
3223       // we may need a binding from a viewport in memory to one recovered from
3224       // XML.
3225       // and then recover its containing af to allow the settings to be applied.
3226       // TODO: fix for vamsas demo
3227       System.err
3228               .println("About to recover a viewport for existing alignment: Sequence set ID is "
3229                       + uniqueSeqSetId);
3230       Object seqsetobj = retrieveExistingObj(uniqueSeqSetId);
3231       if (seqsetobj != null)
3232       {
3233         if (seqsetobj instanceof String)
3234         {
3235           uniqueSeqSetId = (String) seqsetobj;
3236           System.err
3237                   .println("Recovered extant sequence set ID mapping for ID : New Sequence set ID is "
3238                           + uniqueSeqSetId);
3239         }
3240         else
3241         {
3242           System.err
3243                   .println("Warning : Collision between sequence set ID string and existing jalview object mapping.");
3244         }
3245
3246       }
3247     }
3248     /**
3249      * indicate that annotation colours are applied across all groups (pre
3250      * Jalview 2.8.1 behaviour)
3251      */
3252     boolean doGroupAnnColour = isVersionStringLaterThan("2.8.1",
3253             object.getVersion());
3254
3255     AlignmentPanel ap = null;
3256     boolean isnewview = true;
3257     if (viewId != null)
3258     {
3259       // Check to see if this alignment already has a view id == viewId
3260       jalview.gui.AlignmentPanel views[] = Desktop
3261               .getAlignmentPanels(uniqueSeqSetId);
3262       if (views != null && views.length > 0)
3263       {
3264         for (int v = 0; v < views.length; v++)
3265         {
3266           if (views[v].av.getViewId().equalsIgnoreCase(viewId))
3267           {
3268             // recover the existing alignpanel, alignframe, viewport
3269             af = views[v].alignFrame;
3270             av = views[v].av;
3271             ap = views[v];
3272             // TODO: could even skip resetting view settings if we don't want to
3273             // change the local settings from other jalview processes
3274             isnewview = false;
3275           }
3276         }
3277       }
3278     }
3279
3280     if (isnewview)
3281     {
3282       af = loadViewport(file, jseqs, hiddenSeqs, al, jms, view,
3283               uniqueSeqSetId, viewId, autoAlan);
3284       av = af.viewport;
3285       ap = af.alignPanel;
3286     }
3287
3288     /*
3289      * Load any trees, PDB structures and viewers
3290      * 
3291      * Not done if flag is false (when this method is used for New View)
3292      */
3293     if (loadTreesAndStructures)
3294     {
3295       loadTrees(jms, view, af, av, ap);
3296       loadPDBStructures(jprovider, jseqs, af, ap);
3297       loadRnaViewers(jprovider, jseqs, ap);
3298     }
3299     // and finally return.
3300     return af;
3301   }
3302
3303   /**
3304    * Instantiate and link any saved RNA (Varna) viewers. The state of the Varna
3305    * panel is restored from separate jar entries, two (gapped and trimmed) per
3306    * sequence and secondary structure.
3307    * 
3308    * Currently each viewer shows just one sequence and structure (gapped and
3309    * trimmed), however this method is designed to support multiple sequences or
3310    * structures in viewers if wanted in future.
3311    * 
3312    * @param jprovider
3313    * @param jseqs
3314    * @param ap
3315    */
3316   private void loadRnaViewers(jarInputStreamProvider jprovider,
3317           JSeq[] jseqs, AlignmentPanel ap)
3318   {
3319     /*
3320      * scan the sequences for references to viewers; create each one the first
3321      * time it is referenced, add Rna models to existing viewers
3322      */
3323     for (JSeq jseq : jseqs)
3324     {
3325       for (int i = 0; i < jseq.getRnaViewerCount(); i++)
3326       {
3327         RnaViewer viewer = jseq.getRnaViewer(i);
3328         AppVarna appVarna = findOrCreateVarnaViewer(viewer, uniqueSetSuffix,
3329                 ap);
3330
3331         for (int j = 0; j < viewer.getSecondaryStructureCount(); j++)
3332         {
3333           SecondaryStructure ss = viewer.getSecondaryStructure(j);
3334           SequenceI seq = seqRefIds.get(jseq.getId());
3335           AlignmentAnnotation ann = this.annotationIds.get(ss
3336                   .getAnnotationId());
3337
3338           /*
3339            * add the structure to the Varna display (with session state copied
3340            * from the jar to a temporary file)
3341            */
3342           boolean gapped = ss.isGapped();
3343           String rnaTitle = ss.getTitle();
3344           String sessionState = ss.getViewerState();
3345           String tempStateFile = copyJarEntry(jprovider, sessionState,
3346                   "varna");
3347           RnaModel rna = new RnaModel(rnaTitle, ann, seq, null, gapped,
3348                   tempStateFile);
3349           appVarna.addModel(rna, rnaTitle);
3350         }
3351         appVarna.setInitialSelection(viewer.getSelectedRna());
3352       }
3353     }
3354   }
3355
3356   /**
3357    * Locate and return an already instantiated matching AppVarna, or create one
3358    * if not found
3359    * 
3360    * @param viewer
3361    * @param viewIdSuffix
3362    * @param ap
3363    * @return
3364    */
3365   protected AppVarna findOrCreateVarnaViewer(RnaViewer viewer,
3366           String viewIdSuffix, AlignmentPanel ap)
3367   {
3368     /*
3369      * on each load a suffix is appended to the saved viewId, to avoid conflicts
3370      * if load is repeated
3371      */
3372     String postLoadId = viewer.getViewId() + viewIdSuffix;
3373     for (JInternalFrame frame : getAllFrames())
3374     {
3375       if (frame instanceof AppVarna)
3376       {
3377         AppVarna varna = (AppVarna) frame;
3378         if (postLoadId.equals(varna.getViewId()))
3379         {
3380           // this viewer is already instantiated
3381           // could in future here add ap as another 'parent' of the
3382           // AppVarna window; currently just 1-to-many
3383           return varna;
3384         }
3385       }
3386     }
3387
3388     /*
3389      * viewer not found - make it
3390      */
3391     RnaViewerModel model = new RnaViewerModel(postLoadId,
3392             viewer.getTitle(), viewer.getXpos(),
3393             viewer.getYpos(), viewer.getWidth(), viewer.getHeight(),
3394             viewer.getDividerLocation());
3395     AppVarna varna = new AppVarna(model, ap);
3396
3397     return varna;
3398   }
3399
3400   /**
3401    * Load any saved trees
3402    * 
3403    * @param jms
3404    * @param view
3405    * @param af
3406    * @param av
3407    * @param ap
3408    */
3409   protected void loadTrees(JalviewModelSequence jms, Viewport view,
3410           AlignFrame af, AlignViewport av, AlignmentPanel ap)
3411   {
3412     // TODO result of automated refactoring - are all these parameters needed?
3413     try
3414     {
3415       for (int t = 0; t < jms.getTreeCount(); t++)
3416       {
3417
3418         Tree tree = jms.getTree(t);
3419
3420         TreePanel tp = (TreePanel) retrieveExistingObj(tree.getId());
3421         if (tp == null)
3422         {
3423           tp = af.ShowNewickTree(
3424                   new jalview.io.NewickFile(tree.getNewick()),
3425                   tree.getTitle(), tree.getWidth(), tree.getHeight(),
3426                   tree.getXpos(), tree.getYpos());
3427           if (tree.getId() != null)
3428           {
3429             // perhaps bind the tree id to something ?
3430           }
3431         }
3432         else
3433         {
3434           // update local tree attributes ?
3435           // TODO: should check if tp has been manipulated by user - if so its
3436           // settings shouldn't be modified
3437           tp.setTitle(tree.getTitle());
3438           tp.setBounds(new Rectangle(tree.getXpos(), tree.getYpos(), tree
3439                   .getWidth(), tree.getHeight()));
3440           tp.av = av; // af.viewport; // TODO: verify 'associate with all
3441           // views'
3442           // works still
3443           tp.treeCanvas.av = av; // af.viewport;
3444           tp.treeCanvas.ap = ap; // af.alignPanel;
3445
3446         }
3447         if (tp == null)
3448         {
3449           warn("There was a problem recovering stored Newick tree: \n"
3450                   + tree.getNewick());
3451           continue;
3452         }
3453
3454         tp.fitToWindow.setState(tree.getFitToWindow());
3455         tp.fitToWindow_actionPerformed(null);
3456
3457         if (tree.getFontName() != null)
3458         {
3459           tp.setTreeFont(new java.awt.Font(tree.getFontName(), tree
3460                   .getFontStyle(), tree.getFontSize()));
3461         }
3462         else
3463         {
3464           tp.setTreeFont(new java.awt.Font(view.getFontName(), view
3465                   .getFontStyle(), tree.getFontSize()));
3466         }
3467
3468         tp.showPlaceholders(tree.getMarkUnlinked());
3469         tp.showBootstrap(tree.getShowBootstrap());
3470         tp.showDistances(tree.getShowDistances());
3471
3472         tp.treeCanvas.threshold = tree.getThreshold();
3473
3474         if (tree.getCurrentTree())
3475         {
3476           af.viewport.setCurrentTree(tp.getTree());
3477         }
3478       }
3479
3480     } catch (Exception ex)
3481     {
3482       ex.printStackTrace();
3483     }
3484   }
3485
3486   /**
3487    * Load and link any saved structure viewers.
3488    * 
3489    * @param jprovider
3490    * @param jseqs
3491    * @param af
3492    * @param ap
3493    */
3494   protected void loadPDBStructures(jarInputStreamProvider jprovider,
3495           JSeq[] jseqs, AlignFrame af, AlignmentPanel ap)
3496   {
3497     /*
3498      * Run through all PDB ids on the alignment, and collect mappings between
3499      * distinct view ids and all sequences referring to that view.
3500      */
3501     Map<String, StructureViewerModel> structureViewers = new LinkedHashMap<String, StructureViewerModel>();
3502
3503     for (int i = 0; i < jseqs.length; i++)
3504     {
3505       if (jseqs[i].getPdbidsCount() > 0)
3506       {
3507         Pdbids[] ids = jseqs[i].getPdbids();
3508         for (int p = 0; p < ids.length; p++)
3509         {
3510           final int structureStateCount = ids[p].getStructureStateCount();
3511           for (int s = 0; s < structureStateCount; s++)
3512           {
3513             // check to see if we haven't already created this structure view
3514             final StructureState structureState = ids[p]
3515                     .getStructureState(s);
3516             String sviewid = (structureState.getViewId() == null) ? null
3517                     : structureState.getViewId() + uniqueSetSuffix;
3518             jalview.datamodel.PDBEntry jpdb = new jalview.datamodel.PDBEntry();
3519             // Originally : ids[p].getFile()
3520             // : TODO: verify external PDB file recovery still works in normal
3521             // jalview project load
3522             jpdb.setFile(loadPDBFile(jprovider, ids[p].getId()));
3523             jpdb.setId(ids[p].getId());
3524
3525             int x = structureState.getXpos();
3526             int y = structureState.getYpos();
3527             int width = structureState.getWidth();
3528             int height = structureState.getHeight();
3529
3530             // Probably don't need to do this anymore...
3531             // Desktop.desktop.getComponentAt(x, y);
3532             // TODO: NOW: check that this recovers the PDB file correctly.
3533             String pdbFile = loadPDBFile(jprovider, ids[p].getId());
3534             jalview.datamodel.SequenceI seq = seqRefIds.get(jseqs[i]
3535                     .getId() + "");
3536             if (sviewid == null)
3537             {
3538               sviewid = "_jalview_pre2_4_" + x + "," + y + "," + width
3539                       + "," + height;
3540             }
3541             if (!structureViewers.containsKey(sviewid))
3542             {
3543               structureViewers.put(sviewid,
3544                       new StructureViewerModel(x, y, width, height, false,
3545                               false, true, structureState.getViewId(),
3546                               structureState.getType()));
3547               // Legacy pre-2.7 conversion JAL-823 :
3548               // do not assume any view has to be linked for colour by
3549               // sequence
3550             }
3551
3552             // assemble String[] { pdb files }, String[] { id for each
3553             // file }, orig_fileloc, SequenceI[][] {{ seqs_file 1 }, {
3554             // seqs_file 2}, boolean[] {
3555             // linkAlignPanel,superposeWithAlignpanel}} from hash
3556             StructureViewerModel jmoldat = structureViewers.get(sviewid);
3557             jmoldat.setAlignWithPanel(jmoldat.isAlignWithPanel()
3558                     | (structureState.hasAlignwithAlignPanel() ? structureState
3559                             .getAlignwithAlignPanel() : false));
3560
3561             /*
3562              * Default colour by linked panel to false if not specified (e.g.
3563              * for pre-2.7 projects)
3564              */
3565             boolean colourWithAlignPanel = jmoldat.isColourWithAlignPanel();
3566             colourWithAlignPanel |= (structureState
3567                     .hasColourwithAlignPanel() ? structureState
3568                     .getColourwithAlignPanel() : false);
3569             jmoldat.setColourWithAlignPanel(colourWithAlignPanel);
3570
3571             /*
3572              * Default colour by viewer to true if not specified (e.g. for
3573              * pre-2.7 projects)
3574              */
3575             boolean colourByViewer = jmoldat.isColourByViewer();
3576             colourByViewer &= structureState.hasColourByJmol() ? structureState
3577                     .getColourByJmol() : true;
3578             jmoldat.setColourByViewer(colourByViewer);
3579
3580             if (jmoldat.getStateData().length() < structureState
3581                     .getContent().length())
3582             {
3583               {
3584                 jmoldat.setStateData(structureState.getContent());
3585               }
3586             }
3587             if (ids[p].getFile() != null)
3588             {
3589               File mapkey = new File(ids[p].getFile());
3590               StructureData seqstrmaps = jmoldat.getFileData().get(mapkey);
3591               if (seqstrmaps == null)
3592               {
3593                 jmoldat.getFileData().put(
3594                         mapkey,
3595                         seqstrmaps = jmoldat.new StructureData(pdbFile,
3596                                 ids[p].getId()));
3597               }
3598               if (!seqstrmaps.getSeqList().contains(seq))
3599               {
3600                 seqstrmaps.getSeqList().add(seq);
3601                 // TODO and chains?
3602               }
3603             }
3604             else
3605             {
3606               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");
3607               warn(errorMessage);
3608             }
3609           }
3610         }
3611       }
3612     }
3613     // Instantiate the associated structure views
3614     for (Entry<String, StructureViewerModel> entry : structureViewers
3615             .entrySet())
3616     {
3617       try
3618       {
3619         createOrLinkStructureViewer(entry, af, ap, jprovider);
3620       } catch (Exception e)
3621       {
3622         System.err.println("Error loading structure viewer: "
3623                 + e.getMessage());
3624         // failed - try the next one
3625       }
3626     }
3627   }
3628
3629   /**
3630    * 
3631    * @param viewerData
3632    * @param af
3633    * @param ap
3634    * @param jprovider
3635    */
3636   protected void createOrLinkStructureViewer(
3637           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
3638           AlignmentPanel ap, jarInputStreamProvider jprovider)
3639   {
3640     final StructureViewerModel stateData = viewerData.getValue();
3641
3642     /*
3643      * Search for any viewer windows already open from other alignment views
3644      * that exactly match the stored structure state
3645      */
3646     StructureViewerBase comp = findMatchingViewer(viewerData);
3647
3648     if (comp != null)
3649     {
3650       linkStructureViewer(ap, comp, stateData);
3651       return;
3652     }
3653
3654     /*
3655      * From 2.9: stateData.type contains JMOL or CHIMERA, data is in jar entry
3656      * "viewer_"+stateData.viewId
3657      */
3658     if (ViewerType.CHIMERA.toString().equals(stateData.getType()))
3659     {
3660       createChimeraViewer(viewerData, af, jprovider);
3661     }
3662     else
3663     {
3664       /*
3665        * else Jmol (if pre-2.9, stateData contains JMOL state string)
3666        */
3667       createJmolViewer(viewerData, af, jprovider);
3668     }
3669   }
3670
3671   /**
3672    * Create a new Chimera viewer.
3673    * 
3674    * @param data
3675    * @param af
3676    * @param jprovider
3677    */
3678   protected void createChimeraViewer(Entry<String, StructureViewerModel> viewerData,
3679           AlignFrame af,
3680           jarInputStreamProvider jprovider)
3681   {
3682     StructureViewerModel data = viewerData.getValue();
3683     String chimeraSessionFile =  data.getStateData();
3684
3685     /*
3686      * Copy Chimera session from jar entry "viewer_"+viewId to a temporary file
3687      * 
3688      * NB this is the 'saved' viewId as in the project file XML, _not_ the
3689      * 'uniquified' sviewid used to reconstruct the viewer here
3690      */
3691     String viewerJarEntryName = getViewerJarEntryName(data.getViewId());
3692     chimeraSessionFile = copyJarEntry(jprovider, viewerJarEntryName,
3693             "chimera");
3694
3695     Set<Entry<File, StructureData>> fileData = data.getFileData()
3696             .entrySet();
3697     List<PDBEntry> pdbs = new ArrayList<PDBEntry>();
3698     List<SequenceI[]> allseqs = new ArrayList<SequenceI[]>();
3699     for (Entry<File, StructureData> pdb : fileData)
3700     {
3701       String filePath = pdb.getValue().getFilePath();
3702       String pdbId = pdb.getValue().getPdbId();
3703       // pdbs.add(new PDBEntry(filePath, pdbId));
3704       pdbs.add(new PDBEntry(pdbId, null, PDBEntry.Type.PDB, filePath));
3705       final List<SequenceI> seqList = pdb.getValue().getSeqList();
3706       SequenceI[] seqs = seqList.toArray(new SequenceI[seqList.size()]);
3707       allseqs.add(seqs);
3708     }
3709
3710     boolean colourByChimera = data.isColourByViewer();
3711     boolean colourBySequence = data.isColourWithAlignPanel();
3712
3713     // TODO use StructureViewer as a factory here, see JAL-1761
3714     final PDBEntry[] pdbArray = pdbs.toArray(new PDBEntry[pdbs.size()]);
3715     final SequenceI[][] seqsArray = allseqs.toArray(new SequenceI[allseqs
3716             .size()][]);
3717     String newViewId = viewerData.getKey();
3718
3719     ChimeraViewFrame cvf = new ChimeraViewFrame(chimeraSessionFile,
3720             af.alignPanel, pdbArray,
3721             seqsArray, colourByChimera, colourBySequence, newViewId);
3722     cvf.setSize(data.getWidth(), data.getHeight());
3723     cvf.setLocation(data.getX(), data.getY());
3724   }
3725
3726   /**
3727    * Create a new Jmol window. First parse the Jmol state to translate filenames
3728    * loaded into the view, and record the order in which files are shown in the
3729    * Jmol view, so we can add the sequence mappings in same order.
3730    * 
3731    * @param viewerData
3732    * @param af
3733    * @param jprovider
3734    */
3735   protected void createJmolViewer(
3736           final Entry<String, StructureViewerModel> viewerData,
3737           AlignFrame af, jarInputStreamProvider jprovider)
3738   {
3739     final StructureViewerModel svattrib = viewerData.getValue();
3740     String state = svattrib.getStateData();
3741
3742     /*
3743      * Pre-2.9: state element value is the Jmol state string
3744      * 
3745      * 2.9+: @type is "JMOL", state data is in a Jar file member named "viewer_"
3746      * + viewId
3747      */
3748     if (ViewerType.JMOL.toString().equals(svattrib.getType()))
3749     {
3750       state = readJarEntry(jprovider,
3751               getViewerJarEntryName(svattrib.getViewId()));
3752     }
3753
3754     List<String> pdbfilenames = new ArrayList<String>();
3755     List<SequenceI[]> seqmaps = new ArrayList<SequenceI[]>();
3756     List<String> pdbids = new ArrayList<String>();
3757     StringBuilder newFileLoc = new StringBuilder(64);
3758     int cp = 0, ncp, ecp;
3759     Map<File, StructureData> oldFiles = svattrib.getFileData();
3760     while ((ncp = state.indexOf("load ", cp)) > -1)
3761     {
3762       do
3763       {
3764         // look for next filename in load statement
3765         newFileLoc.append(state.substring(cp,
3766                 ncp = (state.indexOf("\"", ncp + 1) + 1)));
3767         String oldfilenam = state.substring(ncp,
3768                 ecp = state.indexOf("\"", ncp));
3769         // recover the new mapping data for this old filename
3770         // have to normalize filename - since Jmol and jalview do
3771         // filename
3772         // translation differently.
3773         StructureData filedat = oldFiles.get(new File(oldfilenam));
3774         newFileLoc.append(Platform.escapeString(filedat.getFilePath()));
3775         pdbfilenames.add(filedat.getFilePath());
3776         pdbids.add(filedat.getPdbId());
3777         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
3778         newFileLoc.append("\"");
3779         cp = ecp + 1; // advance beyond last \" and set cursor so we can
3780                       // look for next file statement.
3781       } while ((ncp = state.indexOf("/*file*/", cp)) > -1);
3782     }
3783     if (cp > 0)
3784     {
3785       // just append rest of state
3786       newFileLoc.append(state.substring(cp));
3787     }
3788     else
3789     {
3790       System.err.print("Ignoring incomplete Jmol state for PDB ids: ");
3791       newFileLoc = new StringBuilder(state);
3792       newFileLoc.append("; load append ");
3793       for (File id : oldFiles.keySet())
3794       {
3795         // add this and any other pdb files that should be present in
3796         // the viewer
3797         StructureData filedat = oldFiles.get(id);
3798         newFileLoc.append(filedat.getFilePath());
3799         pdbfilenames.add(filedat.getFilePath());
3800         pdbids.add(filedat.getPdbId());
3801         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
3802         newFileLoc.append(" \"");
3803         newFileLoc.append(filedat.getFilePath());
3804         newFileLoc.append("\"");
3805
3806       }
3807       newFileLoc.append(";");
3808     }
3809
3810     if (newFileLoc.length() > 0)
3811     {
3812       int histbug = newFileLoc.indexOf("history = ");
3813       histbug += 10;
3814       int diff = histbug == -1 ? -1 : newFileLoc.indexOf(";", histbug);
3815       String val = (diff == -1) ? null : newFileLoc
3816               .substring(histbug, diff);
3817       if (val != null && val.length() >= 4)
3818       {
3819         if (val.contains("e"))
3820         {
3821           if (val.trim().equals("true"))
3822           {
3823             val = "1";
3824           }
3825           else
3826           {
3827             val = "0";
3828           }
3829           newFileLoc.replace(histbug, diff, val);
3830         }
3831       }
3832
3833       final String[] pdbf = pdbfilenames.toArray(new String[pdbfilenames
3834               .size()]);
3835       final String[] id = pdbids.toArray(new String[pdbids.size()]);
3836       final SequenceI[][] sq = seqmaps
3837               .toArray(new SequenceI[seqmaps.size()][]);
3838       final String fileloc = newFileLoc.toString();
3839       final String sviewid = viewerData.getKey();
3840       final AlignFrame alf = af;
3841       final Rectangle rect = new Rectangle(svattrib.getX(),
3842               svattrib.getY(), svattrib.getWidth(), svattrib.getHeight());
3843       try
3844       {
3845         javax.swing.SwingUtilities.invokeAndWait(new Runnable()
3846         {
3847           @Override
3848           public void run()
3849           {
3850             JalviewStructureDisplayI sview = null;
3851             try
3852             {
3853               sview = new StructureViewer(alf.alignPanel
3854                       .getStructureSelectionManager()).createView(
3855                       StructureViewer.ViewerType.JMOL, pdbf, id, sq,
3856                       alf.alignPanel, svattrib, fileloc, rect, sviewid);
3857               addNewStructureViewer(sview);
3858             } catch (OutOfMemoryError ex)
3859             {
3860               new OOMWarning("restoring structure view for PDB id " + id,
3861                       (OutOfMemoryError) ex.getCause());
3862               if (sview != null && sview.isVisible())
3863               {
3864                 sview.closeViewer(false);
3865                 sview.setVisible(false);
3866                 sview.dispose();
3867               }
3868             }
3869           }
3870         });
3871       } catch (InvocationTargetException ex)
3872       {
3873         warn("Unexpected error when opening Jmol view.", ex);
3874
3875       } catch (InterruptedException e)
3876       {
3877         // e.printStackTrace();
3878       }
3879     }
3880   }
3881
3882   /**
3883    * Generates a name for the entry in the project jar file to hold state
3884    * information for a structure viewer
3885    * 
3886    * @param viewId
3887    * @return
3888    */
3889   protected String getViewerJarEntryName(String viewId)
3890   {
3891     return VIEWER_PREFIX + viewId;
3892   }
3893
3894   /**
3895    * Returns any open frame that matches given structure viewer data. The match
3896    * is based on the unique viewId, or (for older project versions) the frame's
3897    * geometry.
3898    * 
3899    * @param viewerData
3900    * @return
3901    */
3902   protected StructureViewerBase findMatchingViewer(
3903           Entry<String, StructureViewerModel> viewerData)
3904   {
3905     final String sviewid = viewerData.getKey();
3906     final StructureViewerModel svattrib = viewerData.getValue();
3907     StructureViewerBase comp = null;
3908     JInternalFrame[] frames = getAllFrames();
3909     for (JInternalFrame frame : frames)
3910     {
3911       if (frame instanceof StructureViewerBase)
3912       {
3913         /*
3914          * Post jalview 2.4 schema includes structure view id
3915          */
3916         if (sviewid != null
3917                 && ((StructureViewerBase) frame).getViewId()
3918                         .equals(sviewid))
3919         {
3920           comp = (StructureViewerBase) frame;
3921           break; // break added in 2.9
3922         }
3923         /*
3924          * Otherwise test for matching position and size of viewer frame
3925          */
3926         else if (frame.getX() == svattrib.getX()
3927                 && frame.getY() == svattrib.getY()
3928                 && frame.getHeight() == svattrib.getHeight()
3929                 && frame.getWidth() == svattrib.getWidth())
3930         {
3931           comp = (StructureViewerBase) frame;
3932           // no break in faint hope of an exact match on viewId
3933         }
3934       }
3935     }
3936     return comp;
3937   }
3938
3939   /**
3940    * Link an AlignmentPanel to an existing structure viewer.
3941    * 
3942    * @param ap
3943    * @param viewer
3944    * @param oldFiles
3945    * @param useinViewerSuperpos
3946    * @param usetoColourbyseq
3947    * @param viewerColouring
3948    */
3949   protected void linkStructureViewer(AlignmentPanel ap,
3950           StructureViewerBase viewer, StructureViewerModel stateData)
3951   {
3952     // NOTE: if the jalview project is part of a shared session then
3953     // view synchronization should/could be done here.
3954
3955     final boolean useinViewerSuperpos = stateData.isAlignWithPanel();
3956     final boolean usetoColourbyseq = stateData.isColourWithAlignPanel();
3957     final boolean viewerColouring = stateData.isColourByViewer();
3958     Map<File, StructureData> oldFiles = stateData.getFileData();
3959
3960     /*
3961      * Add mapping for sequences in this view to an already open viewer
3962      */
3963     final AAStructureBindingModel binding = viewer.getBinding();
3964     for (File id : oldFiles.keySet())
3965     {
3966       // add this and any other pdb files that should be present in the
3967       // viewer
3968       StructureData filedat = oldFiles.get(id);
3969       String pdbFile = filedat.getFilePath();
3970       SequenceI[] seq = filedat.getSeqList().toArray(new SequenceI[0]);
3971       binding.getSsm().setMapping(seq, null, pdbFile,
3972               jalview.io.AppletFormatAdapter.FILE);
3973       binding.addSequenceForStructFile(pdbFile, seq);
3974     }
3975     // and add the AlignmentPanel's reference to the view panel
3976     viewer.addAlignmentPanel(ap);
3977     if (useinViewerSuperpos)
3978     {
3979       viewer.useAlignmentPanelForSuperposition(ap);
3980     }
3981     else
3982     {
3983       viewer.excludeAlignmentPanelForSuperposition(ap);
3984     }
3985     if (usetoColourbyseq)
3986     {
3987       viewer.useAlignmentPanelForColourbyseq(ap, !viewerColouring);
3988     }
3989     else
3990     {
3991       viewer.excludeAlignmentPanelForColourbyseq(ap);
3992     }
3993   }
3994
3995   /**
3996    * Get all frames within the Desktop.
3997    * 
3998    * @return
3999    */
4000   protected JInternalFrame[] getAllFrames()
4001   {
4002     JInternalFrame[] frames = null;
4003     // TODO is this necessary - is it safe - risk of hanging?
4004     do
4005     {
4006       try
4007       {
4008         frames = Desktop.desktop.getAllFrames();
4009       } catch (ArrayIndexOutOfBoundsException e)
4010       {
4011         // occasional No such child exceptions are thrown here...
4012         try
4013         {
4014           Thread.sleep(10);
4015         } catch (InterruptedException f)
4016         {
4017         }
4018       }
4019     } while (frames == null);
4020     return frames;
4021   }
4022
4023   /**
4024    * 
4025    * @param supported
4026    *          - minimum version we are comparing against
4027    * @param version
4028    *          - version of data being processsed.
4029    * @return true if version is development/null or evaluates to the same or
4030    *         later X.Y.Z (where X,Y,Z are like [0-9]+b?[0-9]*)
4031    */
4032   protected boolean isVersionStringLaterThan(String supported,
4033           String version)
4034   {
4035     if (version == null || version.equalsIgnoreCase("DEVELOPMENT BUILD")
4036             || version.equalsIgnoreCase("Test")
4037             || version.equalsIgnoreCase("AUTOMATED BUILD"))
4038     {
4039       System.err.println("Assuming project file with "
4040               + (version == null ? "null" : version)
4041               + " is compatible with Jalview version " + supported);
4042       return true;
4043     }
4044     else
4045     {
4046       StringTokenizer currentV = new StringTokenizer(supported, "."), fileV = new StringTokenizer(
4047               version, ".");
4048       while (currentV.hasMoreTokens() && fileV.hasMoreTokens())
4049       {
4050         // convert b to decimal to catch bugfix releases within a series
4051         String curT = currentV.nextToken().toLowerCase().replace('b', '.');
4052         String fileT = fileV.nextToken().toLowerCase().replace('b', '.');
4053         try
4054         {
4055           if (Float.valueOf(curT) > Float.valueOf(fileT))
4056           {
4057             // current version is newer than the version that wrote the file
4058             return false;
4059           }
4060         } catch (NumberFormatException nfe)
4061         {
4062           System.err
4063                   .println("** WARNING: Version comparison failed for tokens ("
4064                           + curT
4065                           + ") and ("
4066                           + fileT
4067                           + ")\n** Current: '"
4068                           + supported + "' and Version: '" + version + "'");
4069         }
4070       }
4071       if (currentV.hasMoreElements())
4072       {
4073         // fileV has no minor version but identical series to current
4074         return false;
4075       }
4076     }
4077     return true;
4078   }
4079
4080   Vector<JalviewStructureDisplayI> newStructureViewers = null;
4081
4082   protected void addNewStructureViewer(JalviewStructureDisplayI sview)
4083   {
4084     if (newStructureViewers != null)
4085     {
4086       sview.getBinding().setFinishedLoadingFromArchive(false);
4087       newStructureViewers.add(sview);
4088     }
4089   }
4090
4091   protected void setLoadingFinishedForNewStructureViewers()
4092   {
4093     if (newStructureViewers != null)
4094     {
4095       for (JalviewStructureDisplayI sview : newStructureViewers)
4096       {
4097         sview.getBinding().setFinishedLoadingFromArchive(true);
4098       }
4099       newStructureViewers.clear();
4100       newStructureViewers = null;
4101     }
4102   }
4103
4104   AlignFrame loadViewport(String file, JSeq[] JSEQ,
4105           List<SequenceI> hiddenSeqs, Alignment al,
4106           JalviewModelSequence jms, Viewport view, String uniqueSeqSetId,
4107           String viewId, List<JvAnnotRow> autoAlan)
4108   {
4109     AlignFrame af = null;
4110     af = new AlignFrame(al, view.getWidth(), view.getHeight(),
4111             uniqueSeqSetId, viewId);
4112
4113     af.setFileName(file, "Jalview");
4114
4115     for (int i = 0; i < JSEQ.length; i++)
4116     {
4117       af.viewport.setSequenceColour(af.viewport.getAlignment()
4118               .getSequenceAt(i), new java.awt.Color(JSEQ[i].getColour()));
4119     }
4120
4121     af.viewport.setGatherViewsHere(view.getGatheredViews());
4122
4123     if (view.getSequenceSetId() != null)
4124     {
4125       AlignmentViewport av = viewportsAdded.get(uniqueSeqSetId);
4126
4127       af.viewport.setSequenceSetId(uniqueSeqSetId);
4128       if (av != null)
4129       {
4130         // propagate shared settings to this new view
4131         af.viewport.setHistoryList(av.getHistoryList());
4132         af.viewport.setRedoList(av.getRedoList());
4133       }
4134       else
4135       {
4136         viewportsAdded.put(uniqueSeqSetId, af.viewport);
4137       }
4138       // TODO: check if this method can be called repeatedly without
4139       // side-effects if alignpanel already registered.
4140       PaintRefresher.Register(af.alignPanel, uniqueSeqSetId);
4141     }
4142     // apply Hidden regions to view.
4143     if (hiddenSeqs != null)
4144     {
4145       for (int s = 0; s < JSEQ.length; s++)
4146       {
4147         jalview.datamodel.SequenceGroup hidden = new jalview.datamodel.SequenceGroup();
4148
4149         for (int r = 0; r < JSEQ[s].getHiddenSequencesCount(); r++)
4150         {
4151           hidden.addSequence(
4152                   al.getSequenceAt(JSEQ[s].getHiddenSequences(r)), false);
4153         }
4154         af.viewport.hideRepSequences(al.getSequenceAt(s), hidden);
4155       }
4156
4157       // jalview.datamodel.SequenceI[] hseqs = new
4158       // jalview.datamodel.SequenceI[hiddenSeqs
4159       // .size()];
4160       //
4161       // for (int s = 0; s < hiddenSeqs.size(); s++)
4162       // {
4163       // hseqs[s] = (jalview.datamodel.SequenceI) hiddenSeqs.elementAt(s);
4164       // }
4165
4166       SequenceI[] hseqs = hiddenSeqs.toArray(new SequenceI[hiddenSeqs
4167               .size()]);
4168       af.viewport.hideSequence(hseqs);
4169
4170     }
4171     // recover view properties and display parameters
4172     if (view.getViewName() != null)
4173     {
4174       af.viewport.viewName = view.getViewName();
4175       af.setInitialTabVisible();
4176     }
4177     af.setBounds(view.getXpos(), view.getYpos(), view.getWidth(),
4178             view.getHeight());
4179
4180     af.viewport.setShowAnnotation(view.getShowAnnotation());
4181     af.viewport.setAbovePIDThreshold(view.getPidSelected());
4182
4183     af.viewport.setColourText(view.getShowColourText());
4184
4185     af.viewport.setConservationSelected(view.getConservationSelected());
4186     af.viewport.setShowJVSuffix(view.getShowFullId());
4187     af.viewport.setRightAlignIds(view.getRightAlignIds());
4188     af.viewport.setFont(
4189             new java.awt.Font(view.getFontName(), view.getFontStyle(), view
4190                     .getFontSize()), true);
4191     // TODO: allow custom charWidth/Heights to be restored by updating them
4192     // after setting font - which means set above to false
4193     af.viewport.setRenderGaps(view.getRenderGaps());
4194     af.viewport.setWrapAlignment(view.getWrapAlignment());
4195     af.viewport.setShowAnnotation(view.getShowAnnotation());
4196
4197     af.viewport.setShowBoxes(view.getShowBoxes());
4198
4199     af.viewport.setShowText(view.getShowText());
4200
4201     af.viewport.setTextColour(new java.awt.Color(view.getTextCol1()));
4202     af.viewport.setTextColour2(new java.awt.Color(view.getTextCol2()));
4203     af.viewport.setThresholdTextColour(view.getTextColThreshold());
4204     af.viewport.setShowUnconserved(view.hasShowUnconserved() ? view
4205             .isShowUnconserved() : false);
4206     af.viewport.setStartRes(view.getStartRes());
4207     af.viewport.setStartSeq(view.getStartSeq());
4208     af.alignPanel.updateLayout();
4209     ColourSchemeI cs = null;
4210     // apply colourschemes
4211     if (view.getBgColour() != null)
4212     {
4213       if (view.getBgColour().startsWith("ucs"))
4214       {
4215         cs = getUserColourScheme(jms, view.getBgColour());
4216       }
4217       else if (view.getBgColour().startsWith("Annotation"))
4218       {
4219         AnnotationColours viewAnnColour = view.getAnnotationColours();
4220         cs = constructAnnotationColour(viewAnnColour, af, al, jms, true);
4221
4222         // annpos
4223
4224       }
4225       else
4226       {
4227         cs = ColourSchemeProperty.getColour(al, view.getBgColour());
4228       }
4229
4230       if (cs != null)
4231       {
4232         cs.setThreshold(view.getPidThreshold(), true);
4233         cs.setConsensus(af.viewport.getSequenceConsensusHash());
4234       }
4235     }
4236
4237     af.viewport.setGlobalColourScheme(cs);
4238     af.viewport.setColourAppliesToAllGroups(false);
4239
4240     if (view.getConservationSelected() && cs != null)
4241     {
4242       cs.setConservationInc(view.getConsThreshold());
4243     }
4244
4245     af.changeColour(cs);
4246
4247     af.viewport.setColourAppliesToAllGroups(true);
4248
4249     af.viewport.setShowSequenceFeatures(view.getShowSequenceFeatures());
4250
4251     if (view.hasCentreColumnLabels())
4252     {
4253       af.viewport.setCentreColumnLabels(view.getCentreColumnLabels());
4254     }
4255     if (view.hasIgnoreGapsinConsensus())
4256     {
4257       af.viewport.setIgnoreGapsConsensus(view.getIgnoreGapsinConsensus(),
4258               null);
4259     }
4260     if (view.hasFollowHighlight())
4261     {
4262       af.viewport.setFollowHighlight(view.getFollowHighlight());
4263     }
4264     if (view.hasFollowSelection())
4265     {
4266       af.viewport.followSelection = view.getFollowSelection();
4267     }
4268     if (view.hasShowConsensusHistogram())
4269     {
4270       af.viewport.setShowConsensusHistogram(view
4271               .getShowConsensusHistogram());
4272     }
4273     else
4274     {
4275       af.viewport.setShowConsensusHistogram(true);
4276     }
4277     if (view.hasShowSequenceLogo())
4278     {
4279       af.viewport.setShowSequenceLogo(view.getShowSequenceLogo());
4280     }
4281     else
4282     {
4283       af.viewport.setShowSequenceLogo(false);
4284     }
4285     if (view.hasNormaliseSequenceLogo())
4286     {
4287       af.viewport.setNormaliseSequenceLogo(view.getNormaliseSequenceLogo());
4288     }
4289     if (view.hasShowDbRefTooltip())
4290     {
4291       af.viewport.setShowDBRefs(view.getShowDbRefTooltip());
4292     }
4293     if (view.hasShowNPfeatureTooltip())
4294     {
4295       af.viewport.setShowNPFeats(view.hasShowNPfeatureTooltip());
4296     }
4297     if (view.hasShowGroupConsensus())
4298     {
4299       af.viewport.setShowGroupConsensus(view.getShowGroupConsensus());
4300     }
4301     else
4302     {
4303       af.viewport.setShowGroupConsensus(false);
4304     }
4305     if (view.hasShowGroupConservation())
4306     {
4307       af.viewport.setShowGroupConservation(view.getShowGroupConservation());
4308     }
4309     else
4310     {
4311       af.viewport.setShowGroupConservation(false);
4312     }
4313
4314     // recover featre settings
4315     if (jms.getFeatureSettings() != null)
4316     {
4317       FeaturesDisplayed fdi;
4318       af.viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
4319       String[] renderOrder = new String[jms.getFeatureSettings()
4320               .getSettingCount()];
4321       Hashtable featureGroups = new Hashtable();
4322       Hashtable featureColours = new Hashtable();
4323       Hashtable featureOrder = new Hashtable();
4324
4325       for (int fs = 0; fs < jms.getFeatureSettings().getSettingCount(); fs++)
4326       {
4327         Setting setting = jms.getFeatureSettings().getSetting(fs);
4328         if (setting.hasMincolour())
4329         {
4330           GraduatedColor gc = setting.hasMin() ? new GraduatedColor(
4331                   new java.awt.Color(setting.getMincolour()),
4332                   new java.awt.Color(setting.getColour()),
4333                   setting.getMin(), setting.getMax()) : new GraduatedColor(
4334                   new java.awt.Color(setting.getMincolour()),
4335                   new java.awt.Color(setting.getColour()), 0, 1);
4336           if (setting.hasThreshold())
4337           {
4338             gc.setThresh(setting.getThreshold());
4339             gc.setThreshType(setting.getThreshstate());
4340           }
4341           gc.setAutoScaled(true); // default
4342           if (setting.hasAutoScale())
4343           {
4344             gc.setAutoScaled(setting.getAutoScale());
4345           }
4346           if (setting.hasColourByLabel())
4347           {
4348             gc.setColourByLabel(setting.getColourByLabel());
4349           }
4350           // and put in the feature colour table.
4351           featureColours.put(setting.getType(), gc);
4352         }
4353         else
4354         {
4355           featureColours.put(setting.getType(),
4356                   new java.awt.Color(setting.getColour()));
4357         }
4358         renderOrder[fs] = setting.getType();
4359         if (setting.hasOrder())
4360         {
4361           featureOrder.put(setting.getType(), setting.getOrder());
4362         }
4363         else
4364         {
4365           featureOrder.put(setting.getType(), new Float(fs
4366                   / jms.getFeatureSettings().getSettingCount()));
4367         }
4368         if (setting.getDisplay())
4369         {
4370           fdi.setVisible(setting.getType());
4371         }
4372       }
4373       Hashtable fgtable = new Hashtable();
4374       for (int gs = 0; gs < jms.getFeatureSettings().getGroupCount(); gs++)
4375       {
4376         Group grp = jms.getFeatureSettings().getGroup(gs);
4377         fgtable.put(grp.getName(), new Boolean(grp.getDisplay()));
4378       }
4379       // FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
4380       // fgtable, featureColours, jms.getFeatureSettings().hasTransparency() ?
4381       // jms.getFeatureSettings().getTransparency() : 0.0, featureOrder);
4382       FeatureRendererSettings frs = new FeatureRendererSettings(
4383               renderOrder, fgtable, featureColours, 1.0f, featureOrder);
4384       af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer()
4385               .transferSettings(frs);
4386
4387     }
4388
4389     if (view.getHiddenColumnsCount() > 0)
4390     {
4391       for (int c = 0; c < view.getHiddenColumnsCount(); c++)
4392       {
4393         af.viewport.hideColumns(view.getHiddenColumns(c).getStart(), view
4394                 .getHiddenColumns(c).getEnd() // +1
4395                 );
4396       }
4397     }
4398     if (view.getCalcIdParam() != null)
4399     {
4400       for (CalcIdParam calcIdParam : view.getCalcIdParam())
4401       {
4402         if (calcIdParam != null)
4403         {
4404           if (recoverCalcIdParam(calcIdParam, af.viewport))
4405           {
4406           }
4407           else
4408           {
4409             warn("Couldn't recover parameters for "
4410                     + calcIdParam.getCalcId());
4411           }
4412         }
4413       }
4414     }
4415     af.setMenusFromViewport(af.viewport);
4416     
4417     // TODO: we don't need to do this if the viewport is aready visible.
4418     /*
4419      * Add the AlignFrame to the desktop (it may be 'gathered' later), unless it
4420      * has a 'cdna/protein complement' view, in which case save it in order to
4421      * populate a SplitFrame once all views have been read in.
4422      */
4423     String complementaryViewId = view.getComplementId();
4424     if (complementaryViewId == null)
4425     {
4426       Desktop.addInternalFrame(af, view.getTitle(), view.getWidth(),
4427               view.getHeight());
4428       // recompute any autoannotation
4429       af.alignPanel.updateAnnotation(false, true);
4430       reorderAutoannotation(af, al, autoAlan);
4431       af.alignPanel.alignmentChanged();
4432     }
4433     else
4434     {
4435       splitFrameCandidates.put(view, af);
4436     }
4437     return af;
4438   }
4439
4440   private ColourSchemeI constructAnnotationColour(
4441           AnnotationColours viewAnnColour, AlignFrame af, Alignment al,
4442           JalviewModelSequence jms, boolean checkGroupAnnColour)
4443   {
4444     boolean propagateAnnColour = false;
4445     ColourSchemeI cs = null;
4446     AlignmentI annAlignment = af != null ? af.viewport.getAlignment() : al;
4447     if (checkGroupAnnColour && al.getGroups() != null
4448             && al.getGroups().size() > 0)
4449     {
4450       // pre 2.8.1 behaviour
4451       // check to see if we should transfer annotation colours
4452       propagateAnnColour = true;
4453       for (jalview.datamodel.SequenceGroup sg : al.getGroups())
4454       {
4455         if (sg.cs instanceof AnnotationColourGradient)
4456         {
4457           propagateAnnColour = false;
4458         }
4459       }
4460     }
4461     // int find annotation
4462     if (annAlignment.getAlignmentAnnotation() != null)
4463     {
4464       for (int i = 0; i < annAlignment.getAlignmentAnnotation().length; i++)
4465       {
4466         if (annAlignment.getAlignmentAnnotation()[i].label
4467                 .equals(viewAnnColour.getAnnotation()))
4468         {
4469           if (annAlignment.getAlignmentAnnotation()[i].getThreshold() == null)
4470           {
4471             annAlignment.getAlignmentAnnotation()[i]
4472                     .setThreshold(new jalview.datamodel.GraphLine(
4473                             viewAnnColour.getThreshold(), "Threshold",
4474                             java.awt.Color.black)
4475
4476                     );
4477           }
4478
4479           if (viewAnnColour.getColourScheme().equals("None"))
4480           {
4481             cs = new AnnotationColourGradient(
4482                     annAlignment.getAlignmentAnnotation()[i],
4483                     new java.awt.Color(viewAnnColour.getMinColour()),
4484                     new java.awt.Color(viewAnnColour.getMaxColour()),
4485                     viewAnnColour.getAboveThreshold());
4486           }
4487           else if (viewAnnColour.getColourScheme().startsWith("ucs"))
4488           {
4489             cs = new AnnotationColourGradient(
4490                     annAlignment.getAlignmentAnnotation()[i],
4491                     getUserColourScheme(jms,
4492                             viewAnnColour.getColourScheme()),
4493                     viewAnnColour.getAboveThreshold());
4494           }
4495           else
4496           {
4497             cs = new AnnotationColourGradient(
4498                     annAlignment.getAlignmentAnnotation()[i],
4499                     ColourSchemeProperty.getColour(al,
4500                             viewAnnColour.getColourScheme()),
4501                     viewAnnColour.getAboveThreshold());
4502           }
4503           if (viewAnnColour.hasPerSequence())
4504           {
4505             ((AnnotationColourGradient) cs).setSeqAssociated(viewAnnColour
4506                     .isPerSequence());
4507           }
4508           if (viewAnnColour.hasPredefinedColours())
4509           {
4510             ((AnnotationColourGradient) cs)
4511                     .setPredefinedColours(viewAnnColour
4512                             .isPredefinedColours());
4513           }
4514           if (propagateAnnColour && al.getGroups() != null)
4515           {
4516             // Also use these settings for all the groups
4517             for (int g = 0; g < al.getGroups().size(); g++)
4518             {
4519               jalview.datamodel.SequenceGroup sg = al.getGroups().get(g);
4520
4521               if (sg.cs == null)
4522               {
4523                 continue;
4524               }
4525
4526               /*
4527                * if (viewAnnColour.getColourScheme().equals("None" )) { sg.cs =
4528                * new AnnotationColourGradient(
4529                * annAlignment.getAlignmentAnnotation()[i], new
4530                * java.awt.Color(viewAnnColour. getMinColour()), new
4531                * java.awt.Color(viewAnnColour. getMaxColour()),
4532                * viewAnnColour.getAboveThreshold()); } else
4533                */
4534               {
4535                 sg.cs = new AnnotationColourGradient(
4536                         annAlignment.getAlignmentAnnotation()[i], sg.cs,
4537                         viewAnnColour.getAboveThreshold());
4538                 if (cs instanceof AnnotationColourGradient)
4539                 {
4540                   if (viewAnnColour.hasPerSequence())
4541                   {
4542                     ((AnnotationColourGradient) cs)
4543                             .setSeqAssociated(viewAnnColour.isPerSequence());
4544                   }
4545                   if (viewAnnColour.hasPredefinedColours())
4546                   {
4547                     ((AnnotationColourGradient) cs)
4548                             .setPredefinedColours(viewAnnColour
4549                                     .isPredefinedColours());
4550                   }
4551                 }
4552               }
4553
4554             }
4555           }
4556
4557           break;
4558         }
4559
4560       }
4561     }
4562     return cs;
4563   }
4564
4565   private void reorderAutoannotation(AlignFrame af, Alignment al,
4566           List<JvAnnotRow> autoAlan)
4567   {
4568     // copy over visualization settings for autocalculated annotation in the
4569     // view
4570     if (al.getAlignmentAnnotation() != null)
4571     {
4572       /**
4573        * Kludge for magic autoannotation names (see JAL-811)
4574        */
4575       String[] magicNames = new String[]
4576       { "Consensus", "Quality", "Conservation" };
4577       JvAnnotRow nullAnnot = new JvAnnotRow(-1, null);
4578       Hashtable<String, JvAnnotRow> visan = new Hashtable<String, JvAnnotRow>();
4579       for (String nm : magicNames)
4580       {
4581         visan.put(nm, nullAnnot);
4582       }
4583       for (JvAnnotRow auan : autoAlan)
4584       {
4585         visan.put(auan.template.label
4586                 + (auan.template.getCalcId() == null ? "" : "\t"
4587                         + auan.template.getCalcId()), auan);
4588       }
4589       int hSize = al.getAlignmentAnnotation().length;
4590       List<JvAnnotRow> reorder = new ArrayList<JvAnnotRow>();
4591       // work through any autoCalculated annotation already on the view
4592       // removing it if it should be placed in a different location on the
4593       // annotation panel.
4594       List<String> remains = new ArrayList<String>(visan.keySet());
4595       for (int h = 0; h < hSize; h++)
4596       {
4597         jalview.datamodel.AlignmentAnnotation jalan = al
4598                 .getAlignmentAnnotation()[h];
4599         if (jalan.autoCalculated)
4600         {
4601           String k;
4602           JvAnnotRow valan = visan.get(k = jalan.label);
4603           if (jalan.getCalcId() != null)
4604           {
4605             valan = visan.get(k = jalan.label + "\t" + jalan.getCalcId());
4606           }
4607
4608           if (valan != null)
4609           {
4610             // delete the auto calculated row from the alignment
4611             al.deleteAnnotation(jalan, false);
4612             remains.remove(k);
4613             hSize--;
4614             h--;
4615             if (valan != nullAnnot)
4616             {
4617               if (jalan != valan.template)
4618               {
4619                 // newly created autoannotation row instance
4620                 // so keep a reference to the visible annotation row
4621                 // and copy over all relevant attributes
4622                 if (valan.template.graphHeight >= 0)
4623
4624                 {
4625                   jalan.graphHeight = valan.template.graphHeight;
4626                 }
4627                 jalan.visible = valan.template.visible;
4628               }
4629               reorder.add(new JvAnnotRow(valan.order, jalan));
4630             }
4631           }
4632         }
4633       }
4634       // Add any (possibly stale) autocalculated rows that were not appended to
4635       // the view during construction
4636       for (String other : remains)
4637       {
4638         JvAnnotRow othera = visan.get(other);
4639         if (othera != nullAnnot && othera.template.getCalcId() != null
4640                 && othera.template.getCalcId().length() > 0)
4641         {
4642           reorder.add(othera);
4643         }
4644       }
4645       // now put the automatic annotation in its correct place
4646       int s = 0, srt[] = new int[reorder.size()];
4647       JvAnnotRow[] rws = new JvAnnotRow[reorder.size()];
4648       for (JvAnnotRow jvar : reorder)
4649       {
4650         rws[s] = jvar;
4651         srt[s++] = jvar.order;
4652       }
4653       reorder.clear();
4654       jalview.util.QuickSort.sort(srt, rws);
4655       // and re-insert the annotation at its correct position
4656       for (JvAnnotRow jvar : rws)
4657       {
4658         al.addAnnotation(jvar.template, jvar.order);
4659       }
4660       af.alignPanel.adjustAnnotationHeight();
4661     }
4662   }
4663
4664   Hashtable skipList = null;
4665
4666   /**
4667    * TODO remove this method
4668    * 
4669    * @param view
4670    * @return AlignFrame bound to sequenceSetId from view, if one exists. private
4671    *         AlignFrame getSkippedFrame(Viewport view) { if (skipList==null) {
4672    *         throw new Error("Implementation Error. No skipList defined for this
4673    *         Jalview2XML instance."); } return (AlignFrame)
4674    *         skipList.get(view.getSequenceSetId()); }
4675    */
4676
4677   /**
4678    * Check if the Jalview view contained in object should be skipped or not.
4679    * 
4680    * @param object
4681    * @return true if view's sequenceSetId is a key in skipList
4682    */
4683   private boolean skipViewport(JalviewModel object)
4684   {
4685     if (skipList == null)
4686     {
4687       return false;
4688     }
4689     String id;
4690     if (skipList.containsKey(id = object.getJalviewModelSequence()
4691             .getViewport()[0].getSequenceSetId()))
4692     {
4693       if (Cache.log != null && Cache.log.isDebugEnabled())
4694       {
4695         Cache.log.debug("Skipping seuqence set id " + id);
4696       }
4697       return true;
4698     }
4699     return false;
4700   }
4701
4702   public void addToSkipList(AlignFrame af)
4703   {
4704     if (skipList == null)
4705     {
4706       skipList = new Hashtable();
4707     }
4708     skipList.put(af.getViewport().getSequenceSetId(), af);
4709   }
4710
4711   public void clearSkipList()
4712   {
4713     if (skipList != null)
4714     {
4715       skipList.clear();
4716       skipList = null;
4717     }
4718   }
4719
4720   private void recoverDatasetFor(SequenceSet vamsasSet, Alignment al,
4721           boolean ignoreUnrefed)
4722   {
4723     jalview.datamodel.Alignment ds = getDatasetFor(vamsasSet.getDatasetId());
4724     Vector dseqs = null;
4725     if (ds == null)
4726     {
4727       // create a list of new dataset sequences
4728       dseqs = new Vector();
4729     }
4730     for (int i = 0, iSize = vamsasSet.getSequenceCount(); i < iSize; i++)
4731     {
4732       Sequence vamsasSeq = vamsasSet.getSequence(i);
4733       ensureJalviewDatasetSequence(vamsasSeq, ds, dseqs, ignoreUnrefed);
4734     }
4735     // create a new dataset
4736     if (ds == null)
4737     {
4738       SequenceI[] dsseqs = new SequenceI[dseqs.size()];
4739       dseqs.copyInto(dsseqs);
4740       ds = new jalview.datamodel.Alignment(dsseqs);
4741       debug("Created new dataset " + vamsasSet.getDatasetId()
4742               + " for alignment " + System.identityHashCode(al));
4743       addDatasetRef(vamsasSet.getDatasetId(), ds);
4744     }
4745     // set the dataset for the newly imported alignment.
4746     if (al.getDataset() == null && !ignoreUnrefed)
4747     {
4748       al.setDataset(ds);
4749     }
4750   }
4751
4752   /**
4753    * 
4754    * @param vamsasSeq
4755    *          sequence definition to create/merge dataset sequence for
4756    * @param ds
4757    *          dataset alignment
4758    * @param dseqs
4759    *          vector to add new dataset sequence to
4760    */
4761   private void ensureJalviewDatasetSequence(Sequence vamsasSeq,
4762           AlignmentI ds, Vector dseqs, boolean ignoreUnrefed)
4763   {
4764     // JBP TODO: Check this is called for AlCodonFrames to support recovery of
4765     // xRef Codon Maps
4766     SequenceI sq = seqRefIds.get(vamsasSeq.getId());
4767     SequenceI dsq = null;
4768     if (sq != null && sq.getDatasetSequence() != null)
4769     {
4770       dsq = sq.getDatasetSequence();
4771     }
4772     if (sq == null && ignoreUnrefed)
4773     {
4774       return;
4775     }
4776     String sqid = vamsasSeq.getDsseqid();
4777     if (dsq == null)
4778     {
4779       // need to create or add a new dataset sequence reference to this sequence
4780       if (sqid != null)
4781       {
4782         dsq = seqRefIds.get(sqid);
4783       }
4784       // check again
4785       if (dsq == null)
4786       {
4787         // make a new dataset sequence
4788         dsq = sq.createDatasetSequence();
4789         if (sqid == null)
4790         {
4791           // make up a new dataset reference for this sequence
4792           sqid = seqHash(dsq);
4793         }
4794         dsq.setVamsasId(uniqueSetSuffix + sqid);
4795         seqRefIds.put(sqid, dsq);
4796         if (ds == null)
4797         {
4798           if (dseqs != null)
4799           {
4800             dseqs.addElement(dsq);
4801           }
4802         }
4803         else
4804         {
4805           ds.addSequence(dsq);
4806         }
4807       }
4808       else
4809       {
4810         if (sq != dsq)
4811         { // make this dataset sequence sq's dataset sequence
4812           sq.setDatasetSequence(dsq);
4813           // and update the current dataset alignment
4814           if (ds == null)
4815           {
4816             if (dseqs != null)
4817             {
4818               if (!dseqs.contains(dsq))
4819               {
4820                 dseqs.add(dsq);
4821               }
4822             }
4823             else
4824             {
4825               if (ds.findIndex(dsq) < 0)
4826               {
4827                 ds.addSequence(dsq);
4828               }
4829             }
4830           }
4831         }
4832       }
4833     }
4834     // TODO: refactor this as a merge dataset sequence function
4835     // now check that sq (the dataset sequence) sequence really is the union of
4836     // all references to it
4837     // boolean pre = sq.getStart() < dsq.getStart();
4838     // boolean post = sq.getEnd() > dsq.getEnd();
4839     // if (pre || post)
4840     if (sq != dsq)
4841     {
4842       // StringBuffer sb = new StringBuffer();
4843       String newres = jalview.analysis.AlignSeq.extractGaps(
4844               jalview.util.Comparison.GapChars, sq.getSequenceAsString());
4845       if (!newres.equalsIgnoreCase(dsq.getSequenceAsString())
4846               && newres.length() > dsq.getLength())
4847       {
4848         // Update with the longer sequence.
4849         synchronized (dsq)
4850         {
4851           /*
4852            * if (pre) { sb.insert(0, newres .substring(0, dsq.getStart() -
4853            * sq.getStart())); dsq.setStart(sq.getStart()); } if (post) {
4854            * sb.append(newres.substring(newres.length() - sq.getEnd() -
4855            * dsq.getEnd())); dsq.setEnd(sq.getEnd()); }
4856            */
4857           dsq.setSequence(newres);
4858         }
4859         // TODO: merges will never happen if we 'know' we have the real dataset
4860         // sequence - this should be detected when id==dssid
4861         System.err
4862                 .println("DEBUG Notice:  Merged dataset sequence (if you see this often, post at http://issues.jalview.org/browse/JAL-1474)"); // ("
4863         // + (pre ? "prepended" : "") + " "
4864         // + (post ? "appended" : ""));
4865       }
4866     }
4867   }
4868
4869   /*
4870    * TODO use AlignmentI here and in related methods - needs
4871    * AlignmentI.getDataset() changed to return AlignmentI instead of Alignment
4872    */
4873   Hashtable<String, Alignment> datasetIds = null;
4874
4875   IdentityHashMap<Alignment, String> dataset2Ids = null;
4876
4877   private Alignment getDatasetFor(String datasetId)
4878   {
4879     if (datasetIds == null)
4880     {
4881       datasetIds = new Hashtable<String, Alignment>();
4882       return null;
4883     }
4884     if (datasetIds.containsKey(datasetId))
4885     {
4886       return datasetIds.get(datasetId);
4887     }
4888     return null;
4889   }
4890
4891   private void addDatasetRef(String datasetId, Alignment dataset)
4892   {
4893     if (datasetIds == null)
4894     {
4895       datasetIds = new Hashtable<String, Alignment>();
4896     }
4897     datasetIds.put(datasetId, dataset);
4898   }
4899
4900   /**
4901    * make a new dataset ID for this jalview dataset alignment
4902    * 
4903    * @param dataset
4904    * @return
4905    */
4906   private String getDatasetIdRef(Alignment dataset)
4907   {
4908     if (dataset.getDataset() != null)
4909     {
4910       warn("Serious issue!  Dataset Object passed to getDatasetIdRef is not a Jalview DATASET alignment...");
4911     }
4912     String datasetId = makeHashCode(dataset, null);
4913     if (datasetId == null)
4914     {
4915       // make a new datasetId and record it
4916       if (dataset2Ids == null)
4917       {
4918         dataset2Ids = new IdentityHashMap<Alignment, String>();
4919       }
4920       else
4921       {
4922         datasetId = dataset2Ids.get(dataset);
4923       }
4924       if (datasetId == null)
4925       {
4926         datasetId = "ds" + dataset2Ids.size() + 1;
4927         dataset2Ids.put(dataset, datasetId);
4928       }
4929     }
4930     return datasetId;
4931   }
4932
4933   private void addDBRefs(SequenceI datasetSequence, Sequence sequence)
4934   {
4935     for (int d = 0; d < sequence.getDBRefCount(); d++)
4936     {
4937       DBRef dr = sequence.getDBRef(d);
4938       jalview.datamodel.DBRefEntry entry = new jalview.datamodel.DBRefEntry(
4939               sequence.getDBRef(d).getSource(), sequence.getDBRef(d)
4940                       .getVersion(), sequence.getDBRef(d).getAccessionId());
4941       if (dr.getMapping() != null)
4942       {
4943         entry.setMap(addMapping(dr.getMapping()));
4944       }
4945       datasetSequence.addDBRef(entry);
4946     }
4947   }
4948
4949   private jalview.datamodel.Mapping addMapping(Mapping m)
4950   {
4951     SequenceI dsto = null;
4952     // Mapping m = dr.getMapping();
4953     int fr[] = new int[m.getMapListFromCount() * 2];
4954     Enumeration f = m.enumerateMapListFrom();
4955     for (int _i = 0; f.hasMoreElements(); _i += 2)
4956     {
4957       MapListFrom mf = (MapListFrom) f.nextElement();
4958       fr[_i] = mf.getStart();
4959       fr[_i + 1] = mf.getEnd();
4960     }
4961     int fto[] = new int[m.getMapListToCount() * 2];
4962     f = m.enumerateMapListTo();
4963     for (int _i = 0; f.hasMoreElements(); _i += 2)
4964     {
4965       MapListTo mf = (MapListTo) f.nextElement();
4966       fto[_i] = mf.getStart();
4967       fto[_i + 1] = mf.getEnd();
4968     }
4969     jalview.datamodel.Mapping jmap = new jalview.datamodel.Mapping(dsto,
4970             fr, fto, (int) m.getMapFromUnit(), (int) m.getMapToUnit());
4971     if (m.getMappingChoice() != null)
4972     {
4973       MappingChoice mc = m.getMappingChoice();
4974       if (mc.getDseqFor() != null)
4975       {
4976         String dsfor = "" + mc.getDseqFor();
4977         if (seqRefIds.containsKey(dsfor))
4978         {
4979           /**
4980            * recover from hash
4981            */
4982           jmap.setTo(seqRefIds.get(dsfor));
4983         }
4984         else
4985         {
4986           frefedSequence.add(new Object[]
4987           { dsfor, jmap });
4988         }
4989       }
4990       else
4991       {
4992         /**
4993          * local sequence definition
4994          */
4995         Sequence ms = mc.getSequence();
4996         SequenceI djs = null;
4997         String sqid = ms.getDsseqid();
4998         if (sqid != null && sqid.length() > 0)
4999         {
5000           /*
5001            * recover dataset sequence
5002            */
5003           djs = seqRefIds.get(sqid);
5004         }
5005         else
5006         {
5007           System.err
5008                   .println("Warning - making up dataset sequence id for DbRef sequence map reference");
5009           sqid = ((Object) ms).toString(); // make up a new hascode for
5010           // undefined dataset sequence hash
5011           // (unlikely to happen)
5012         }
5013
5014         if (djs == null)
5015         {
5016           /**
5017            * make a new dataset sequence and add it to refIds hash
5018            */
5019           djs = new jalview.datamodel.Sequence(ms.getName(),
5020                   ms.getSequence());
5021           djs.setStart(jmap.getMap().getToLowest());
5022           djs.setEnd(jmap.getMap().getToHighest());
5023           djs.setVamsasId(uniqueSetSuffix + sqid);
5024           jmap.setTo(djs);
5025           seqRefIds.put(sqid, djs);
5026
5027         }
5028         jalview.bin.Cache.log.debug("about to recurse on addDBRefs.");
5029         addDBRefs(djs, ms);
5030
5031       }
5032     }
5033     return (jmap);
5034
5035   }
5036
5037   public jalview.gui.AlignmentPanel copyAlignPanel(AlignmentPanel ap,
5038           boolean keepSeqRefs)
5039   {
5040     initSeqRefs();
5041     JalviewModel jm = saveState(ap, null, null, null);
5042
5043     if (!keepSeqRefs)
5044     {
5045       clearSeqRefs();
5046       jm.getJalviewModelSequence().getViewport(0).setSequenceSetId(null);
5047     }
5048     else
5049     {
5050       uniqueSetSuffix = "";
5051       jm.getJalviewModelSequence().getViewport(0).setId(null); // we don't
5052       // overwrite the
5053       // view we just
5054       // copied
5055     }
5056     if (this.frefedSequence == null)
5057     {
5058       frefedSequence = new Vector();
5059     }
5060
5061     viewportsAdded.clear();
5062
5063     AlignFrame af = loadFromObject(jm, null, false, null);
5064     af.alignPanels.clear();
5065     af.closeMenuItem_actionPerformed(true);
5066
5067     /*
5068      * if(ap.av.getAlignment().getAlignmentAnnotation()!=null) { for(int i=0;
5069      * i<ap.av.getAlignment().getAlignmentAnnotation().length; i++) {
5070      * if(!ap.av.getAlignment().getAlignmentAnnotation()[i].autoCalculated) {
5071      * af.alignPanel.av.getAlignment().getAlignmentAnnotation()[i] =
5072      * ap.av.getAlignment().getAlignmentAnnotation()[i]; } } }
5073      */
5074
5075     return af.alignPanel;
5076   }
5077
5078   /**
5079    * flag indicating if hashtables should be cleared on finalization TODO this
5080    * flag may not be necessary
5081    */
5082   private final boolean _cleartables = true;
5083
5084   private Hashtable jvids2vobj;
5085
5086   /*
5087    * (non-Javadoc)
5088    * 
5089    * @see java.lang.Object#finalize()
5090    */
5091   @Override
5092   protected void finalize() throws Throwable
5093   {
5094     // really make sure we have no buried refs left.
5095     if (_cleartables)
5096     {
5097       clearSeqRefs();
5098     }
5099     this.seqRefIds = null;
5100     this.seqsToIds = null;
5101     super.finalize();
5102   }
5103
5104   private void warn(String msg)
5105   {
5106     warn(msg, null);
5107   }
5108
5109   private void warn(String msg, Exception e)
5110   {
5111     if (Cache.log != null)
5112     {
5113       if (e != null)
5114       {
5115         Cache.log.warn(msg, e);
5116       }
5117       else
5118       {
5119         Cache.log.warn(msg);
5120       }
5121     }
5122     else
5123     {
5124       System.err.println("Warning: " + msg);
5125       if (e != null)
5126       {
5127         e.printStackTrace();
5128       }
5129     }
5130   }
5131
5132   private void debug(String string)
5133   {
5134     debug(string, null);
5135   }
5136
5137   private void debug(String msg, Exception e)
5138   {
5139     if (Cache.log != null)
5140     {
5141       if (e != null)
5142       {
5143         Cache.log.debug(msg, e);
5144       }
5145       else
5146       {
5147         Cache.log.debug(msg);
5148       }
5149     }
5150     else
5151     {
5152       System.err.println("Warning: " + msg);
5153       if (e != null)
5154       {
5155         e.printStackTrace();
5156       }
5157     }
5158   }
5159
5160   /**
5161    * set the object to ID mapping tables used to write/recover objects and XML
5162    * ID strings for the jalview project. If external tables are provided then
5163    * finalize and clearSeqRefs will not clear the tables when the Jalview2XML
5164    * object goes out of scope. - also populates the datasetIds hashtable with
5165    * alignment objects containing dataset sequences
5166    * 
5167    * @param vobj2jv
5168    *          Map from ID strings to jalview datamodel
5169    * @param jv2vobj
5170    *          Map from jalview datamodel to ID strings
5171    * 
5172    * 
5173    */
5174   public void setObjectMappingTables(Hashtable vobj2jv,
5175           IdentityHashMap jv2vobj)
5176   {
5177     this.jv2vobj = jv2vobj;
5178     this.vobj2jv = vobj2jv;
5179     Iterator ds = jv2vobj.keySet().iterator();
5180     String id;
5181     while (ds.hasNext())
5182     {
5183       Object jvobj = ds.next();
5184       id = jv2vobj.get(jvobj).toString();
5185       if (jvobj instanceof jalview.datamodel.Alignment)
5186       {
5187         if (((jalview.datamodel.Alignment) jvobj).getDataset() == null)
5188         {
5189           addDatasetRef(id, (jalview.datamodel.Alignment) jvobj);
5190         }
5191       }
5192       else if (jvobj instanceof jalview.datamodel.Sequence)
5193       {
5194         // register sequence object so the XML parser can recover it.
5195         if (seqRefIds == null)
5196         {
5197           seqRefIds = new HashMap<String, SequenceI>();
5198         }
5199         if (seqsToIds == null)
5200         {
5201           seqsToIds = new IdentityHashMap<SequenceI, String>();
5202         }
5203         seqRefIds.put(jv2vobj.get(jvobj).toString(), (SequenceI) jvobj);
5204         seqsToIds.put((SequenceI) jvobj, id);
5205       }
5206       else if (jvobj instanceof jalview.datamodel.AlignmentAnnotation)
5207       {
5208         String anid;
5209         AlignmentAnnotation jvann = (AlignmentAnnotation) jvobj;
5210         annotationIds.put(anid = jv2vobj.get(jvobj).toString(), jvann);
5211         if (jvann.annotationId == null)
5212         {
5213           jvann.annotationId = anid;
5214         }
5215         if (!jvann.annotationId.equals(anid))
5216         {
5217           // TODO verify that this is the correct behaviour
5218           this.warn("Overriding Annotation ID for " + anid
5219                   + " from different id : " + jvann.annotationId);
5220           jvann.annotationId = anid;
5221         }
5222       }
5223       else if (jvobj instanceof String)
5224       {
5225         if (jvids2vobj == null)
5226         {
5227           jvids2vobj = new Hashtable();
5228           jvids2vobj.put(jvobj, jv2vobj.get(jvobj).toString());
5229         }
5230       }
5231       else
5232       {
5233         Cache.log.debug("Ignoring " + jvobj.getClass() + " (ID = " + id);
5234       }
5235     }
5236   }
5237
5238   /**
5239    * set the uniqueSetSuffix used to prefix/suffix object IDs for jalview
5240    * objects created from the project archive. If string is null (default for
5241    * construction) then suffix will be set automatically.
5242    * 
5243    * @param string
5244    */
5245   public void setUniqueSetSuffix(String string)
5246   {
5247     uniqueSetSuffix = string;
5248
5249   }
5250
5251   /**
5252    * uses skipList2 as the skipList for skipping views on sequence sets
5253    * associated with keys in the skipList
5254    * 
5255    * @param skipList2
5256    */
5257   public void setSkipList(Hashtable skipList2)
5258   {
5259     skipList = skipList2;
5260   }
5261
5262   /**
5263    * Reads the jar entry of given name and returns its contents, or null if the
5264    * entry is not found.
5265    * 
5266    * @param jprovider
5267    * @param jarEntryName
5268    * @return
5269    */
5270   protected String readJarEntry(jarInputStreamProvider jprovider,
5271           String jarEntryName)
5272   {
5273     String result = null;
5274     BufferedReader in = null;
5275
5276     try
5277     {
5278       /*
5279        * Reopen the jar input stream and traverse its entries to find a matching
5280        * name
5281        */
5282       JarInputStream jin = jprovider.getJarInputStream();
5283       JarEntry entry = null;
5284       do
5285       {
5286         entry = jin.getNextJarEntry();
5287       } while (entry != null && !entry.getName().equals(jarEntryName));
5288
5289       if (entry != null)
5290       {
5291         StringBuilder out = new StringBuilder(256);
5292         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
5293         String data;
5294
5295         while ((data = in.readLine()) != null)
5296         {
5297           out.append(data);
5298         }
5299         result = out.toString();
5300       }
5301       else
5302       {
5303         warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
5304       }
5305     } catch (Exception ex)
5306     {
5307       ex.printStackTrace();
5308     } finally
5309     {
5310       if (in != null)
5311       {
5312         try
5313         {
5314           in.close();
5315         } catch (IOException e)
5316         {
5317           // ignore
5318         }
5319       }
5320     }
5321   
5322     return result;
5323   }
5324
5325   /**
5326    * Returns an incrementing counter (0, 1, 2...)
5327    * 
5328    * @return
5329    */
5330   private synchronized int nextCounter()
5331   {
5332     return counter++;
5333   }
5334 }