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