JAL-1677 commenting use of AlignViewport.gatherViewsHere
[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       int r[] = mlst.getFromRanges();
1791       for (int s = 0; s < r.length; s += 2)
1792       {
1793         MapListFrom mfrom = new MapListFrom();
1794         mfrom.setStart(r[s]);
1795         mfrom.setEnd(r[s + 1]);
1796         mp.addMapListFrom(mfrom);
1797       }
1798       r = mlst.getToRanges();
1799       for (int s = 0; s < r.length; s += 2)
1800       {
1801         MapListTo mto = new MapListTo();
1802         mto.setStart(r[s]);
1803         mto.setEnd(r[s + 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     dnaFrame.setVisible(true);
2267     proteinFrame.setVisible(true);
2268     proteinFrame.getViewport().setCodingComplement(dnaFrame.getViewport());
2269     final StructureSelectionManager ssm = StructureSelectionManager
2270             .getStructureSelectionManager(Desktop.instance);
2271     ssm.addCommandListener(proteinFrame.getViewport());
2272     ssm.addCommandListener(dnaFrame.getViewport());
2273
2274     SplitFrame splitFrame = new SplitFrame(dnaFrame, proteinFrame);
2275     String title = MessageManager.getString("label.linked_view_title");
2276     Desktop.addInternalFrame(splitFrame, title, -1, -1);
2277     return splitFrame;
2278   }
2279
2280   /**
2281    * check errorMessage for a valid error message and raise an error box in the
2282    * GUI or write the current errorMessage to stderr and then clear the error
2283    * state.
2284    */
2285   protected void reportErrors()
2286   {
2287     reportErrors(false);
2288   }
2289
2290   protected void reportErrors(final boolean saving)
2291   {
2292     if (errorMessage != null)
2293     {
2294       final String finalErrorMessage = errorMessage;
2295       if (raiseGUI)
2296       {
2297         javax.swing.SwingUtilities.invokeLater(new Runnable()
2298         {
2299           @Override
2300           public void run()
2301           {
2302             JOptionPane.showInternalMessageDialog(Desktop.desktop,
2303                     finalErrorMessage, "Error "
2304                             + (saving ? "saving" : "loading")
2305                             + " Jalview file", JOptionPane.WARNING_MESSAGE);
2306           }
2307         });
2308       }
2309       else
2310       {
2311         System.err.println("Problem loading Jalview file: " + errorMessage);
2312       }
2313     }
2314     errorMessage = null;
2315   }
2316
2317   Map<String, String> alreadyLoadedPDB = new HashMap<String, String>();
2318
2319   /**
2320    * when set, local views will be updated from view stored in JalviewXML
2321    * Currently (28th Sep 2008) things will go horribly wrong in vamsas document
2322    * sync if this is set to true.
2323    */
2324   private final boolean updateLocalViews = false;
2325
2326   String loadPDBFile(jarInputStreamProvider jprovider, String pdbId)
2327   {
2328     if (alreadyLoadedPDB.containsKey(pdbId))
2329     {
2330       return alreadyLoadedPDB.get(pdbId).toString();
2331     }
2332
2333     try
2334     {
2335       JarInputStream jin = jprovider.getJarInputStream();
2336       /*
2337        * if (jprovider.startsWith("http://")) { jin = new JarInputStream(new
2338        * URL(jprovider).openStream()); } else { jin = new JarInputStream(new
2339        * FileInputStream(jprovider)); }
2340        */
2341
2342       JarEntry entry = null;
2343       do
2344       {
2345         entry = jin.getNextJarEntry();
2346       } while (entry != null && !entry.getName().equals(pdbId));
2347       if (entry != null)
2348       {
2349         BufferedReader in = new BufferedReader(new InputStreamReader(jin));
2350         File outFile = File.createTempFile("jalview_pdb", ".txt");
2351         outFile.deleteOnExit();
2352         PrintWriter out = new PrintWriter(new FileOutputStream(outFile));
2353         String data;
2354
2355         while ((data = in.readLine()) != null)
2356         {
2357           out.println(data);
2358         }
2359         try
2360         {
2361           out.flush();
2362         } catch (Exception foo)
2363         {
2364         }
2365         ;
2366         out.close();
2367         String t = outFile.getAbsolutePath();
2368         alreadyLoadedPDB.put(pdbId, t);
2369         return t;
2370       }
2371       else
2372       {
2373         warn("Couldn't find PDB file entry in Jalview Jar for " + pdbId);
2374       }
2375     } catch (Exception ex)
2376     {
2377       ex.printStackTrace();
2378     }
2379
2380     return null;
2381   }
2382
2383   private class JvAnnotRow
2384   {
2385     public JvAnnotRow(int i, AlignmentAnnotation jaa)
2386     {
2387       order = i;
2388       template = jaa;
2389     }
2390
2391     /**
2392      * persisted version of annotation row from which to take vis properties
2393      */
2394     public jalview.datamodel.AlignmentAnnotation template;
2395
2396     /**
2397      * original position of the annotation row in the alignment
2398      */
2399     public int order;
2400   }
2401
2402   /**
2403    * Load alignment frame from jalview XML DOM object
2404    * 
2405    * @param object
2406    *          DOM
2407    * @param file
2408    *          filename source string
2409    * @param loadTreesAndStructures
2410    *          when false only create Viewport
2411    * @param jprovider
2412    *          data source provider
2413    * @return alignment frame created from view stored in DOM
2414    */
2415   AlignFrame loadFromObject(JalviewModel object, String file,
2416           boolean loadTreesAndStructures, jarInputStreamProvider jprovider)
2417   {
2418     SequenceSet vamsasSet = object.getVamsasModel().getSequenceSet(0);
2419     Sequence[] vamsasSeq = vamsasSet.getSequence();
2420
2421     JalviewModelSequence jms = object.getJalviewModelSequence();
2422
2423     Viewport view = (jms.getViewportCount() > 0) ? jms.getViewport(0)
2424             : null;
2425
2426     // ////////////////////////////////
2427     // LOAD SEQUENCES
2428
2429     List<SequenceI> hiddenSeqs = null;
2430     jalview.datamodel.Sequence jseq;
2431
2432     List<SequenceI> tmpseqs = new ArrayList<SequenceI>();
2433
2434     boolean multipleView = false;
2435
2436     JSeq[] jseqs = object.getJalviewModelSequence().getJSeq();
2437     int vi = 0; // counter in vamsasSeq array
2438     for (int i = 0; i < jseqs.length; i++)
2439     {
2440       String seqId = jseqs[i].getId();
2441
2442       if (seqRefIds.get(seqId) != null)
2443       {
2444         tmpseqs.add(seqRefIds.get(seqId));
2445         multipleView = true;
2446       }
2447       else
2448       {
2449         jseq = new jalview.datamodel.Sequence(vamsasSeq[vi].getName(),
2450                 vamsasSeq[vi].getSequence());
2451         jseq.setDescription(vamsasSeq[vi].getDescription());
2452         jseq.setStart(jseqs[i].getStart());
2453         jseq.setEnd(jseqs[i].getEnd());
2454         jseq.setVamsasId(uniqueSetSuffix + seqId);
2455         seqRefIds.put(vamsasSeq[vi].getId(), jseq);
2456         tmpseqs.add(jseq);
2457         vi++;
2458       }
2459
2460       if (jseqs[i].getHidden())
2461       {
2462         if (hiddenSeqs == null)
2463         {
2464           hiddenSeqs = new ArrayList<SequenceI>();
2465         }
2466
2467         hiddenSeqs.add(seqRefIds.get(seqId));
2468       }
2469
2470     }
2471
2472     // /
2473     // Create the alignment object from the sequence set
2474     // ///////////////////////////////
2475     SequenceI[] orderedSeqs = tmpseqs
2476             .toArray(new SequenceI[tmpseqs.size()]);
2477
2478     Alignment al = new Alignment(orderedSeqs);
2479
2480     // / Add the alignment properties
2481     for (int i = 0; i < vamsasSet.getSequenceSetPropertiesCount(); i++)
2482     {
2483       SequenceSetProperties ssp = vamsasSet.getSequenceSetProperties(i);
2484       al.setProperty(ssp.getKey(), ssp.getValue());
2485     }
2486
2487     // /
2488     // SequenceFeatures are added to the DatasetSequence,
2489     // so we must create or recover the dataset before loading features
2490     // ///////////////////////////////
2491     if (vamsasSet.getDatasetId() == null || vamsasSet.getDatasetId() == "")
2492     {
2493       // older jalview projects do not have a dataset id.
2494       al.setDataset(null);
2495     }
2496     else
2497     {
2498       // recover dataset - passing on flag indicating if this a 'viewless'
2499       // sequence set (a.k.a. a stored dataset for the project)
2500       recoverDatasetFor(vamsasSet, al, object.getJalviewModelSequence()
2501               .getViewportCount() == 0);
2502     }
2503     // ///////////////////////////////
2504
2505     Hashtable pdbloaded = new Hashtable(); // TODO nothing writes to this??
2506     if (!multipleView)
2507     {
2508       // load sequence features, database references and any associated PDB
2509       // structures for the alignment
2510       for (int i = 0; i < vamsasSeq.length; i++)
2511       {
2512         if (jseqs[i].getFeaturesCount() > 0)
2513         {
2514           Features[] features = jseqs[i].getFeatures();
2515           for (int f = 0; f < features.length; f++)
2516           {
2517             jalview.datamodel.SequenceFeature sf = new jalview.datamodel.SequenceFeature(
2518                     features[f].getType(), features[f].getDescription(),
2519                     features[f].getStatus(), features[f].getBegin(),
2520                     features[f].getEnd(), features[f].getFeatureGroup());
2521
2522             sf.setScore(features[f].getScore());
2523             for (int od = 0; od < features[f].getOtherDataCount(); od++)
2524             {
2525               OtherData keyValue = features[f].getOtherData(od);
2526               if (keyValue.getKey().startsWith("LINK"))
2527               {
2528                 sf.addLink(keyValue.getValue());
2529               }
2530               else
2531               {
2532                 sf.setValue(keyValue.getKey(), keyValue.getValue());
2533               }
2534
2535             }
2536
2537             al.getSequenceAt(i).getDatasetSequence().addSequenceFeature(sf);
2538           }
2539         }
2540         if (vamsasSeq[i].getDBRefCount() > 0)
2541         {
2542           addDBRefs(al.getSequenceAt(i).getDatasetSequence(), vamsasSeq[i]);
2543         }
2544         if (jseqs[i].getPdbidsCount() > 0)
2545         {
2546           Pdbids[] ids = jseqs[i].getPdbids();
2547           for (int p = 0; p < ids.length; p++)
2548           {
2549             jalview.datamodel.PDBEntry entry = new jalview.datamodel.PDBEntry();
2550             entry.setId(ids[p].getId());
2551             entry.setType(ids[p].getType());
2552             if (ids[p].getFile() != null)
2553             {
2554               if (!pdbloaded.containsKey(ids[p].getFile()))
2555               {
2556                 entry.setFile(loadPDBFile(jprovider, ids[p].getId()));
2557               }
2558               else
2559               {
2560                 entry.setFile(pdbloaded.get(ids[p].getId()).toString());
2561               }
2562             }
2563             StructureSelectionManager.getStructureSelectionManager(
2564                     Desktop.instance).registerPDBEntry(entry);
2565             al.getSequenceAt(i).getDatasetSequence().addPDBId(entry);
2566           }
2567         }
2568       }
2569     } // end !multipleview
2570
2571     // ///////////////////////////////
2572     // LOAD SEQUENCE MAPPINGS
2573
2574     if (vamsasSet.getAlcodonFrameCount() > 0)
2575     {
2576       // TODO Potentially this should only be done once for all views of an
2577       // alignment
2578       AlcodonFrame[] alc = vamsasSet.getAlcodonFrame();
2579       for (int i = 0; i < alc.length; i++)
2580       {
2581         AlignedCodonFrame cf = new AlignedCodonFrame();
2582         if (alc[i].getAlcodMapCount() > 0)
2583         {
2584           AlcodMap[] maps = alc[i].getAlcodMap();
2585           for (int m = 0; m < maps.length; m++)
2586           {
2587             SequenceI dnaseq = seqRefIds.get(maps[m].getDnasq());
2588             // Load Mapping
2589             jalview.datamodel.Mapping mapping = null;
2590             // attach to dna sequence reference.
2591             if (maps[m].getMapping() != null)
2592             {
2593               mapping = addMapping(maps[m].getMapping());
2594             }
2595             if (dnaseq != null)
2596             {
2597               cf.addMap(dnaseq, mapping.getTo(), mapping.getMap());
2598             }
2599             else
2600             {
2601               // defer to later
2602               frefedSequence.add(new Object[]
2603               { maps[m].getDnasq(), cf, mapping });
2604             }
2605           }
2606         }
2607         al.addCodonFrame(cf);
2608       }
2609     }
2610
2611     // ////////////////////////////////
2612     // LOAD ANNOTATIONS
2613     List<JvAnnotRow> autoAlan = new ArrayList<JvAnnotRow>();
2614     /**
2615      * store any annotations which forward reference a group's ID
2616      */
2617     Hashtable<String, ArrayList<jalview.datamodel.AlignmentAnnotation>> groupAnnotRefs = new Hashtable<String, ArrayList<jalview.datamodel.AlignmentAnnotation>>();
2618
2619     if (vamsasSet.getAnnotationCount() > 0)
2620     {
2621       Annotation[] an = vamsasSet.getAnnotation();
2622
2623       for (int i = 0; i < an.length; i++)
2624       {
2625         /**
2626          * test if annotation is automatically calculated for this view only
2627          */
2628         boolean autoForView = false;
2629         if (an[i].getLabel().equals("Quality")
2630                 || an[i].getLabel().equals("Conservation")
2631                 || an[i].getLabel().equals("Consensus"))
2632         {
2633           // Kludge for pre 2.5 projects which lacked the autocalculated flag
2634           autoForView = true;
2635           if (!an[i].hasAutoCalculated())
2636           {
2637             an[i].setAutoCalculated(true);
2638           }
2639         }
2640         if (autoForView
2641                 || (an[i].hasAutoCalculated() && an[i].isAutoCalculated()))
2642         {
2643           // remove ID - we don't recover annotation from other views for
2644           // view-specific annotation
2645           an[i].setId(null);
2646         }
2647
2648         // set visiblity for other annotation in this view
2649         if (an[i].getId() != null
2650                 && annotationIds.containsKey(an[i].getId()))
2651         {
2652           AlignmentAnnotation jda = annotationIds.get(an[i].getId());
2653           // in principle Visible should always be true for annotation displayed
2654           // in multiple views
2655           if (an[i].hasVisible())
2656           {
2657             jda.visible = an[i].getVisible();
2658           }
2659
2660           al.addAnnotation(jda);
2661
2662           continue;
2663         }
2664         // Construct new annotation from model.
2665         AnnotationElement[] ae = an[i].getAnnotationElement();
2666         jalview.datamodel.Annotation[] anot = null;
2667         java.awt.Color firstColour = null;
2668         int anpos;
2669         if (!an[i].getScoreOnly())
2670         {
2671           anot = new jalview.datamodel.Annotation[al.getWidth()];
2672           for (int aa = 0; aa < ae.length && aa < anot.length; aa++)
2673           {
2674             anpos = ae[aa].getPosition();
2675
2676             if (anpos >= anot.length)
2677             {
2678               continue;
2679             }
2680
2681             anot[anpos] = new jalview.datamodel.Annotation(
2682
2683             ae[aa].getDisplayCharacter(), ae[aa].getDescription(),
2684                     (ae[aa].getSecondaryStructure() == null || ae[aa]
2685                             .getSecondaryStructure().length() == 0) ? ' '
2686                             : ae[aa].getSecondaryStructure().charAt(0),
2687                     ae[aa].getValue()
2688
2689             );
2690             // JBPNote: Consider verifying dataflow for IO of secondary
2691             // structure annotation read from Stockholm files
2692             // this was added to try to ensure that
2693             // if (anot[ae[aa].getPosition()].secondaryStructure>' ')
2694             // {
2695             // anot[ae[aa].getPosition()].displayCharacter = "";
2696             // }
2697             anot[anpos].colour = new java.awt.Color(ae[aa].getColour());
2698             if (firstColour == null)
2699             {
2700               firstColour = anot[anpos].colour;
2701             }
2702           }
2703         }
2704         jalview.datamodel.AlignmentAnnotation jaa = null;
2705
2706         if (an[i].getGraph())
2707         {
2708           float llim = 0, hlim = 0;
2709           // if (autoForView || an[i].isAutoCalculated()) {
2710           // hlim=11f;
2711           // }
2712           jaa = new jalview.datamodel.AlignmentAnnotation(an[i].getLabel(),
2713                   an[i].getDescription(), anot, llim, hlim,
2714                   an[i].getGraphType());
2715
2716           jaa.graphGroup = an[i].getGraphGroup();
2717           jaa._linecolour = firstColour;
2718           if (an[i].getThresholdLine() != null)
2719           {
2720             jaa.setThreshold(new jalview.datamodel.GraphLine(an[i]
2721                     .getThresholdLine().getValue(), an[i]
2722                     .getThresholdLine().getLabel(), new java.awt.Color(
2723                     an[i].getThresholdLine().getColour())));
2724
2725           }
2726           if (autoForView || an[i].isAutoCalculated())
2727           {
2728             // Hardwire the symbol display line to ensure that labels for
2729             // histograms are displayed
2730             jaa.hasText = true;
2731           }
2732         }
2733         else
2734         {
2735           jaa = new jalview.datamodel.AlignmentAnnotation(an[i].getLabel(),
2736                   an[i].getDescription(), anot);
2737           jaa._linecolour = firstColour;
2738         }
2739         // register new annotation
2740         if (an[i].getId() != null)
2741         {
2742           annotationIds.put(an[i].getId(), jaa);
2743           jaa.annotationId = an[i].getId();
2744         }
2745         // recover sequence association
2746         if (an[i].getSequenceRef() != null)
2747         {
2748           if (al.findName(an[i].getSequenceRef()) != null)
2749           {
2750             jaa.createSequenceMapping(al.findName(an[i].getSequenceRef()),
2751                     1, true);
2752             al.findName(an[i].getSequenceRef()).addAlignmentAnnotation(jaa);
2753           }
2754         }
2755         // and make a note of any group association
2756         if (an[i].getGroupRef() != null && an[i].getGroupRef().length() > 0)
2757         {
2758           ArrayList<jalview.datamodel.AlignmentAnnotation> aal = groupAnnotRefs
2759                   .get(an[i].getGroupRef());
2760           if (aal == null)
2761           {
2762             aal = new ArrayList<jalview.datamodel.AlignmentAnnotation>();
2763             groupAnnotRefs.put(an[i].getGroupRef(), aal);
2764           }
2765           aal.add(jaa);
2766         }
2767
2768         if (an[i].hasScore())
2769         {
2770           jaa.setScore(an[i].getScore());
2771         }
2772         if (an[i].hasVisible())
2773         {
2774           jaa.visible = an[i].getVisible();
2775         }
2776
2777         if (an[i].hasCentreColLabels())
2778         {
2779           jaa.centreColLabels = an[i].getCentreColLabels();
2780         }
2781
2782         if (an[i].hasScaleColLabels())
2783         {
2784           jaa.scaleColLabel = an[i].getScaleColLabels();
2785         }
2786         if (an[i].hasAutoCalculated() && an[i].isAutoCalculated())
2787         {
2788           // newer files have an 'autoCalculated' flag and store calculation
2789           // state in viewport properties
2790           jaa.autoCalculated = true; // means annotation will be marked for
2791           // update at end of load.
2792         }
2793         if (an[i].hasGraphHeight())
2794         {
2795           jaa.graphHeight = an[i].getGraphHeight();
2796         }
2797         if (an[i].hasBelowAlignment())
2798         {
2799           jaa.belowAlignment = an[i].isBelowAlignment();
2800         }
2801         jaa.setCalcId(an[i].getCalcId());
2802         if (an[i].getPropertyCount() > 0)
2803         {
2804           for (jalview.schemabinding.version2.Property prop : an[i]
2805                   .getProperty())
2806           {
2807             jaa.setProperty(prop.getName(), prop.getValue());
2808           }
2809         }
2810         if (jaa.autoCalculated)
2811         {
2812           autoAlan.add(new JvAnnotRow(i, jaa));
2813         }
2814         else
2815         // if (!autoForView)
2816         {
2817           // add autocalculated group annotation and any user created annotation
2818           // for the view
2819           al.addAnnotation(jaa);
2820         }
2821       }
2822     }
2823     // ///////////////////////
2824     // LOAD GROUPS
2825     // Create alignment markup and styles for this view
2826     if (jms.getJGroupCount() > 0)
2827     {
2828       JGroup[] groups = jms.getJGroup();
2829       boolean addAnnotSchemeGroup = false;
2830       for (int i = 0; i < groups.length; i++)
2831       {
2832         ColourSchemeI cs = null;
2833
2834         if (groups[i].getColour() != null)
2835         {
2836           if (groups[i].getColour().startsWith("ucs"))
2837           {
2838             cs = getUserColourScheme(jms, groups[i].getColour());
2839           }
2840           else if (groups[i].getColour().equals("AnnotationColourGradient")
2841                   && groups[i].getAnnotationColours() != null)
2842           {
2843             addAnnotSchemeGroup = true;
2844             cs = null;
2845           }
2846           else
2847           {
2848             cs = ColourSchemeProperty.getColour(al, groups[i].getColour());
2849           }
2850
2851           if (cs != null)
2852           {
2853             cs.setThreshold(groups[i].getPidThreshold(), true);
2854           }
2855         }
2856
2857         Vector seqs = new Vector();
2858
2859         for (int s = 0; s < groups[i].getSeqCount(); s++)
2860         {
2861           String seqId = groups[i].getSeq(s) + "";
2862           jalview.datamodel.SequenceI ts = seqRefIds.get(seqId);
2863
2864           if (ts != null)
2865           {
2866             seqs.addElement(ts);
2867           }
2868         }
2869
2870         if (seqs.size() < 1)
2871         {
2872           continue;
2873         }
2874
2875         jalview.datamodel.SequenceGroup sg = new jalview.datamodel.SequenceGroup(
2876                 seqs, groups[i].getName(), cs, groups[i].getDisplayBoxes(),
2877                 groups[i].getDisplayText(), groups[i].getColourText(),
2878                 groups[i].getStart(), groups[i].getEnd());
2879
2880         sg.setOutlineColour(new java.awt.Color(groups[i].getOutlineColour()));
2881
2882         sg.textColour = new java.awt.Color(groups[i].getTextCol1());
2883         sg.textColour2 = new java.awt.Color(groups[i].getTextCol2());
2884         sg.setShowNonconserved(groups[i].hasShowUnconserved() ? groups[i]
2885                 .isShowUnconserved() : false);
2886         sg.thresholdTextColour = groups[i].getTextColThreshold();
2887         if (groups[i].hasShowConsensusHistogram())
2888         {
2889           sg.setShowConsensusHistogram(groups[i].isShowConsensusHistogram());
2890         }
2891         ;
2892         if (groups[i].hasShowSequenceLogo())
2893         {
2894           sg.setshowSequenceLogo(groups[i].isShowSequenceLogo());
2895         }
2896         if (groups[i].hasNormaliseSequenceLogo())
2897         {
2898           sg.setNormaliseSequenceLogo(groups[i].isNormaliseSequenceLogo());
2899         }
2900         if (groups[i].hasIgnoreGapsinConsensus())
2901         {
2902           sg.setIgnoreGapsConsensus(groups[i].getIgnoreGapsinConsensus());
2903         }
2904         if (groups[i].getConsThreshold() != 0)
2905         {
2906           jalview.analysis.Conservation c = new jalview.analysis.Conservation(
2907                   "All", ResidueProperties.propHash, 3,
2908                   sg.getSequences(null), 0, sg.getWidth() - 1);
2909           c.calculate();
2910           c.verdict(false, 25);
2911           sg.cs.setConservation(c);
2912         }
2913
2914         if (groups[i].getId() != null && groupAnnotRefs.size() > 0)
2915         {
2916           // re-instate unique group/annotation row reference
2917           ArrayList<jalview.datamodel.AlignmentAnnotation> jaal = groupAnnotRefs
2918                   .get(groups[i].getId());
2919           if (jaal != null)
2920           {
2921             for (jalview.datamodel.AlignmentAnnotation jaa : jaal)
2922             {
2923               jaa.groupRef = sg;
2924               if (jaa.autoCalculated)
2925               {
2926                 // match up and try to set group autocalc alignment row for this
2927                 // annotation
2928                 if (jaa.label.startsWith("Consensus for "))
2929                 {
2930                   sg.setConsensus(jaa);
2931                 }
2932                 // match up and try to set group autocalc alignment row for this
2933                 // annotation
2934                 if (jaa.label.startsWith("Conservation for "))
2935                 {
2936                   sg.setConservationRow(jaa);
2937                 }
2938               }
2939             }
2940           }
2941         }
2942         al.addGroup(sg);
2943         if (addAnnotSchemeGroup)
2944         {
2945           // reconstruct the annotation colourscheme
2946           sg.cs = constructAnnotationColour(
2947                   groups[i].getAnnotationColours(), null, al, jms, false);
2948         }
2949       }
2950     }
2951     if (view == null)
2952     {
2953       // only dataset in this model, so just return.
2954       return null;
2955     }
2956     // ///////////////////////////////
2957     // LOAD VIEWPORT
2958
2959     // If we just load in the same jar file again, the sequenceSetId
2960     // will be the same, and we end up with multiple references
2961     // to the same sequenceSet. We must modify this id on load
2962     // so that each load of the file gives a unique id
2963     String uniqueSeqSetId = view.getSequenceSetId() + uniqueSetSuffix;
2964     String viewId = (view.getId() == null ? null : view.getId()
2965             + uniqueSetSuffix);
2966     AlignFrame af = null;
2967     AlignViewport av = null;
2968     // now check to see if we really need to create a new viewport.
2969     if (multipleView && viewportsAdded.size() == 0)
2970     {
2971       // We recovered an alignment for which a viewport already exists.
2972       // TODO: fix up any settings necessary for overlaying stored state onto
2973       // state recovered from another document. (may not be necessary).
2974       // we may need a binding from a viewport in memory to one recovered from
2975       // XML.
2976       // and then recover its containing af to allow the settings to be applied.
2977       // TODO: fix for vamsas demo
2978       System.err
2979               .println("About to recover a viewport for existing alignment: Sequence set ID is "
2980                       + uniqueSeqSetId);
2981       Object seqsetobj = retrieveExistingObj(uniqueSeqSetId);
2982       if (seqsetobj != null)
2983       {
2984         if (seqsetobj instanceof String)
2985         {
2986           uniqueSeqSetId = (String) seqsetobj;
2987           System.err
2988                   .println("Recovered extant sequence set ID mapping for ID : New Sequence set ID is "
2989                           + uniqueSeqSetId);
2990         }
2991         else
2992         {
2993           System.err
2994                   .println("Warning : Collision between sequence set ID string and existing jalview object mapping.");
2995         }
2996
2997       }
2998     }
2999     /**
3000      * indicate that annotation colours are applied across all groups (pre
3001      * Jalview 2.8.1 behaviour)
3002      */
3003     boolean doGroupAnnColour = isVersionStringLaterThan("2.8.1",
3004             object.getVersion());
3005
3006     AlignmentPanel ap = null;
3007     boolean isnewview = true;
3008     if (viewId != null)
3009     {
3010       // Check to see if this alignment already has a view id == viewId
3011       jalview.gui.AlignmentPanel views[] = Desktop
3012               .getAlignmentPanels(uniqueSeqSetId);
3013       if (views != null && views.length > 0)
3014       {
3015         for (int v = 0; v < views.length; v++)
3016         {
3017           if (views[v].av.getViewId().equalsIgnoreCase(viewId))
3018           {
3019             // recover the existing alignpanel, alignframe, viewport
3020             af = views[v].alignFrame;
3021             av = views[v].av;
3022             ap = views[v];
3023             // TODO: could even skip resetting view settings if we don't want to
3024             // change the local settings from other jalview processes
3025             isnewview = false;
3026           }
3027         }
3028       }
3029     }
3030
3031     if (isnewview)
3032     {
3033       af = loadViewport(file, jseqs, hiddenSeqs, al, jms, view,
3034               uniqueSeqSetId, viewId, autoAlan);
3035       av = af.viewport;
3036       ap = af.alignPanel;
3037     }
3038     // LOAD TREES
3039     // /////////////////////////////////////
3040     if (loadTreesAndStructures && jms.getTreeCount() > 0)
3041     {
3042       try
3043       {
3044         for (int t = 0; t < jms.getTreeCount(); t++)
3045         {
3046
3047           Tree tree = jms.getTree(t);
3048
3049           TreePanel tp = (TreePanel) retrieveExistingObj(tree.getId());
3050           if (tp == null)
3051           {
3052             tp = af.ShowNewickTree(
3053                     new jalview.io.NewickFile(tree.getNewick()),
3054                     tree.getTitle(), tree.getWidth(), tree.getHeight(),
3055                     tree.getXpos(), tree.getYpos());
3056             if (tree.getId() != null)
3057             {
3058               // perhaps bind the tree id to something ?
3059             }
3060           }
3061           else
3062           {
3063             // update local tree attributes ?
3064             // TODO: should check if tp has been manipulated by user - if so its
3065             // settings shouldn't be modified
3066             tp.setTitle(tree.getTitle());
3067             tp.setBounds(new Rectangle(tree.getXpos(), tree.getYpos(), tree
3068                     .getWidth(), tree.getHeight()));
3069             tp.av = av; // af.viewport; // TODO: verify 'associate with all
3070             // views'
3071             // works still
3072             tp.treeCanvas.av = av; // af.viewport;
3073             tp.treeCanvas.ap = ap; // af.alignPanel;
3074
3075           }
3076           if (tp == null)
3077           {
3078             warn("There was a problem recovering stored Newick tree: \n"
3079                     + tree.getNewick());
3080             continue;
3081           }
3082
3083           tp.fitToWindow.setState(tree.getFitToWindow());
3084           tp.fitToWindow_actionPerformed(null);
3085
3086           if (tree.getFontName() != null)
3087           {
3088             tp.setTreeFont(new java.awt.Font(tree.getFontName(), tree
3089                     .getFontStyle(), tree.getFontSize()));
3090           }
3091           else
3092           {
3093             tp.setTreeFont(new java.awt.Font(view.getFontName(), view
3094                     .getFontStyle(), tree.getFontSize()));
3095           }
3096
3097           tp.showPlaceholders(tree.getMarkUnlinked());
3098           tp.showBootstrap(tree.getShowBootstrap());
3099           tp.showDistances(tree.getShowDistances());
3100
3101           tp.treeCanvas.threshold = tree.getThreshold();
3102
3103           if (tree.getCurrentTree())
3104           {
3105             af.viewport.setCurrentTree(tp.getTree());
3106           }
3107         }
3108
3109       } catch (Exception ex)
3110       {
3111         ex.printStackTrace();
3112       }
3113     }
3114
3115     // //LOAD STRUCTURES
3116     if (loadTreesAndStructures)
3117     {
3118       loadStructures(jprovider, jseqs, af, ap);
3119     }
3120     // and finally return.
3121     return af;
3122   }
3123
3124   /**
3125    * Load and link any saved structure viewers.
3126    * 
3127    * @param jprovider
3128    * @param jseqs
3129    * @param af
3130    * @param ap
3131    */
3132   protected void loadStructures(jarInputStreamProvider jprovider,
3133           JSeq[] jseqs, AlignFrame af, AlignmentPanel ap)
3134   {
3135     /*
3136      * Run through all PDB ids on the alignment, and collect mappings between
3137      * distinct view ids and all sequences referring to that view.
3138      */
3139     Map<String, StructureViewerModel> structureViewers = new LinkedHashMap<String, StructureViewerModel>();
3140
3141     for (int i = 0; i < jseqs.length; i++)
3142     {
3143       if (jseqs[i].getPdbidsCount() > 0)
3144       {
3145         Pdbids[] ids = jseqs[i].getPdbids();
3146         for (int p = 0; p < ids.length; p++)
3147         {
3148           final int structureStateCount = ids[p].getStructureStateCount();
3149           for (int s = 0; s < structureStateCount; s++)
3150           {
3151             // check to see if we haven't already created this structure view
3152             final StructureState structureState = ids[p]
3153                     .getStructureState(s);
3154             String sviewid = (structureState.getViewId() == null) ? null
3155                     : structureState.getViewId() + uniqueSetSuffix;
3156             jalview.datamodel.PDBEntry jpdb = new jalview.datamodel.PDBEntry();
3157             // Originally : ids[p].getFile()
3158             // : TODO: verify external PDB file recovery still works in normal
3159             // jalview project load
3160             jpdb.setFile(loadPDBFile(jprovider, ids[p].getId()));
3161             jpdb.setId(ids[p].getId());
3162
3163             int x = structureState.getXpos();
3164             int y = structureState.getYpos();
3165             int width = structureState.getWidth();
3166             int height = structureState.getHeight();
3167
3168             // Probably don't need to do this anymore...
3169             // Desktop.desktop.getComponentAt(x, y);
3170             // TODO: NOW: check that this recovers the PDB file correctly.
3171             String pdbFile = loadPDBFile(jprovider, ids[p].getId());
3172             jalview.datamodel.SequenceI seq = seqRefIds.get(jseqs[i]
3173                     .getId() + "");
3174             if (sviewid == null)
3175             {
3176               sviewid = "_jalview_pre2_4_" + x + "," + y + "," + width
3177                       + "," + height;
3178             }
3179             if (!structureViewers.containsKey(sviewid))
3180             {
3181               structureViewers.put(sviewid, new StructureViewerModel(x, y,
3182                       width, height, false, false, true));
3183               // Legacy pre-2.7 conversion JAL-823 :
3184               // do not assume any view has to be linked for colour by
3185               // sequence
3186             }
3187
3188             // assemble String[] { pdb files }, String[] { id for each
3189             // file }, orig_fileloc, SequenceI[][] {{ seqs_file 1 }, {
3190             // seqs_file 2}, boolean[] {
3191             // linkAlignPanel,superposeWithAlignpanel}} from hash
3192             StructureViewerModel jmoldat = structureViewers.get(sviewid);
3193             jmoldat.setAlignWithPanel(jmoldat.isAlignWithPanel()
3194                     | (structureState.hasAlignwithAlignPanel() ? structureState
3195                             .getAlignwithAlignPanel() : false));
3196
3197             /*
3198              * Default colour by linked panel to false if not specified (e.g.
3199              * for pre-2.7 projects)
3200              */
3201             boolean colourWithAlignPanel = jmoldat.isColourWithAlignPanel();
3202             colourWithAlignPanel |= (structureState
3203                     .hasColourwithAlignPanel() ? structureState
3204                     .getColourwithAlignPanel() : false);
3205             jmoldat.setColourWithAlignPanel(colourWithAlignPanel);
3206
3207             /*
3208              * Default colour by viewer to true if not specified (e.g. for
3209              * pre-2.7 projects)
3210              */
3211             boolean colourByViewer = jmoldat.isColourByViewer();
3212             colourByViewer &= structureState.hasColourByJmol() ? structureState
3213                     .getColourByJmol() : true;
3214             jmoldat.setColourByViewer(colourByViewer);
3215
3216             if (jmoldat.getStateData().length() < structureState
3217                     .getContent().length())
3218             {
3219               {
3220                 jmoldat.setStateData(structureState.getContent());
3221               }
3222             }
3223             if (ids[p].getFile() != null)
3224             {
3225               File mapkey = new File(ids[p].getFile());
3226               StructureData seqstrmaps = jmoldat.getFileData().get(mapkey);
3227               if (seqstrmaps == null)
3228               {
3229                 jmoldat.getFileData().put(
3230                         mapkey,
3231                         seqstrmaps = jmoldat.new StructureData(pdbFile,
3232                                 ids[p].getId()));
3233               }
3234               if (!seqstrmaps.getSeqList().contains(seq))
3235               {
3236                 seqstrmaps.getSeqList().add(seq);
3237                 // TODO and chains?
3238               }
3239             }
3240             else
3241             {
3242               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");
3243               warn(errorMessage);
3244             }
3245           }
3246         }
3247       }
3248     }
3249     // Instantiate the associated structure views
3250     for (Entry<String, StructureViewerModel> entry : structureViewers
3251             .entrySet())
3252       {
3253       createOrLinkStructureViewer(entry, af, ap);
3254     }
3255   }
3256
3257   /**
3258    * 
3259    * @param viewerData
3260    * @param af
3261    * @param ap
3262    */
3263   protected void createOrLinkStructureViewer(
3264           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
3265           AlignmentPanel ap)
3266   {
3267     final StructureViewerModel svattrib = viewerData.getValue();
3268
3269     /*
3270      * Search for any viewer windows already open from other alignment views
3271      * that exactly match the stored structure state
3272      */
3273     StructureViewerBase comp = findMatchingViewer(viewerData);
3274
3275     if (comp != null)
3276     {
3277       linkStructureViewer(ap, comp, svattrib);
3278       return;
3279     }
3280
3281     /*
3282      * Pending an XML element for ViewerType, just check if stateData contains
3283      * "chimera" (part of the chimera session filename).
3284      */
3285     if (svattrib.getStateData().indexOf("chimera") > -1)
3286     {
3287       createChimeraViewer(viewerData, af);
3288     }
3289     else
3290     {
3291       createJmolViewer(viewerData, af);
3292     }
3293   }
3294
3295   /**
3296    * Create a new Chimera viewer.
3297    * 
3298    * @param viewerData
3299    * @param af
3300    */
3301   protected void createChimeraViewer(
3302           Entry<String, StructureViewerModel> viewerData, AlignFrame af)
3303   {
3304     final StructureViewerModel data = viewerData.getValue();
3305     String chimeraSession = data.getStateData();
3306
3307     if (new File(chimeraSession).exists())
3308     {
3309       Set<Entry<File, StructureData>> fileData = data.getFileData()
3310               .entrySet();
3311       List<PDBEntry> pdbs = new ArrayList<PDBEntry>();
3312       List<SequenceI[]> allseqs = new ArrayList<SequenceI[]>();
3313       for (Entry<File, StructureData> pdb : fileData)
3314       {
3315         String filePath = pdb.getValue().getFilePath();
3316         String pdbId = pdb.getValue().getPdbId();
3317         pdbs.add(new PDBEntry(filePath, pdbId));
3318         final List<SequenceI> seqList = pdb.getValue().getSeqList();
3319         SequenceI[] seqs = seqList.toArray(new SequenceI[seqList.size()]);
3320         allseqs.add(seqs);
3321       }
3322
3323       boolean colourByChimera = data.isColourByViewer();
3324       boolean colourBySequence = data.isColourWithAlignPanel();
3325
3326       // TODO can/should this be done via StructureViewer (like Jmol)?
3327       final PDBEntry[] pdbArray = pdbs.toArray(new PDBEntry[pdbs.size()]);
3328       final SequenceI[][] seqsArray = allseqs.toArray(new SequenceI[allseqs
3329               .size()][]);
3330       new ChimeraViewFrame(chimeraSession, af.alignPanel, pdbArray,
3331               seqsArray, colourByChimera, colourBySequence);
3332     }
3333     else
3334     {
3335       Cache.log.error("Chimera session file " + chimeraSession
3336               + " not found");
3337     }
3338   }
3339
3340   /**
3341    * Create a new Jmol window. First parse the Jmol state to translate filenames
3342    * loaded into the view, and record the order in which files are shown in the
3343    * Jmol view, so we can add the sequence mappings in same order.
3344    * 
3345    * @param viewerData
3346    * @param af
3347    */
3348   protected void createJmolViewer(
3349           final Entry<String, StructureViewerModel> viewerData,
3350           AlignFrame af)
3351   {
3352     final StructureViewerModel svattrib = viewerData.getValue();
3353     String state = svattrib.getStateData();
3354     List<String> pdbfilenames = new ArrayList<String>();
3355     List<SequenceI[]> seqmaps = new ArrayList<SequenceI[]>();
3356     List<String> pdbids = new ArrayList<String>();
3357     StringBuilder newFileLoc = new StringBuilder(64);
3358     int cp = 0, ncp, ecp;
3359     Map<File, StructureData> oldFiles = svattrib.getFileData();
3360     while ((ncp = state.indexOf("load ", cp)) > -1)
3361     {
3362       do
3363       {
3364         // look for next filename in load statement
3365         newFileLoc.append(state.substring(cp,
3366                 ncp = (state.indexOf("\"", ncp + 1) + 1)));
3367         String oldfilenam = state.substring(ncp,
3368                 ecp = state.indexOf("\"", ncp));
3369         // recover the new mapping data for this old filename
3370         // have to normalize filename - since Jmol and jalview do
3371         // filename
3372         // translation differently.
3373         StructureData filedat = oldFiles.get(new File(oldfilenam));
3374         newFileLoc.append(Platform.escapeString(filedat.getFilePath()));
3375         pdbfilenames.add(filedat.getFilePath());
3376         pdbids.add(filedat.getPdbId());
3377         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
3378         newFileLoc.append("\"");
3379         cp = ecp + 1; // advance beyond last \" and set cursor so we can
3380                       // look for next file statement.
3381       } while ((ncp = state.indexOf("/*file*/", cp)) > -1);
3382     }
3383     if (cp > 0)
3384     {
3385       // just append rest of state
3386       newFileLoc.append(state.substring(cp));
3387     }
3388     else
3389     {
3390       System.err.print("Ignoring incomplete Jmol state for PDB ids: ");
3391       newFileLoc = new StringBuilder(state);
3392       newFileLoc.append("; load append ");
3393       for (File id : oldFiles.keySet())
3394       {
3395         // add this and any other pdb files that should be present in
3396         // the viewer
3397         StructureData filedat = oldFiles.get(id);
3398         newFileLoc.append(filedat.getFilePath());
3399         pdbfilenames.add(filedat.getFilePath());
3400         pdbids.add(filedat.getPdbId());
3401         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
3402         newFileLoc.append(" \"");
3403         newFileLoc.append(filedat.getFilePath());
3404         newFileLoc.append("\"");
3405
3406       }
3407       newFileLoc.append(";");
3408     }
3409
3410     if (newFileLoc.length() > 0)
3411     {
3412       int histbug = newFileLoc.indexOf("history = ");
3413       histbug += 10;
3414       int diff = histbug == -1 ? -1 : newFileLoc.indexOf(";", histbug);
3415       String val = (diff == -1) ? null : newFileLoc
3416               .substring(histbug, diff);
3417       if (val != null && val.length() >= 4)
3418       {
3419         if (val.contains("e"))
3420         {
3421           if (val.trim().equals("true"))
3422           {
3423             val = "1";
3424           }
3425           else
3426           {
3427             val = "0";
3428           }
3429           newFileLoc.replace(histbug, diff, val);
3430         }
3431       }
3432
3433       final String[] pdbf = pdbfilenames.toArray(new String[pdbfilenames
3434               .size()]);
3435       final String[] id = pdbids.toArray(new String[pdbids.size()]);
3436       final SequenceI[][] sq = seqmaps
3437               .toArray(new SequenceI[seqmaps.size()][]);
3438       final String fileloc = newFileLoc.toString();
3439       final String sviewid = viewerData.getKey();
3440       final AlignFrame alf = af;
3441       final Rectangle rect = new Rectangle(svattrib.getX(),
3442               svattrib.getY(), svattrib.getWidth(), svattrib.getHeight());
3443       try
3444       {
3445         javax.swing.SwingUtilities.invokeAndWait(new Runnable()
3446         {
3447           @Override
3448           public void run()
3449           {
3450             JalviewStructureDisplayI sview = null;
3451             try
3452             {
3453               // JAL-1333 note - we probably can't migrate Jmol views to UCSF
3454               // Chimera!
3455               sview = new StructureViewer(alf.alignPanel
3456                       .getStructureSelectionManager()).createView(
3457                       StructureViewer.ViewerType.JMOL, pdbf, id, sq,
3458                       alf.alignPanel, svattrib, fileloc, rect, sviewid);
3459               addNewStructureViewer(sview);
3460             } catch (OutOfMemoryError ex)
3461             {
3462               new OOMWarning("restoring structure view for PDB id " + id,
3463                       (OutOfMemoryError) ex.getCause());
3464               if (sview != null && sview.isVisible())
3465               {
3466                 sview.closeViewer();
3467                 sview.setVisible(false);
3468                 sview.dispose();
3469               }
3470             }
3471           }
3472         });
3473       } catch (InvocationTargetException ex)
3474       {
3475         warn("Unexpected error when opening Jmol view.", ex);
3476
3477       } catch (InterruptedException e)
3478       {
3479         // e.printStackTrace();
3480       }
3481     }
3482   }
3483
3484   /**
3485    * Returns any open frame that matches given structure viewer data. The match
3486    * is based on the unique viewId, or (for older project versions) the frame's
3487    * geometry.
3488    * 
3489    * @param viewerData
3490    * @return
3491    */
3492   protected StructureViewerBase findMatchingViewer(
3493           Entry<String, StructureViewerModel> viewerData)
3494   {
3495     final String sviewid = viewerData.getKey();
3496     final StructureViewerModel svattrib = viewerData.getValue();
3497     StructureViewerBase comp = null;
3498     JInternalFrame[] frames = getAllFrames();
3499     for (JInternalFrame frame : frames)
3500     {
3501       if (frame instanceof StructureViewerBase)
3502       {
3503         /*
3504          * Post jalview 2.4 schema includes structure view id
3505          */
3506         if (sviewid != null
3507                 && ((StructureViewerBase) frame).getViewId()
3508                         .equals(sviewid))
3509         {
3510           comp = (AppJmol) frame;
3511           // todo: break?
3512         }
3513         /*
3514          * Otherwise test for matching position and size of viewer frame
3515          */
3516         else if (frame.getX() == svattrib.getX()
3517                 && frame.getY() == svattrib.getY()
3518                 && frame.getHeight() == svattrib.getHeight()
3519                 && frame.getWidth() == svattrib.getWidth())
3520         {
3521           comp = (AppJmol) frame;
3522           // todo: break?
3523         }
3524       }
3525     }
3526     return comp;
3527   }
3528
3529   /**
3530    * Link an AlignmentPanel to an existing structure viewer.
3531    * 
3532    * @param ap
3533    * @param viewer
3534    * @param oldFiles
3535    * @param useinViewerSuperpos
3536    * @param usetoColourbyseq
3537    * @param viewerColouring
3538    */
3539   protected void linkStructureViewer(AlignmentPanel ap,
3540           StructureViewerBase viewer, StructureViewerModel svattrib)
3541   {
3542     // NOTE: if the jalview project is part of a shared session then
3543     // view synchronization should/could be done here.
3544
3545     final boolean useinViewerSuperpos = svattrib.isAlignWithPanel();
3546     final boolean usetoColourbyseq = svattrib.isColourWithAlignPanel();
3547     final boolean viewerColouring = svattrib.isColourByViewer();
3548     Map<File, StructureData> oldFiles = svattrib.getFileData();
3549
3550     /*
3551      * Add mapping for sequences in this view to an already open viewer
3552      */
3553     final AAStructureBindingModel binding = viewer.getBinding();
3554     for (File id : oldFiles.keySet())
3555     {
3556       // add this and any other pdb files that should be present in the
3557       // viewer
3558       StructureData filedat = oldFiles.get(id);
3559       String pdbFile = filedat.getFilePath();
3560       SequenceI[] seq = filedat.getSeqList().toArray(new SequenceI[0]);
3561       binding.getSsm().setMapping(seq, null, pdbFile,
3562               jalview.io.AppletFormatAdapter.FILE);
3563       binding.addSequenceForStructFile(pdbFile, seq);
3564     }
3565     // and add the AlignmentPanel's reference to the view panel
3566     viewer.addAlignmentPanel(ap);
3567     if (useinViewerSuperpos)
3568     {
3569       viewer.useAlignmentPanelForSuperposition(ap);
3570     }
3571     else
3572     {
3573       viewer.excludeAlignmentPanelForSuperposition(ap);
3574     }
3575     if (usetoColourbyseq)
3576     {
3577       viewer.useAlignmentPanelForColourbyseq(ap, !viewerColouring);
3578     }
3579     else
3580     {
3581       viewer.excludeAlignmentPanelForColourbyseq(ap);
3582     }
3583   }
3584
3585   /**
3586    * Get all frames within the Desktop.
3587    * 
3588    * @return
3589    */
3590   protected JInternalFrame[] getAllFrames()
3591   {
3592     JInternalFrame[] frames = null;
3593     // TODO is this necessary - is it safe - risk of hanging?
3594     do
3595     {
3596       try
3597       {
3598         frames = Desktop.desktop.getAllFrames();
3599       } catch (ArrayIndexOutOfBoundsException e)
3600       {
3601         // occasional No such child exceptions are thrown here...
3602         try
3603         {
3604           Thread.sleep(10);
3605         } catch (InterruptedException f)
3606         {
3607         }
3608       }
3609     } while (frames == null);
3610     return frames;
3611   }
3612
3613   /**
3614    * 
3615    * @param supported
3616    *          - minimum version we are comparing against
3617    * @param version
3618    *          - version of data being processsed.
3619    * @return true if version is development/null or evaluates to the same or
3620    *         later X.Y.Z (where X,Y,Z are like [0-9]+b?[0-9]*)
3621    */
3622   private boolean isVersionStringLaterThan(String supported, String version)
3623   {
3624     if (version == null || version.equalsIgnoreCase("DEVELOPMENT BUILD")
3625             || version.equalsIgnoreCase("Test")
3626             || version.equalsIgnoreCase("AUTOMATED BUILD"))
3627     {
3628       System.err.println("Assuming project file with "
3629               + (version == null ? "null" : version)
3630               + " is compatible with Jalview version " + supported);
3631       return true;
3632     }
3633     else
3634     {
3635       StringTokenizer currentV = new StringTokenizer(supported, "."), fileV = new StringTokenizer(
3636               version, ".");
3637       while (currentV.hasMoreTokens() && fileV.hasMoreTokens())
3638       {
3639         // convert b to decimal to catch bugfix releases within a series
3640         String curT = currentV.nextToken().toLowerCase().replace('b', '.');
3641         String fileT = fileV.nextToken().toLowerCase().replace('b', '.');
3642         try
3643         {
3644           if (Float.valueOf(curT) > Float.valueOf(fileT))
3645           {
3646             // current version is newer than the version that wrote the file
3647             return false;
3648           }
3649         } catch (NumberFormatException nfe)
3650         {
3651           System.err
3652                   .println("** WARNING: Version comparison failed for tokens ("
3653                           + curT
3654                           + ") and ("
3655                           + fileT
3656                           + ")\n** Current: '"
3657                           + supported + "' and Version: '" + version + "'");
3658         }
3659       }
3660       if (currentV.hasMoreElements())
3661       {
3662         // fileV has no minor version but identical series to current
3663         return false;
3664       }
3665     }
3666     return true;
3667   }
3668
3669   Vector<JalviewStructureDisplayI> newStructureViewers = null;
3670
3671   protected void addNewStructureViewer(JalviewStructureDisplayI sview)
3672   {
3673     if (newStructureViewers != null)
3674     {
3675       sview.getBinding().setFinishedLoadingFromArchive(false);
3676       newStructureViewers.add(sview);
3677     }
3678   }
3679
3680   protected void setLoadingFinishedForNewStructureViewers()
3681   {
3682     if (newStructureViewers != null)
3683     {
3684       for (JalviewStructureDisplayI sview : newStructureViewers)
3685       {
3686         sview.getBinding().setFinishedLoadingFromArchive(true);
3687       }
3688       newStructureViewers.clear();
3689       newStructureViewers = null;
3690     }
3691   }
3692
3693   AlignFrame loadViewport(String file, JSeq[] JSEQ,
3694           List<SequenceI> hiddenSeqs, Alignment al,
3695           JalviewModelSequence jms, Viewport view, String uniqueSeqSetId,
3696           String viewId, List<JvAnnotRow> autoAlan)
3697   {
3698     AlignFrame af = null;
3699     af = new AlignFrame(al, view.getWidth(), view.getHeight(),
3700             uniqueSeqSetId, viewId);
3701
3702     af.setFileName(file, "Jalview");
3703
3704     for (int i = 0; i < JSEQ.length; i++)
3705     {
3706       af.viewport.setSequenceColour(af.viewport.getAlignment()
3707               .getSequenceAt(i), new java.awt.Color(JSEQ[i].getColour()));
3708     }
3709
3710     af.viewport.setGatherViewsHere(view.getGatheredViews());
3711
3712     if (view.getSequenceSetId() != null)
3713     {
3714       AlignmentViewport av = viewportsAdded.get(uniqueSeqSetId);
3715
3716       af.viewport.setSequenceSetId(uniqueSeqSetId);
3717       if (av != null)
3718       {
3719         // propagate shared settings to this new view
3720         af.viewport.setHistoryList(av.getHistoryList());
3721         af.viewport.setRedoList(av.getRedoList());
3722       }
3723       else
3724       {
3725         viewportsAdded.put(uniqueSeqSetId, af.viewport);
3726       }
3727       // TODO: check if this method can be called repeatedly without
3728       // side-effects if alignpanel already registered.
3729       PaintRefresher.Register(af.alignPanel, uniqueSeqSetId);
3730     }
3731     // apply Hidden regions to view.
3732     if (hiddenSeqs != null)
3733     {
3734       for (int s = 0; s < JSEQ.length; s++)
3735       {
3736         jalview.datamodel.SequenceGroup hidden = new jalview.datamodel.SequenceGroup();
3737
3738         for (int r = 0; r < JSEQ[s].getHiddenSequencesCount(); r++)
3739         {
3740           hidden.addSequence(
3741                   al.getSequenceAt(JSEQ[s].getHiddenSequences(r)), false);
3742         }
3743         af.viewport.hideRepSequences(al.getSequenceAt(s), hidden);
3744       }
3745
3746       // jalview.datamodel.SequenceI[] hseqs = new
3747       // jalview.datamodel.SequenceI[hiddenSeqs
3748       // .size()];
3749       //
3750       // for (int s = 0; s < hiddenSeqs.size(); s++)
3751       // {
3752       // hseqs[s] = (jalview.datamodel.SequenceI) hiddenSeqs.elementAt(s);
3753       // }
3754
3755       SequenceI[] hseqs = hiddenSeqs.toArray(new SequenceI[hiddenSeqs
3756               .size()]);
3757       af.viewport.hideSequence(hseqs);
3758
3759     }
3760     // recover view properties and display parameters
3761     if (view.getViewName() != null)
3762     {
3763       af.viewport.viewName = view.getViewName();
3764       af.setInitialTabVisible();
3765     }
3766     af.setBounds(view.getXpos(), view.getYpos(), view.getWidth(),
3767             view.getHeight());
3768
3769     af.viewport.setShowAnnotation(view.getShowAnnotation());
3770     af.viewport.setAbovePIDThreshold(view.getPidSelected());
3771
3772     af.viewport.setColourText(view.getShowColourText());
3773
3774     af.viewport.setConservationSelected(view.getConservationSelected());
3775     af.viewport.setShowJVSuffix(view.getShowFullId());
3776     af.viewport.setRightAlignIds(view.getRightAlignIds());
3777     af.viewport.setFont(
3778             new java.awt.Font(view.getFontName(), view.getFontStyle(), view
3779                     .getFontSize()), true);
3780     // TODO: allow custom charWidth/Heights to be restored by updating them
3781     // after setting font - which means set above to false
3782     af.viewport.setRenderGaps(view.getRenderGaps());
3783     af.viewport.setWrapAlignment(view.getWrapAlignment());
3784     af.viewport.setShowAnnotation(view.getShowAnnotation());
3785
3786     af.viewport.setShowBoxes(view.getShowBoxes());
3787
3788     af.viewport.setShowText(view.getShowText());
3789
3790     af.viewport.setTextColour(new java.awt.Color(view.getTextCol1()));
3791     af.viewport.setTextColour2(new java.awt.Color(view.getTextCol2()));
3792     af.viewport.setThresholdTextColour(view.getTextColThreshold());
3793     af.viewport.setShowUnconserved(view.hasShowUnconserved() ? view
3794             .isShowUnconserved() : false);
3795     af.viewport.setStartRes(view.getStartRes());
3796     af.viewport.setStartSeq(view.getStartSeq());
3797     af.alignPanel.updateLayout();
3798     ColourSchemeI cs = null;
3799     // apply colourschemes
3800     if (view.getBgColour() != null)
3801     {
3802       if (view.getBgColour().startsWith("ucs"))
3803       {
3804         cs = getUserColourScheme(jms, view.getBgColour());
3805       }
3806       else if (view.getBgColour().startsWith("Annotation"))
3807       {
3808         AnnotationColours viewAnnColour = view.getAnnotationColours();
3809         cs = constructAnnotationColour(viewAnnColour, af, al, jms, true);
3810
3811         // annpos
3812
3813       }
3814       else
3815       {
3816         cs = ColourSchemeProperty.getColour(al, view.getBgColour());
3817       }
3818
3819       if (cs != null)
3820       {
3821         cs.setThreshold(view.getPidThreshold(), true);
3822         cs.setConsensus(af.viewport.getSequenceConsensusHash());
3823       }
3824     }
3825
3826     af.viewport.setGlobalColourScheme(cs);
3827     af.viewport.setColourAppliesToAllGroups(false);
3828
3829     if (view.getConservationSelected() && cs != null)
3830     {
3831       cs.setConservationInc(view.getConsThreshold());
3832     }
3833
3834     af.changeColour(cs);
3835
3836     af.viewport.setColourAppliesToAllGroups(true);
3837
3838     af.viewport.setShowSequenceFeatures(view.getShowSequenceFeatures());
3839
3840     if (view.hasCentreColumnLabels())
3841     {
3842       af.viewport.setCentreColumnLabels(view.getCentreColumnLabels());
3843     }
3844     if (view.hasIgnoreGapsinConsensus())
3845     {
3846       af.viewport.setIgnoreGapsConsensus(view.getIgnoreGapsinConsensus(),
3847               null);
3848     }
3849     if (view.hasFollowHighlight())
3850     {
3851       af.viewport.followHighlight = view.getFollowHighlight();
3852     }
3853     if (view.hasFollowSelection())
3854     {
3855       af.viewport.followSelection = view.getFollowSelection();
3856     }
3857     if (view.hasShowConsensusHistogram())
3858     {
3859       af.viewport.setShowConsensusHistogram(view
3860               .getShowConsensusHistogram());
3861     }
3862     else
3863     {
3864       af.viewport.setShowConsensusHistogram(true);
3865     }
3866     if (view.hasShowSequenceLogo())
3867     {
3868       af.viewport.setShowSequenceLogo(view.getShowSequenceLogo());
3869     }
3870     else
3871     {
3872       af.viewport.setShowSequenceLogo(false);
3873     }
3874     if (view.hasNormaliseSequenceLogo())
3875     {
3876       af.viewport.setNormaliseSequenceLogo(view.getNormaliseSequenceLogo());
3877     }
3878     if (view.hasShowDbRefTooltip())
3879     {
3880       af.viewport.setShowDBRefs(view.getShowDbRefTooltip());
3881     }
3882     if (view.hasShowNPfeatureTooltip())
3883     {
3884       af.viewport.setShowNPFeats(view.hasShowNPfeatureTooltip());
3885     }
3886     if (view.hasShowGroupConsensus())
3887     {
3888       af.viewport.setShowGroupConsensus(view.getShowGroupConsensus());
3889     }
3890     else
3891     {
3892       af.viewport.setShowGroupConsensus(false);
3893     }
3894     if (view.hasShowGroupConservation())
3895     {
3896       af.viewport.setShowGroupConservation(view.getShowGroupConservation());
3897     }
3898     else
3899     {
3900       af.viewport.setShowGroupConservation(false);
3901     }
3902
3903     // recover featre settings
3904     if (jms.getFeatureSettings() != null)
3905     {
3906       FeaturesDisplayed fdi;
3907       af.viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
3908       String[] renderOrder = new String[jms.getFeatureSettings()
3909               .getSettingCount()];
3910       Hashtable featureGroups = new Hashtable();
3911       Hashtable featureColours = new Hashtable();
3912       Hashtable featureOrder = new Hashtable();
3913
3914       for (int fs = 0; fs < jms.getFeatureSettings().getSettingCount(); fs++)
3915       {
3916         Setting setting = jms.getFeatureSettings().getSetting(fs);
3917         if (setting.hasMincolour())
3918         {
3919           GraduatedColor gc = setting.hasMin() ? new GraduatedColor(
3920                   new java.awt.Color(setting.getMincolour()),
3921                   new java.awt.Color(setting.getColour()),
3922                   setting.getMin(), setting.getMax()) : new GraduatedColor(
3923                   new java.awt.Color(setting.getMincolour()),
3924                   new java.awt.Color(setting.getColour()), 0, 1);
3925           if (setting.hasThreshold())
3926           {
3927             gc.setThresh(setting.getThreshold());
3928             gc.setThreshType(setting.getThreshstate());
3929           }
3930           gc.setAutoScaled(true); // default
3931           if (setting.hasAutoScale())
3932           {
3933             gc.setAutoScaled(setting.getAutoScale());
3934           }
3935           if (setting.hasColourByLabel())
3936           {
3937             gc.setColourByLabel(setting.getColourByLabel());
3938           }
3939           // and put in the feature colour table.
3940           featureColours.put(setting.getType(), gc);
3941         }
3942         else
3943         {
3944           featureColours.put(setting.getType(),
3945                   new java.awt.Color(setting.getColour()));
3946         }
3947         renderOrder[fs] = setting.getType();
3948         if (setting.hasOrder())
3949         {
3950           featureOrder.put(setting.getType(), setting.getOrder());
3951         }
3952         else
3953         {
3954           featureOrder.put(setting.getType(), new Float(fs
3955                   / jms.getFeatureSettings().getSettingCount()));
3956         }
3957         if (setting.getDisplay())
3958         {
3959           fdi.setVisible(setting.getType());
3960         }
3961       }
3962       Hashtable fgtable = new Hashtable();
3963       for (int gs = 0; gs < jms.getFeatureSettings().getGroupCount(); gs++)
3964       {
3965         Group grp = jms.getFeatureSettings().getGroup(gs);
3966         fgtable.put(grp.getName(), new Boolean(grp.getDisplay()));
3967       }
3968       // FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
3969       // fgtable, featureColours, jms.getFeatureSettings().hasTransparency() ?
3970       // jms.getFeatureSettings().getTransparency() : 0.0, featureOrder);
3971       FeatureRendererSettings frs = new FeatureRendererSettings(
3972               renderOrder, fgtable, featureColours, 1.0f, featureOrder);
3973       af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer()
3974               .transferSettings(frs);
3975
3976     }
3977
3978     if (view.getHiddenColumnsCount() > 0)
3979     {
3980       for (int c = 0; c < view.getHiddenColumnsCount(); c++)
3981       {
3982         af.viewport.hideColumns(view.getHiddenColumns(c).getStart(), view
3983                 .getHiddenColumns(c).getEnd() // +1
3984                 );
3985       }
3986     }
3987     if (view.getCalcIdParam() != null)
3988     {
3989       for (CalcIdParam calcIdParam : view.getCalcIdParam())
3990       {
3991         if (calcIdParam != null)
3992         {
3993           if (recoverCalcIdParam(calcIdParam, af.viewport))
3994           {
3995           }
3996           else
3997           {
3998             warn("Couldn't recover parameters for "
3999                     + calcIdParam.getCalcId());
4000           }
4001         }
4002       }
4003     }
4004     af.setMenusFromViewport(af.viewport);
4005     
4006     // TODO: we don't need to do this if the viewport is aready visible.
4007     /*
4008      * Add the AlignFrame to the desktop (it may be 'gathered' later), unless it
4009      * has a 'cdna/protein complement' view, in which case save it in order to
4010      * populate a SplitFrame once all views have been read in.
4011      */
4012     String complementaryViewId = view.getComplementId();
4013     if (complementaryViewId == null)
4014     {
4015       Desktop.addInternalFrame(af, view.getTitle(), view.getWidth(),
4016               view.getHeight());
4017       // recompute any autoannotation
4018       af.alignPanel.updateAnnotation(false, true);
4019       reorderAutoannotation(af, al, autoAlan);
4020       af.alignPanel.alignmentChanged();
4021     }
4022     else
4023     {
4024       splitFrameCandidates.put(view, af);
4025     }
4026     return af;
4027   }
4028
4029   private ColourSchemeI constructAnnotationColour(
4030           AnnotationColours viewAnnColour, AlignFrame af, Alignment al,
4031           JalviewModelSequence jms, boolean checkGroupAnnColour)
4032   {
4033     boolean propagateAnnColour = false;
4034     ColourSchemeI cs = null;
4035     AlignmentI annAlignment = af != null ? af.viewport.getAlignment() : al;
4036     if (checkGroupAnnColour && al.getGroups() != null
4037             && al.getGroups().size() > 0)
4038     {
4039       // pre 2.8.1 behaviour
4040       // check to see if we should transfer annotation colours
4041       propagateAnnColour = true;
4042       for (jalview.datamodel.SequenceGroup sg : al.getGroups())
4043       {
4044         if (sg.cs instanceof AnnotationColourGradient)
4045         {
4046           propagateAnnColour = false;
4047         }
4048       }
4049     }
4050     // int find annotation
4051     if (annAlignment.getAlignmentAnnotation() != null)
4052     {
4053       for (int i = 0; i < annAlignment.getAlignmentAnnotation().length; i++)
4054       {
4055         if (annAlignment.getAlignmentAnnotation()[i].label
4056                 .equals(viewAnnColour.getAnnotation()))
4057         {
4058           if (annAlignment.getAlignmentAnnotation()[i].getThreshold() == null)
4059           {
4060             annAlignment.getAlignmentAnnotation()[i]
4061                     .setThreshold(new jalview.datamodel.GraphLine(
4062                             viewAnnColour.getThreshold(), "Threshold",
4063                             java.awt.Color.black)
4064
4065                     );
4066           }
4067
4068           if (viewAnnColour.getColourScheme().equals("None"))
4069           {
4070             cs = new AnnotationColourGradient(
4071                     annAlignment.getAlignmentAnnotation()[i],
4072                     new java.awt.Color(viewAnnColour.getMinColour()),
4073                     new java.awt.Color(viewAnnColour.getMaxColour()),
4074                     viewAnnColour.getAboveThreshold());
4075           }
4076           else if (viewAnnColour.getColourScheme().startsWith("ucs"))
4077           {
4078             cs = new AnnotationColourGradient(
4079                     annAlignment.getAlignmentAnnotation()[i],
4080                     getUserColourScheme(jms,
4081                             viewAnnColour.getColourScheme()),
4082                     viewAnnColour.getAboveThreshold());
4083           }
4084           else
4085           {
4086             cs = new AnnotationColourGradient(
4087                     annAlignment.getAlignmentAnnotation()[i],
4088                     ColourSchemeProperty.getColour(al,
4089                             viewAnnColour.getColourScheme()),
4090                     viewAnnColour.getAboveThreshold());
4091           }
4092           if (viewAnnColour.hasPerSequence())
4093           {
4094             ((AnnotationColourGradient) cs).setSeqAssociated(viewAnnColour
4095                     .isPerSequence());
4096           }
4097           if (viewAnnColour.hasPredefinedColours())
4098           {
4099             ((AnnotationColourGradient) cs)
4100                     .setPredefinedColours(viewAnnColour
4101                             .isPredefinedColours());
4102           }
4103           if (propagateAnnColour && al.getGroups() != null)
4104           {
4105             // Also use these settings for all the groups
4106             for (int g = 0; g < al.getGroups().size(); g++)
4107             {
4108               jalview.datamodel.SequenceGroup sg = al.getGroups().get(g);
4109
4110               if (sg.cs == null)
4111               {
4112                 continue;
4113               }
4114
4115               /*
4116                * if (viewAnnColour.getColourScheme().equals("None" )) { sg.cs =
4117                * new AnnotationColourGradient(
4118                * annAlignment.getAlignmentAnnotation()[i], new
4119                * java.awt.Color(viewAnnColour. getMinColour()), new
4120                * java.awt.Color(viewAnnColour. getMaxColour()),
4121                * viewAnnColour.getAboveThreshold()); } else
4122                */
4123               {
4124                 sg.cs = new AnnotationColourGradient(
4125                         annAlignment.getAlignmentAnnotation()[i], sg.cs,
4126                         viewAnnColour.getAboveThreshold());
4127                 if (cs instanceof AnnotationColourGradient)
4128                 {
4129                   if (viewAnnColour.hasPerSequence())
4130                   {
4131                     ((AnnotationColourGradient) cs)
4132                             .setSeqAssociated(viewAnnColour.isPerSequence());
4133                   }
4134                   if (viewAnnColour.hasPredefinedColours())
4135                   {
4136                     ((AnnotationColourGradient) cs)
4137                             .setPredefinedColours(viewAnnColour
4138                                     .isPredefinedColours());
4139                   }
4140                 }
4141               }
4142
4143             }
4144           }
4145
4146           break;
4147         }
4148
4149       }
4150     }
4151     return cs;
4152   }
4153
4154   private void reorderAutoannotation(AlignFrame af, Alignment al,
4155           List<JvAnnotRow> autoAlan)
4156   {
4157     // copy over visualization settings for autocalculated annotation in the
4158     // view
4159     if (al.getAlignmentAnnotation() != null)
4160     {
4161       /**
4162        * Kludge for magic autoannotation names (see JAL-811)
4163        */
4164       String[] magicNames = new String[]
4165       { "Consensus", "Quality", "Conservation" };
4166       JvAnnotRow nullAnnot = new JvAnnotRow(-1, null);
4167       Hashtable<String, JvAnnotRow> visan = new Hashtable<String, JvAnnotRow>();
4168       for (String nm : magicNames)
4169       {
4170         visan.put(nm, nullAnnot);
4171       }
4172       for (JvAnnotRow auan : autoAlan)
4173       {
4174         visan.put(auan.template.label
4175                 + (auan.template.getCalcId() == null ? "" : "\t"
4176                         + auan.template.getCalcId()), auan);
4177       }
4178       int hSize = al.getAlignmentAnnotation().length;
4179       List<JvAnnotRow> reorder = new ArrayList<JvAnnotRow>();
4180       // work through any autoCalculated annotation already on the view
4181       // removing it if it should be placed in a different location on the
4182       // annotation panel.
4183       List<String> remains = new ArrayList<String>(visan.keySet());
4184       for (int h = 0; h < hSize; h++)
4185       {
4186         jalview.datamodel.AlignmentAnnotation jalan = al
4187                 .getAlignmentAnnotation()[h];
4188         if (jalan.autoCalculated)
4189         {
4190           String k;
4191           JvAnnotRow valan = visan.get(k = jalan.label);
4192           if (jalan.getCalcId() != null)
4193           {
4194             valan = visan.get(k = jalan.label + "\t" + jalan.getCalcId());
4195           }
4196
4197           if (valan != null)
4198           {
4199             // delete the auto calculated row from the alignment
4200             al.deleteAnnotation(jalan, false);
4201             remains.remove(k);
4202             hSize--;
4203             h--;
4204             if (valan != nullAnnot)
4205             {
4206               if (jalan != valan.template)
4207               {
4208                 // newly created autoannotation row instance
4209                 // so keep a reference to the visible annotation row
4210                 // and copy over all relevant attributes
4211                 if (valan.template.graphHeight >= 0)
4212
4213                 {
4214                   jalan.graphHeight = valan.template.graphHeight;
4215                 }
4216                 jalan.visible = valan.template.visible;
4217               }
4218               reorder.add(new JvAnnotRow(valan.order, jalan));
4219             }
4220           }
4221         }
4222       }
4223       // Add any (possibly stale) autocalculated rows that were not appended to
4224       // the view during construction
4225       for (String other : remains)
4226       {
4227         JvAnnotRow othera = visan.get(other);
4228         if (othera != nullAnnot && othera.template.getCalcId() != null
4229                 && othera.template.getCalcId().length() > 0)
4230         {
4231           reorder.add(othera);
4232         }
4233       }
4234       // now put the automatic annotation in its correct place
4235       int s = 0, srt[] = new int[reorder.size()];
4236       JvAnnotRow[] rws = new JvAnnotRow[reorder.size()];
4237       for (JvAnnotRow jvar : reorder)
4238       {
4239         rws[s] = jvar;
4240         srt[s++] = jvar.order;
4241       }
4242       reorder.clear();
4243       jalview.util.QuickSort.sort(srt, rws);
4244       // and re-insert the annotation at its correct position
4245       for (JvAnnotRow jvar : rws)
4246       {
4247         al.addAnnotation(jvar.template, jvar.order);
4248       }
4249       af.alignPanel.adjustAnnotationHeight();
4250     }
4251   }
4252
4253   Hashtable skipList = null;
4254
4255   /**
4256    * TODO remove this method
4257    * 
4258    * @param view
4259    * @return AlignFrame bound to sequenceSetId from view, if one exists. private
4260    *         AlignFrame getSkippedFrame(Viewport view) { if (skipList==null) {
4261    *         throw new Error("Implementation Error. No skipList defined for this
4262    *         Jalview2XML instance."); } return (AlignFrame)
4263    *         skipList.get(view.getSequenceSetId()); }
4264    */
4265
4266   /**
4267    * Check if the Jalview view contained in object should be skipped or not.
4268    * 
4269    * @param object
4270    * @return true if view's sequenceSetId is a key in skipList
4271    */
4272   private boolean skipViewport(JalviewModel object)
4273   {
4274     if (skipList == null)
4275     {
4276       return false;
4277     }
4278     String id;
4279     if (skipList.containsKey(id = object.getJalviewModelSequence()
4280             .getViewport()[0].getSequenceSetId()))
4281     {
4282       if (Cache.log != null && Cache.log.isDebugEnabled())
4283       {
4284         Cache.log.debug("Skipping seuqence set id " + id);
4285       }
4286       return true;
4287     }
4288     return false;
4289   }
4290
4291   public void addToSkipList(AlignFrame af)
4292   {
4293     if (skipList == null)
4294     {
4295       skipList = new Hashtable();
4296     }
4297     skipList.put(af.getViewport().getSequenceSetId(), af);
4298   }
4299
4300   public void clearSkipList()
4301   {
4302     if (skipList != null)
4303     {
4304       skipList.clear();
4305       skipList = null;
4306     }
4307   }
4308
4309   private void recoverDatasetFor(SequenceSet vamsasSet, Alignment al,
4310           boolean ignoreUnrefed)
4311   {
4312     jalview.datamodel.Alignment ds = getDatasetFor(vamsasSet.getDatasetId());
4313     Vector dseqs = null;
4314     if (ds == null)
4315     {
4316       // create a list of new dataset sequences
4317       dseqs = new Vector();
4318     }
4319     for (int i = 0, iSize = vamsasSet.getSequenceCount(); i < iSize; i++)
4320     {
4321       Sequence vamsasSeq = vamsasSet.getSequence(i);
4322       ensureJalviewDatasetSequence(vamsasSeq, ds, dseqs, ignoreUnrefed);
4323     }
4324     // create a new dataset
4325     if (ds == null)
4326     {
4327       SequenceI[] dsseqs = new SequenceI[dseqs.size()];
4328       dseqs.copyInto(dsseqs);
4329       ds = new jalview.datamodel.Alignment(dsseqs);
4330       debug("Created new dataset " + vamsasSet.getDatasetId()
4331               + " for alignment " + System.identityHashCode(al));
4332       addDatasetRef(vamsasSet.getDatasetId(), ds);
4333     }
4334     // set the dataset for the newly imported alignment.
4335     if (al.getDataset() == null && !ignoreUnrefed)
4336     {
4337       al.setDataset(ds);
4338     }
4339   }
4340
4341   /**
4342    * 
4343    * @param vamsasSeq
4344    *          sequence definition to create/merge dataset sequence for
4345    * @param ds
4346    *          dataset alignment
4347    * @param dseqs
4348    *          vector to add new dataset sequence to
4349    */
4350   private void ensureJalviewDatasetSequence(Sequence vamsasSeq,
4351           AlignmentI ds, Vector dseqs, boolean ignoreUnrefed)
4352   {
4353     // JBP TODO: Check this is called for AlCodonFrames to support recovery of
4354     // xRef Codon Maps
4355     SequenceI sq = seqRefIds.get(vamsasSeq.getId());
4356     SequenceI dsq = null;
4357     if (sq != null && sq.getDatasetSequence() != null)
4358     {
4359       dsq = sq.getDatasetSequence();
4360     }
4361     if (sq == null && ignoreUnrefed)
4362     {
4363       return;
4364     }
4365     String sqid = vamsasSeq.getDsseqid();
4366     if (dsq == null)
4367     {
4368       // need to create or add a new dataset sequence reference to this sequence
4369       if (sqid != null)
4370       {
4371         dsq = seqRefIds.get(sqid);
4372       }
4373       // check again
4374       if (dsq == null)
4375       {
4376         // make a new dataset sequence
4377         dsq = sq.createDatasetSequence();
4378         if (sqid == null)
4379         {
4380           // make up a new dataset reference for this sequence
4381           sqid = seqHash(dsq);
4382         }
4383         dsq.setVamsasId(uniqueSetSuffix + sqid);
4384         seqRefIds.put(sqid, dsq);
4385         if (ds == null)
4386         {
4387           if (dseqs != null)
4388           {
4389             dseqs.addElement(dsq);
4390           }
4391         }
4392         else
4393         {
4394           ds.addSequence(dsq);
4395         }
4396       }
4397       else
4398       {
4399         if (sq != dsq)
4400         { // make this dataset sequence sq's dataset sequence
4401           sq.setDatasetSequence(dsq);
4402           // and update the current dataset alignment
4403           if (ds == null)
4404           {
4405             if (dseqs != null)
4406             {
4407               if (!dseqs.contains(dsq))
4408               {
4409                 dseqs.add(dsq);
4410               }
4411             }
4412             else
4413             {
4414               if (ds.findIndex(dsq) < 0)
4415               {
4416                 ds.addSequence(dsq);
4417               }
4418             }
4419           }
4420         }
4421       }
4422     }
4423     // TODO: refactor this as a merge dataset sequence function
4424     // now check that sq (the dataset sequence) sequence really is the union of
4425     // all references to it
4426     // boolean pre = sq.getStart() < dsq.getStart();
4427     // boolean post = sq.getEnd() > dsq.getEnd();
4428     // if (pre || post)
4429     if (sq != dsq)
4430     {
4431       // StringBuffer sb = new StringBuffer();
4432       String newres = jalview.analysis.AlignSeq.extractGaps(
4433               jalview.util.Comparison.GapChars, sq.getSequenceAsString());
4434       if (!newres.equalsIgnoreCase(dsq.getSequenceAsString())
4435               && newres.length() > dsq.getLength())
4436       {
4437         // Update with the longer sequence.
4438         synchronized (dsq)
4439         {
4440           /*
4441            * if (pre) { sb.insert(0, newres .substring(0, dsq.getStart() -
4442            * sq.getStart())); dsq.setStart(sq.getStart()); } if (post) {
4443            * sb.append(newres.substring(newres.length() - sq.getEnd() -
4444            * dsq.getEnd())); dsq.setEnd(sq.getEnd()); }
4445            */
4446           dsq.setSequence(newres);
4447         }
4448         // TODO: merges will never happen if we 'know' we have the real dataset
4449         // sequence - this should be detected when id==dssid
4450         System.err
4451                 .println("DEBUG Notice:  Merged dataset sequence (if you see this often, post at http://issues.jalview.org/browse/JAL-1474)"); // ("
4452         // + (pre ? "prepended" : "") + " "
4453         // + (post ? "appended" : ""));
4454       }
4455     }
4456   }
4457
4458   java.util.Hashtable datasetIds = null;
4459
4460   java.util.IdentityHashMap dataset2Ids = null;
4461
4462   private Alignment getDatasetFor(String datasetId)
4463   {
4464     if (datasetIds == null)
4465     {
4466       datasetIds = new Hashtable();
4467       return null;
4468     }
4469     if (datasetIds.containsKey(datasetId))
4470     {
4471       return (Alignment) datasetIds.get(datasetId);
4472     }
4473     return null;
4474   }
4475
4476   private void addDatasetRef(String datasetId, Alignment dataset)
4477   {
4478     if (datasetIds == null)
4479     {
4480       datasetIds = new Hashtable();
4481     }
4482     datasetIds.put(datasetId, dataset);
4483   }
4484
4485   /**
4486    * make a new dataset ID for this jalview dataset alignment
4487    * 
4488    * @param dataset
4489    * @return
4490    */
4491   private String getDatasetIdRef(jalview.datamodel.Alignment dataset)
4492   {
4493     if (dataset.getDataset() != null)
4494     {
4495       warn("Serious issue!  Dataset Object passed to getDatasetIdRef is not a Jalview DATASET alignment...");
4496     }
4497     String datasetId = makeHashCode(dataset, null);
4498     if (datasetId == null)
4499     {
4500       // make a new datasetId and record it
4501       if (dataset2Ids == null)
4502       {
4503         dataset2Ids = new IdentityHashMap();
4504       }
4505       else
4506       {
4507         datasetId = (String) dataset2Ids.get(dataset);
4508       }
4509       if (datasetId == null)
4510       {
4511         datasetId = "ds" + dataset2Ids.size() + 1;
4512         dataset2Ids.put(dataset, datasetId);
4513       }
4514     }
4515     return datasetId;
4516   }
4517
4518   private void addDBRefs(SequenceI datasetSequence, Sequence sequence)
4519   {
4520     for (int d = 0; d < sequence.getDBRefCount(); d++)
4521     {
4522       DBRef dr = sequence.getDBRef(d);
4523       jalview.datamodel.DBRefEntry entry = new jalview.datamodel.DBRefEntry(
4524               sequence.getDBRef(d).getSource(), sequence.getDBRef(d)
4525                       .getVersion(), sequence.getDBRef(d).getAccessionId());
4526       if (dr.getMapping() != null)
4527       {
4528         entry.setMap(addMapping(dr.getMapping()));
4529       }
4530       datasetSequence.addDBRef(entry);
4531     }
4532   }
4533
4534   private jalview.datamodel.Mapping addMapping(Mapping m)
4535   {
4536     SequenceI dsto = null;
4537     // Mapping m = dr.getMapping();
4538     int fr[] = new int[m.getMapListFromCount() * 2];
4539     Enumeration f = m.enumerateMapListFrom();
4540     for (int _i = 0; f.hasMoreElements(); _i += 2)
4541     {
4542       MapListFrom mf = (MapListFrom) f.nextElement();
4543       fr[_i] = mf.getStart();
4544       fr[_i + 1] = mf.getEnd();
4545     }
4546     int fto[] = new int[m.getMapListToCount() * 2];
4547     f = m.enumerateMapListTo();
4548     for (int _i = 0; f.hasMoreElements(); _i += 2)
4549     {
4550       MapListTo mf = (MapListTo) f.nextElement();
4551       fto[_i] = mf.getStart();
4552       fto[_i + 1] = mf.getEnd();
4553     }
4554     jalview.datamodel.Mapping jmap = new jalview.datamodel.Mapping(dsto,
4555             fr, fto, (int) m.getMapFromUnit(), (int) m.getMapToUnit());
4556     if (m.getMappingChoice() != null)
4557     {
4558       MappingChoice mc = m.getMappingChoice();
4559       if (mc.getDseqFor() != null)
4560       {
4561         String dsfor = "" + mc.getDseqFor();
4562         if (seqRefIds.containsKey(dsfor))
4563         {
4564           /**
4565            * recover from hash
4566            */
4567           jmap.setTo(seqRefIds.get(dsfor));
4568         }
4569         else
4570         {
4571           frefedSequence.add(new Object[]
4572           { dsfor, jmap });
4573         }
4574       }
4575       else
4576       {
4577         /**
4578          * local sequence definition
4579          */
4580         Sequence ms = mc.getSequence();
4581         SequenceI djs = null;
4582         String sqid = ms.getDsseqid();
4583         if (sqid != null && sqid.length() > 0)
4584         {
4585           /*
4586            * recover dataset sequence
4587            */
4588           djs = seqRefIds.get(sqid);
4589         }
4590         else
4591         {
4592           System.err
4593                   .println("Warning - making up dataset sequence id for DbRef sequence map reference");
4594           sqid = ((Object) ms).toString(); // make up a new hascode for
4595           // undefined dataset sequence hash
4596           // (unlikely to happen)
4597         }
4598
4599         if (djs == null)
4600         {
4601           /**
4602            * make a new dataset sequence and add it to refIds hash
4603            */
4604           djs = new jalview.datamodel.Sequence(ms.getName(),
4605                   ms.getSequence());
4606           djs.setStart(jmap.getMap().getToLowest());
4607           djs.setEnd(jmap.getMap().getToHighest());
4608           djs.setVamsasId(uniqueSetSuffix + sqid);
4609           jmap.setTo(djs);
4610           seqRefIds.put(sqid, djs);
4611
4612         }
4613         jalview.bin.Cache.log.debug("about to recurse on addDBRefs.");
4614         addDBRefs(djs, ms);
4615
4616       }
4617     }
4618     return (jmap);
4619
4620   }
4621
4622   public jalview.gui.AlignmentPanel copyAlignPanel(AlignmentPanel ap,
4623           boolean keepSeqRefs)
4624   {
4625     initSeqRefs();
4626     jalview.schemabinding.version2.JalviewModel jm = saveState(ap, null,
4627             null);
4628
4629     if (!keepSeqRefs)
4630     {
4631       clearSeqRefs();
4632       jm.getJalviewModelSequence().getViewport(0).setSequenceSetId(null);
4633     }
4634     else
4635     {
4636       uniqueSetSuffix = "";
4637       jm.getJalviewModelSequence().getViewport(0).setId(null); // we don't
4638       // overwrite the
4639       // view we just
4640       // copied
4641     }
4642     if (this.frefedSequence == null)
4643     {
4644       frefedSequence = new Vector();
4645     }
4646
4647     viewportsAdded.clear();
4648
4649     AlignFrame af = loadFromObject(jm, null, false, null);
4650     af.alignPanels.clear();
4651     af.closeMenuItem_actionPerformed(true);
4652
4653     /*
4654      * if(ap.av.getAlignment().getAlignmentAnnotation()!=null) { for(int i=0;
4655      * i<ap.av.getAlignment().getAlignmentAnnotation().length; i++) {
4656      * if(!ap.av.getAlignment().getAlignmentAnnotation()[i].autoCalculated) {
4657      * af.alignPanel.av.getAlignment().getAlignmentAnnotation()[i] =
4658      * ap.av.getAlignment().getAlignmentAnnotation()[i]; } } }
4659      */
4660
4661     return af.alignPanel;
4662   }
4663
4664   /**
4665    * flag indicating if hashtables should be cleared on finalization TODO this
4666    * flag may not be necessary
4667    */
4668   private final boolean _cleartables = true;
4669
4670   private Hashtable jvids2vobj;
4671
4672   /*
4673    * (non-Javadoc)
4674    * 
4675    * @see java.lang.Object#finalize()
4676    */
4677   @Override
4678   protected void finalize() throws Throwable
4679   {
4680     // really make sure we have no buried refs left.
4681     if (_cleartables)
4682     {
4683       clearSeqRefs();
4684     }
4685     this.seqRefIds = null;
4686     this.seqsToIds = null;
4687     super.finalize();
4688   }
4689
4690   private void warn(String msg)
4691   {
4692     warn(msg, null);
4693   }
4694
4695   private void warn(String msg, Exception e)
4696   {
4697     if (Cache.log != null)
4698     {
4699       if (e != null)
4700       {
4701         Cache.log.warn(msg, e);
4702       }
4703       else
4704       {
4705         Cache.log.warn(msg);
4706       }
4707     }
4708     else
4709     {
4710       System.err.println("Warning: " + msg);
4711       if (e != null)
4712       {
4713         e.printStackTrace();
4714       }
4715     }
4716   }
4717
4718   private void debug(String string)
4719   {
4720     debug(string, null);
4721   }
4722
4723   private void debug(String msg, Exception e)
4724   {
4725     if (Cache.log != null)
4726     {
4727       if (e != null)
4728       {
4729         Cache.log.debug(msg, e);
4730       }
4731       else
4732       {
4733         Cache.log.debug(msg);
4734       }
4735     }
4736     else
4737     {
4738       System.err.println("Warning: " + msg);
4739       if (e != null)
4740       {
4741         e.printStackTrace();
4742       }
4743     }
4744   }
4745
4746   /**
4747    * set the object to ID mapping tables used to write/recover objects and XML
4748    * ID strings for the jalview project. If external tables are provided then
4749    * finalize and clearSeqRefs will not clear the tables when the Jalview2XML
4750    * object goes out of scope. - also populates the datasetIds hashtable with
4751    * alignment objects containing dataset sequences
4752    * 
4753    * @param vobj2jv
4754    *          Map from ID strings to jalview datamodel
4755    * @param jv2vobj
4756    *          Map from jalview datamodel to ID strings
4757    * 
4758    * 
4759    */
4760   public void setObjectMappingTables(Hashtable vobj2jv,
4761           IdentityHashMap jv2vobj)
4762   {
4763     this.jv2vobj = jv2vobj;
4764     this.vobj2jv = vobj2jv;
4765     Iterator ds = jv2vobj.keySet().iterator();
4766     String id;
4767     while (ds.hasNext())
4768     {
4769       Object jvobj = ds.next();
4770       id = jv2vobj.get(jvobj).toString();
4771       if (jvobj instanceof jalview.datamodel.Alignment)
4772       {
4773         if (((jalview.datamodel.Alignment) jvobj).getDataset() == null)
4774         {
4775           addDatasetRef(id, (jalview.datamodel.Alignment) jvobj);
4776         }
4777       }
4778       else if (jvobj instanceof jalview.datamodel.Sequence)
4779       {
4780         // register sequence object so the XML parser can recover it.
4781         if (seqRefIds == null)
4782         {
4783           seqRefIds = new HashMap<String, SequenceI>();
4784         }
4785         if (seqsToIds == null)
4786         {
4787           seqsToIds = new IdentityHashMap<SequenceI, String>();
4788         }
4789         seqRefIds.put(jv2vobj.get(jvobj).toString(), (SequenceI) jvobj);
4790         seqsToIds.put((SequenceI) jvobj, id);
4791       }
4792       else if (jvobj instanceof jalview.datamodel.AlignmentAnnotation)
4793       {
4794         String anid;
4795         AlignmentAnnotation jvann = (AlignmentAnnotation) jvobj;
4796         annotationIds.put(anid = jv2vobj.get(jvobj).toString(), jvann);
4797         if (jvann.annotationId == null)
4798         {
4799           jvann.annotationId = anid;
4800         }
4801         if (!jvann.annotationId.equals(anid))
4802         {
4803           // TODO verify that this is the correct behaviour
4804           this.warn("Overriding Annotation ID for " + anid
4805                   + " from different id : " + jvann.annotationId);
4806           jvann.annotationId = anid;
4807         }
4808       }
4809       else if (jvobj instanceof String)
4810       {
4811         if (jvids2vobj == null)
4812         {
4813           jvids2vobj = new Hashtable();
4814           jvids2vobj.put(jvobj, jv2vobj.get(jvobj).toString());
4815         }
4816       }
4817       else
4818       {
4819         Cache.log.debug("Ignoring " + jvobj.getClass() + " (ID = " + id);
4820       }
4821     }
4822   }
4823
4824   /**
4825    * set the uniqueSetSuffix used to prefix/suffix object IDs for jalview
4826    * objects created from the project archive. If string is null (default for
4827    * construction) then suffix will be set automatically.
4828    * 
4829    * @param string
4830    */
4831   public void setUniqueSetSuffix(String string)
4832   {
4833     uniqueSetSuffix = string;
4834
4835   }
4836
4837   /**
4838    * uses skipList2 as the skipList for skipping views on sequence sets
4839    * associated with keys in the skipList
4840    * 
4841    * @param skipList2
4842    */
4843   public void setSkipList(Hashtable skipList2)
4844   {
4845     skipList = skipList2;
4846   }
4847 }