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