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