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