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