JAL-1759 formatting

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