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