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