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