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