Merge branch 'bug/JAL-2154projectMappings' into develop
[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 (_af != null
2381                     && object.getJalviewModelSequence().getViewportCount() > 0)
2382             {
2383               if (af == null)
2384               {
2385                 // store a reference to the first view
2386                 af = _af;
2387               }
2388               if (_af.viewport.isGatherViewsHere())
2389               {
2390                 // if this is a gathered view, keep its reference since
2391                 // after gathering views, only this frame will remain
2392                 af = _af;
2393                 gatherToThisFrame.put(_af.viewport.getSequenceSetId(), _af);
2394               }
2395               // Save dataset to register mappings once all resolved
2396               importedDatasets.put(af.viewport.getAlignment().getDataset(),
2397                       af.viewport.getAlignment().getDataset());
2398             }
2399           }
2400           entryCount++;
2401         }
2402         else if (jarentry != null)
2403         {
2404           // Some other file here.
2405           entryCount++;
2406         }
2407       } while (jarentry != null);
2408       resolveFrefedSequences();
2409     } catch (IOException ex)
2410     {
2411       ex.printStackTrace();
2412       errorMessage = "Couldn't locate Jalview XML file : " + file;
2413       System.err.println("Exception whilst loading jalview XML file : "
2414               + ex + "\n");
2415     } catch (Exception ex)
2416     {
2417       System.err.println("Parsing as Jalview Version 2 file failed.");
2418       ex.printStackTrace(System.err);
2419       if (attemptversion1parse)
2420       {
2421         // Is Version 1 Jar file?
2422         try
2423         {
2424           af = new Jalview2XML_V1(raiseGUI).LoadJalviewAlign(jprovider);
2425         } catch (Exception ex2)
2426         {
2427           System.err.println("Exception whilst loading as jalviewXMLV1:");
2428           ex2.printStackTrace();
2429           af = null;
2430         }
2431       }
2432       if (Desktop.instance != null)
2433       {
2434         Desktop.instance.stopLoading();
2435       }
2436       if (af != null)
2437       {
2438         System.out.println("Successfully loaded archive file");
2439         return af;
2440       }
2441       ex.printStackTrace();
2442
2443       System.err.println("Exception whilst loading jalview XML file : "
2444               + ex + "\n");
2445     } catch (OutOfMemoryError e)
2446     {
2447       // Don't use the OOM Window here
2448       errorMessage = "Out of memory loading jalview XML file";
2449       System.err.println("Out of memory whilst loading jalview XML file");
2450       e.printStackTrace();
2451     }
2452
2453     /*
2454      * Regather multiple views (with the same sequence set id) to the frame (if
2455      * any) that is flagged as the one to gather to, i.e. convert them to tabbed
2456      * views instead of separate frames. Note this doesn't restore a state where
2457      * some expanded views in turn have tabbed views - the last "first tab" read
2458      * in will play the role of gatherer for all.
2459      */
2460     for (AlignFrame fr : gatherToThisFrame.values())
2461     {
2462       Desktop.instance.gatherViews(fr);
2463     }
2464
2465     restoreSplitFrames();
2466     for (AlignmentI ds : importedDatasets.keySet())
2467     {
2468       if (ds.getCodonFrames() != null)
2469       {
2470         StructureSelectionManager.getStructureSelectionManager(
2471                 Desktop.instance).registerMappings(ds.getCodonFrames());
2472       }
2473     }
2474     if (errorMessage != null)
2475     {
2476       reportErrors();
2477     }
2478
2479     if (Desktop.instance != null)
2480     {
2481       Desktop.instance.stopLoading();
2482     }
2483
2484     return af;
2485   }
2486
2487   /**
2488    * Try to reconstruct and display SplitFrame windows, where each contains
2489    * complementary dna and protein alignments. Done by pairing up AlignFrame
2490    * objects (created earlier) which have complementary viewport ids associated.
2491    */
2492   protected void restoreSplitFrames()
2493   {
2494     List<SplitFrame> gatherTo = new ArrayList<SplitFrame>();
2495     List<AlignFrame> addedToSplitFrames = new ArrayList<AlignFrame>();
2496     Map<String, AlignFrame> dna = new HashMap<String, AlignFrame>();
2497
2498     /*
2499      * Identify the DNA alignments
2500      */
2501     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
2502             .entrySet())
2503     {
2504       AlignFrame af = candidate.getValue();
2505       if (af.getViewport().getAlignment().isNucleotide())
2506       {
2507         dna.put(candidate.getKey().getId(), af);
2508       }
2509     }
2510
2511     /*
2512      * Try to match up the protein complements
2513      */
2514     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
2515             .entrySet())
2516     {
2517       AlignFrame af = candidate.getValue();
2518       if (!af.getViewport().getAlignment().isNucleotide())
2519       {
2520         String complementId = candidate.getKey().getComplementId();
2521         // only non-null complements should be in the Map
2522         if (complementId != null && dna.containsKey(complementId))
2523         {
2524           final AlignFrame dnaFrame = dna.get(complementId);
2525           SplitFrame sf = createSplitFrame(dnaFrame, af);
2526           addedToSplitFrames.add(dnaFrame);
2527           addedToSplitFrames.add(af);
2528           if (af.viewport.isGatherViewsHere())
2529           {
2530             gatherTo.add(sf);
2531           }
2532         }
2533       }
2534     }
2535
2536     /*
2537      * Open any that we failed to pair up (which shouldn't happen!) as
2538      * standalone AlignFrame's.
2539      */
2540     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
2541             .entrySet())
2542     {
2543       AlignFrame af = candidate.getValue();
2544       if (!addedToSplitFrames.contains(af))
2545       {
2546         Viewport view = candidate.getKey();
2547         Desktop.addInternalFrame(af, view.getTitle(), view.getWidth(),
2548                 view.getHeight());
2549         System.err.println("Failed to restore view " + view.getTitle()
2550                 + " to split frame");
2551       }
2552     }
2553
2554     /*
2555      * Gather back into tabbed views as flagged.
2556      */
2557     for (SplitFrame sf : gatherTo)
2558     {
2559       Desktop.instance.gatherViews(sf);
2560     }
2561
2562     splitFrameCandidates.clear();
2563   }
2564
2565   /**
2566    * Construct and display one SplitFrame holding DNA and protein alignments.
2567    * 
2568    * @param dnaFrame
2569    * @param proteinFrame
2570    * @return
2571    */
2572   protected SplitFrame createSplitFrame(AlignFrame dnaFrame,
2573           AlignFrame proteinFrame)
2574   {
2575     SplitFrame splitFrame = new SplitFrame(dnaFrame, proteinFrame);
2576     String title = MessageManager.getString("label.linked_view_title");
2577     int width = (int) dnaFrame.getBounds().getWidth();
2578     int height = (int) (dnaFrame.getBounds().getHeight()
2579             + proteinFrame.getBounds().getHeight() + 50);
2580
2581     /*
2582      * SplitFrame location is saved to both enclosed frames
2583      */
2584     splitFrame.setLocation(dnaFrame.getX(), dnaFrame.getY());
2585     Desktop.addInternalFrame(splitFrame, title, width, height);
2586
2587     /*
2588      * And compute cDNA consensus (couldn't do earlier with consensus as
2589      * mappings were not yet present)
2590      */
2591     proteinFrame.viewport.alignmentChanged(proteinFrame.alignPanel);
2592
2593     return splitFrame;
2594   }
2595
2596   /**
2597    * check errorMessage for a valid error message and raise an error box in the
2598    * GUI or write the current errorMessage to stderr and then clear the error
2599    * state.
2600    */
2601   protected void reportErrors()
2602   {
2603     reportErrors(false);
2604   }
2605
2606   protected void reportErrors(final boolean saving)
2607   {
2608     if (errorMessage != null)
2609     {
2610       final String finalErrorMessage = errorMessage;
2611       if (raiseGUI)
2612       {
2613         javax.swing.SwingUtilities.invokeLater(new Runnable()
2614         {
2615           @Override
2616           public void run()
2617           {
2618             JOptionPane.showInternalMessageDialog(Desktop.desktop,
2619                     finalErrorMessage, "Error "
2620                             + (saving ? "saving" : "loading")
2621                             + " Jalview file", JOptionPane.WARNING_MESSAGE);
2622           }
2623         });
2624       }
2625       else
2626       {
2627         System.err.println("Problem loading Jalview file: " + errorMessage);
2628       }
2629     }
2630     errorMessage = null;
2631   }
2632
2633   Map<String, String> alreadyLoadedPDB = new HashMap<String, String>();
2634
2635   /**
2636    * when set, local views will be updated from view stored in JalviewXML
2637    * Currently (28th Sep 2008) things will go horribly wrong in vamsas document
2638    * sync if this is set to true.
2639    */
2640   private final boolean updateLocalViews = false;
2641
2642   /**
2643    * Returns the path to a temporary file holding the PDB file for the given PDB
2644    * id. The first time of asking, searches for a file of that name in the
2645    * Jalview project jar, and copies it to a new temporary file. Any repeat
2646    * requests just return the path to the file previously created.
2647    * 
2648    * @param jprovider
2649    * @param pdbId
2650    * @return
2651    */
2652   String loadPDBFile(jarInputStreamProvider jprovider, String pdbId)
2653   {
2654     if (alreadyLoadedPDB.containsKey(pdbId))
2655     {
2656       return alreadyLoadedPDB.get(pdbId).toString();
2657     }
2658
2659     String tempFile = copyJarEntry(jprovider, pdbId, "jalview_pdb");
2660     if (tempFile != null)
2661     {
2662       alreadyLoadedPDB.put(pdbId, tempFile);
2663     }
2664     return tempFile;
2665   }
2666
2667   /**
2668    * Copies the jar entry of given name to a new temporary file and returns the
2669    * path to the file, or null if the entry is not found.
2670    * 
2671    * @param jprovider
2672    * @param jarEntryName
2673    * @param prefix
2674    *          a prefix for the temporary file name, must be at least three
2675    *          characters long
2676    * @return
2677    */
2678   protected String copyJarEntry(jarInputStreamProvider jprovider,
2679           String jarEntryName, String prefix)
2680   {
2681     BufferedReader in = null;
2682     PrintWriter out = null;
2683
2684     try
2685     {
2686       JarInputStream jin = jprovider.getJarInputStream();
2687       /*
2688        * if (jprovider.startsWith("http://")) { jin = new JarInputStream(new
2689        * URL(jprovider).openStream()); } else { jin = new JarInputStream(new
2690        * FileInputStream(jprovider)); }
2691        */
2692
2693       JarEntry entry = null;
2694       do
2695       {
2696         entry = jin.getNextJarEntry();
2697       } while (entry != null && !entry.getName().equals(jarEntryName));
2698       if (entry != null)
2699       {
2700         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
2701         File outFile = File.createTempFile(prefix, ".tmp");
2702         outFile.deleteOnExit();
2703         out = new PrintWriter(new FileOutputStream(outFile));
2704         String data;
2705
2706         while ((data = in.readLine()) != null)
2707         {
2708           out.println(data);
2709         }
2710         out.flush();
2711         String t = outFile.getAbsolutePath();
2712         return t;
2713       }
2714       else
2715       {
2716         warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
2717       }
2718     } catch (Exception ex)
2719     {
2720       ex.printStackTrace();
2721     } finally
2722     {
2723       if (in != null)
2724       {
2725         try
2726         {
2727           in.close();
2728         } catch (IOException e)
2729         {
2730           // ignore
2731         }
2732       }
2733       if (out != null)
2734       {
2735         out.close();
2736       }
2737     }
2738
2739     return null;
2740   }
2741
2742   private class JvAnnotRow
2743   {
2744     public JvAnnotRow(int i, AlignmentAnnotation jaa)
2745     {
2746       order = i;
2747       template = jaa;
2748     }
2749
2750     /**
2751      * persisted version of annotation row from which to take vis properties
2752      */
2753     public jalview.datamodel.AlignmentAnnotation template;
2754
2755     /**
2756      * original position of the annotation row in the alignment
2757      */
2758     public int order;
2759   }
2760
2761   /**
2762    * Load alignment frame from jalview XML DOM object
2763    * 
2764    * @param object
2765    *          DOM
2766    * @param file
2767    *          filename source string
2768    * @param loadTreesAndStructures
2769    *          when false only create Viewport
2770    * @param jprovider
2771    *          data source provider
2772    * @return alignment frame created from view stored in DOM
2773    */
2774   AlignFrame loadFromObject(JalviewModel object, String file,
2775           boolean loadTreesAndStructures, jarInputStreamProvider jprovider)
2776   {
2777     SequenceSet vamsasSet = object.getVamsasModel().getSequenceSet(0);
2778     Sequence[] vamsasSeq = vamsasSet.getSequence();
2779
2780     JalviewModelSequence jms = object.getJalviewModelSequence();
2781
2782     Viewport view = (jms.getViewportCount() > 0) ? jms.getViewport(0)
2783             : null;
2784
2785     // ////////////////////////////////
2786     // LOAD SEQUENCES
2787
2788     List<SequenceI> hiddenSeqs = null;
2789
2790
2791     List<SequenceI> tmpseqs = new ArrayList<SequenceI>();
2792
2793     boolean multipleView = false;
2794     SequenceI referenceseqForView = null;
2795     JSeq[] jseqs = object.getJalviewModelSequence().getJSeq();
2796     int vi = 0; // counter in vamsasSeq array
2797     for (int i = 0; i < jseqs.length; i++)
2798     {
2799       String seqId = jseqs[i].getId();
2800
2801       SequenceI tmpSeq = seqRefIds.get(seqId);
2802       if (tmpSeq != null)
2803       {
2804         if (!incompleteSeqs.containsKey(seqId))
2805         {
2806           // may not need this check, but keep it for at least 2.9,1 release
2807           if (tmpSeq.getStart()!=jseqs[i].getStart() || tmpSeq.getEnd()!=jseqs[i].getEnd())
2808           { 
2809             System.err
2810                     .println("Warning JAL-2154 regression: updating start/end for sequence "
2811                     + tmpSeq.toString());
2812           }
2813         } else {
2814           incompleteSeqs.remove(seqId);
2815         }
2816         tmpSeq.setStart(jseqs[i].getStart());
2817         tmpSeq.setEnd(jseqs[i].getEnd());
2818         tmpseqs.add(tmpSeq);
2819         multipleView = true;
2820       }
2821       else
2822       {
2823         tmpSeq = new jalview.datamodel.Sequence(vamsasSeq[vi].getName(),
2824                 vamsasSeq[vi].getSequence());
2825         tmpSeq.setDescription(vamsasSeq[vi].getDescription());
2826         tmpSeq.setStart(jseqs[i].getStart());
2827         tmpSeq.setEnd(jseqs[i].getEnd());
2828         tmpSeq.setVamsasId(uniqueSetSuffix + seqId);
2829         seqRefIds.put(vamsasSeq[vi].getId(), tmpSeq);
2830         tmpseqs.add(tmpSeq);
2831         vi++;
2832       }
2833
2834       if (jseqs[i].hasViewreference() && jseqs[i].getViewreference())
2835       {
2836         referenceseqForView = tmpseqs.get(tmpseqs.size() - 1);
2837       }
2838
2839       if (jseqs[i].getHidden())
2840       {
2841         if (hiddenSeqs == null)
2842         {
2843           hiddenSeqs = new ArrayList<SequenceI>();
2844         }
2845
2846         hiddenSeqs.add(tmpSeq);
2847       }
2848     }
2849
2850     // /
2851     // Create the alignment object from the sequence set
2852     // ///////////////////////////////
2853     SequenceI[] orderedSeqs = tmpseqs
2854             .toArray(new SequenceI[tmpseqs.size()]);
2855
2856     AlignmentI al = null;
2857     // so we must create or recover the dataset alignment before going further
2858     // ///////////////////////////////
2859     if (vamsasSet.getDatasetId() == null || vamsasSet.getDatasetId() == "")
2860     {
2861       // older jalview projects do not have a dataset - so creat alignment and
2862       // dataset
2863       al = new Alignment(orderedSeqs);
2864       al.setDataset(null);
2865     }
2866     else
2867     {
2868       boolean isdsal = object.getJalviewModelSequence().getViewportCount() == 0;
2869       if (isdsal)
2870       {
2871         // we are importing a dataset record, so
2872         // recover reference to an alignment already materialsed as dataset
2873         al = getDatasetFor(vamsasSet.getDatasetId());
2874       }
2875       if (al == null)
2876       {
2877         // materialse the alignment
2878         al = new Alignment(orderedSeqs);
2879       }
2880       if (isdsal)
2881       {
2882         addDatasetRef(vamsasSet.getDatasetId(), al);
2883       }
2884
2885       // finally, verify all data in vamsasSet is actually present in al
2886       // passing on flag indicating if it is actually a stored dataset
2887       recoverDatasetFor(vamsasSet, al, isdsal);
2888     }
2889
2890     if (referenceseqForView != null)
2891     {
2892       al.setSeqrep(referenceseqForView);
2893     }
2894     // / Add the alignment properties
2895     for (int i = 0; i < vamsasSet.getSequenceSetPropertiesCount(); i++)
2896     {
2897       SequenceSetProperties ssp = vamsasSet.getSequenceSetProperties(i);
2898       al.setProperty(ssp.getKey(), ssp.getValue());
2899     }
2900
2901     // ///////////////////////////////
2902
2903     Hashtable pdbloaded = new Hashtable(); // TODO nothing writes to this??
2904     if (!multipleView)
2905     {
2906       // load sequence features, database references and any associated PDB
2907       // structures for the alignment
2908       for (int i = 0; i < vamsasSeq.length; i++)
2909       {
2910         if (jseqs[i].getFeaturesCount() > 0)
2911         {
2912           Features[] features = jseqs[i].getFeatures();
2913           for (int f = 0; f < features.length; f++)
2914           {
2915             jalview.datamodel.SequenceFeature sf = new jalview.datamodel.SequenceFeature(
2916                     features[f].getType(), features[f].getDescription(),
2917                     features[f].getStatus(), features[f].getBegin(),
2918                     features[f].getEnd(), features[f].getFeatureGroup());
2919
2920             sf.setScore(features[f].getScore());
2921             for (int od = 0; od < features[f].getOtherDataCount(); od++)
2922             {
2923               OtherData keyValue = features[f].getOtherData(od);
2924               if (keyValue.getKey().startsWith("LINK"))
2925               {
2926                 sf.addLink(keyValue.getValue());
2927               }
2928               else
2929               {
2930                 sf.setValue(keyValue.getKey(), keyValue.getValue());
2931               }
2932
2933             }
2934
2935             al.getSequenceAt(i).getDatasetSequence().addSequenceFeature(sf);
2936           }
2937         }
2938         if (vamsasSeq[i].getDBRefCount() > 0)
2939         {
2940           addDBRefs(al.getSequenceAt(i).getDatasetSequence(), vamsasSeq[i]);
2941         }
2942         if (jseqs[i].getPdbidsCount() > 0)
2943         {
2944           Pdbids[] ids = jseqs[i].getPdbids();
2945           for (int p = 0; p < ids.length; p++)
2946           {
2947             jalview.datamodel.PDBEntry entry = new jalview.datamodel.PDBEntry();
2948             entry.setId(ids[p].getId());
2949             if (ids[p].getType() != null)
2950             {
2951               if (ids[p].getType().equalsIgnoreCase("PDB"))
2952               {
2953                 entry.setType(PDBEntry.Type.PDB);
2954               }
2955               else
2956               {
2957                 entry.setType(PDBEntry.Type.FILE);
2958               }
2959             }
2960             if (ids[p].getFile() != null)
2961             {
2962               if (!pdbloaded.containsKey(ids[p].getFile()))
2963               {
2964                 entry.setFile(loadPDBFile(jprovider, ids[p].getId()));
2965               }
2966               else
2967               {
2968                 entry.setFile(pdbloaded.get(ids[p].getId()).toString());
2969               }
2970             }
2971             StructureSelectionManager.getStructureSelectionManager(
2972                     Desktop.instance).registerPDBEntry(entry);
2973             al.getSequenceAt(i).getDatasetSequence().addPDBId(entry);
2974           }
2975         }
2976       }
2977     } // end !multipleview
2978
2979     // ///////////////////////////////
2980     // LOAD SEQUENCE MAPPINGS
2981
2982     if (vamsasSet.getAlcodonFrameCount() > 0)
2983     {
2984       // TODO Potentially this should only be done once for all views of an
2985       // alignment
2986       AlcodonFrame[] alc = vamsasSet.getAlcodonFrame();
2987       for (int i = 0; i < alc.length; i++)
2988       {
2989         AlignedCodonFrame cf = new AlignedCodonFrame();
2990         if (alc[i].getAlcodMapCount() > 0)
2991         {
2992           AlcodMap[] maps = alc[i].getAlcodMap();
2993           for (int m = 0; m < maps.length; m++)
2994           {
2995             SequenceI dnaseq = seqRefIds.get(maps[m].getDnasq());
2996             // Load Mapping
2997             jalview.datamodel.Mapping mapping = null;
2998             // attach to dna sequence reference.
2999             if (maps[m].getMapping() != null)
3000             {
3001               mapping = addMapping(maps[m].getMapping());
3002             }
3003             if (dnaseq != null && mapping.getTo() != null)
3004             {
3005               cf.addMap(dnaseq, mapping.getTo(), mapping.getMap());
3006             }
3007             else
3008             {
3009               // defer to later
3010               frefedSequence.add(newAlcodMapRef(maps[m].getDnasq(), cf,
3011                       mapping));
3012             }
3013           }
3014           al.addCodonFrame(cf);
3015         }
3016       }
3017     }
3018
3019     // ////////////////////////////////
3020     // LOAD ANNOTATIONS
3021     List<JvAnnotRow> autoAlan = new ArrayList<JvAnnotRow>();
3022
3023     /*
3024      * store any annotations which forward reference a group's ID
3025      */
3026     Map<String, List<AlignmentAnnotation>> groupAnnotRefs = new Hashtable<String, List<AlignmentAnnotation>>();
3027
3028     if (vamsasSet.getAnnotationCount() > 0)
3029     {
3030       Annotation[] an = vamsasSet.getAnnotation();
3031
3032       for (int i = 0; i < an.length; i++)
3033       {
3034         Annotation annotation = an[i];
3035
3036         /**
3037          * test if annotation is automatically calculated for this view only
3038          */
3039         boolean autoForView = false;
3040         if (annotation.getLabel().equals("Quality")
3041                 || annotation.getLabel().equals("Conservation")
3042                 || annotation.getLabel().equals("Consensus"))
3043         {
3044           // Kludge for pre 2.5 projects which lacked the autocalculated flag
3045           autoForView = true;
3046           if (!annotation.hasAutoCalculated())
3047           {
3048             annotation.setAutoCalculated(true);
3049           }
3050         }
3051         if (autoForView
3052                 || (annotation.hasAutoCalculated() && annotation
3053                         .isAutoCalculated()))
3054         {
3055           // remove ID - we don't recover annotation from other views for
3056           // view-specific annotation
3057           annotation.setId(null);
3058         }
3059
3060         // set visiblity for other annotation in this view
3061         String annotationId = annotation.getId();
3062         if (annotationId != null && annotationIds.containsKey(annotationId))
3063         {
3064           AlignmentAnnotation jda = annotationIds.get(annotationId);
3065           // in principle Visible should always be true for annotation displayed
3066           // in multiple views
3067           if (annotation.hasVisible())
3068           {
3069             jda.visible = annotation.getVisible();
3070           }
3071
3072           al.addAnnotation(jda);
3073
3074           continue;
3075         }
3076         // Construct new annotation from model.
3077         AnnotationElement[] ae = annotation.getAnnotationElement();
3078         jalview.datamodel.Annotation[] anot = null;
3079         java.awt.Color firstColour = null;
3080         int anpos;
3081         if (!annotation.getScoreOnly())
3082         {
3083           anot = new jalview.datamodel.Annotation[al.getWidth()];
3084           for (int aa = 0; aa < ae.length && aa < anot.length; aa++)
3085           {
3086             anpos = ae[aa].getPosition();
3087
3088             if (anpos >= anot.length)
3089             {
3090               continue;
3091             }
3092
3093             anot[anpos] = new jalview.datamodel.Annotation(
3094
3095             ae[aa].getDisplayCharacter(), ae[aa].getDescription(),
3096                     (ae[aa].getSecondaryStructure() == null || ae[aa]
3097                             .getSecondaryStructure().length() == 0) ? ' '
3098                             : ae[aa].getSecondaryStructure().charAt(0),
3099                     ae[aa].getValue()
3100
3101             );
3102             // JBPNote: Consider verifying dataflow for IO of secondary
3103             // structure annotation read from Stockholm files
3104             // this was added to try to ensure that
3105             // if (anot[ae[aa].getPosition()].secondaryStructure>' ')
3106             // {
3107             // anot[ae[aa].getPosition()].displayCharacter = "";
3108             // }
3109             anot[anpos].colour = new java.awt.Color(ae[aa].getColour());
3110             if (firstColour == null)
3111             {
3112               firstColour = anot[anpos].colour;
3113             }
3114           }
3115         }
3116         jalview.datamodel.AlignmentAnnotation jaa = null;
3117
3118         if (annotation.getGraph())
3119         {
3120           float llim = 0, hlim = 0;
3121           // if (autoForView || an[i].isAutoCalculated()) {
3122           // hlim=11f;
3123           // }
3124           jaa = new jalview.datamodel.AlignmentAnnotation(
3125                   annotation.getLabel(), annotation.getDescription(), anot,
3126                   llim, hlim, annotation.getGraphType());
3127
3128           jaa.graphGroup = annotation.getGraphGroup();
3129           jaa._linecolour = firstColour;
3130           if (annotation.getThresholdLine() != null)
3131           {
3132             jaa.setThreshold(new jalview.datamodel.GraphLine(annotation
3133                     .getThresholdLine().getValue(), annotation
3134                     .getThresholdLine().getLabel(), new java.awt.Color(
3135                     annotation.getThresholdLine().getColour())));
3136
3137           }
3138           if (autoForView || annotation.isAutoCalculated())
3139           {
3140             // Hardwire the symbol display line to ensure that labels for
3141             // histograms are displayed
3142             jaa.hasText = true;
3143           }
3144         }
3145         else
3146         {
3147           jaa = new jalview.datamodel.AlignmentAnnotation(an[i].getLabel(),
3148                   an[i].getDescription(), anot);
3149           jaa._linecolour = firstColour;
3150         }
3151         // register new annotation
3152         if (an[i].getId() != null)
3153         {
3154           annotationIds.put(an[i].getId(), jaa);
3155           jaa.annotationId = an[i].getId();
3156         }
3157         // recover sequence association
3158         String sequenceRef = an[i].getSequenceRef();
3159         if (sequenceRef != null)
3160         {
3161           // from 2.9 sequenceRef is to sequence id (JAL-1781)
3162           SequenceI sequence = seqRefIds.get(sequenceRef);
3163           if (sequence == null)
3164           {
3165             // in pre-2.9 projects sequence ref is to sequence name
3166             sequence = al.findName(sequenceRef);
3167           }
3168           if (sequence != null)
3169           {
3170             jaa.createSequenceMapping(sequence, 1, true);
3171             sequence.addAlignmentAnnotation(jaa);
3172           }
3173         }
3174         // and make a note of any group association
3175         if (an[i].getGroupRef() != null && an[i].getGroupRef().length() > 0)
3176         {
3177           List<jalview.datamodel.AlignmentAnnotation> aal = groupAnnotRefs
3178                   .get(an[i].getGroupRef());
3179           if (aal == null)
3180           {
3181             aal = new ArrayList<jalview.datamodel.AlignmentAnnotation>();
3182             groupAnnotRefs.put(an[i].getGroupRef(), aal);
3183           }
3184           aal.add(jaa);
3185         }
3186
3187         if (an[i].hasScore())
3188         {
3189           jaa.setScore(an[i].getScore());
3190         }
3191         if (an[i].hasVisible())
3192         {
3193           jaa.visible = an[i].getVisible();
3194         }
3195
3196         if (an[i].hasCentreColLabels())
3197         {
3198           jaa.centreColLabels = an[i].getCentreColLabels();
3199         }
3200
3201         if (an[i].hasScaleColLabels())
3202         {
3203           jaa.scaleColLabel = an[i].getScaleColLabels();
3204         }
3205         if (an[i].hasAutoCalculated() && an[i].isAutoCalculated())
3206         {
3207           // newer files have an 'autoCalculated' flag and store calculation
3208           // state in viewport properties
3209           jaa.autoCalculated = true; // means annotation will be marked for
3210           // update at end of load.
3211         }
3212         if (an[i].hasGraphHeight())
3213         {
3214           jaa.graphHeight = an[i].getGraphHeight();
3215         }
3216         if (an[i].hasBelowAlignment())
3217         {
3218           jaa.belowAlignment = an[i].isBelowAlignment();
3219         }
3220         jaa.setCalcId(an[i].getCalcId());
3221         if (an[i].getPropertyCount() > 0)
3222         {
3223           for (jalview.schemabinding.version2.Property prop : an[i]
3224                   .getProperty())
3225           {
3226             jaa.setProperty(prop.getName(), prop.getValue());
3227           }
3228         }
3229         if (jaa.autoCalculated)
3230         {
3231           autoAlan.add(new JvAnnotRow(i, jaa));
3232         }
3233         else
3234         // if (!autoForView)
3235         {
3236           // add autocalculated group annotation and any user created annotation
3237           // for the view
3238           al.addAnnotation(jaa);
3239         }
3240       }
3241     }
3242     // ///////////////////////
3243     // LOAD GROUPS
3244     // Create alignment markup and styles for this view
3245     if (jms.getJGroupCount() > 0)
3246     {
3247       JGroup[] groups = jms.getJGroup();
3248       boolean addAnnotSchemeGroup = false;
3249       for (int i = 0; i < groups.length; i++)
3250       {
3251         JGroup jGroup = groups[i];
3252         ColourSchemeI cs = null;
3253         if (jGroup.getColour() != null)
3254         {
3255           if (jGroup.getColour().startsWith("ucs"))
3256           {
3257             cs = getUserColourScheme(jms, jGroup.getColour());
3258           }
3259           else if (jGroup.getColour().equals("AnnotationColourGradient")
3260                   && jGroup.getAnnotationColours() != null)
3261           {
3262             addAnnotSchemeGroup = true;
3263             cs = null;
3264           }
3265           else
3266           {
3267             cs = ColourSchemeProperty.getColour(al, jGroup.getColour());
3268           }
3269
3270           if (cs != null)
3271           {
3272             cs.setThreshold(jGroup.getPidThreshold(), true);
3273           }
3274         }
3275
3276         Vector<SequenceI> seqs = new Vector<SequenceI>();
3277
3278         for (int s = 0; s < jGroup.getSeqCount(); s++)
3279         {
3280           String seqId = jGroup.getSeq(s) + "";
3281           SequenceI ts = seqRefIds.get(seqId);
3282
3283           if (ts != null)
3284           {
3285             seqs.addElement(ts);
3286           }
3287         }
3288
3289         if (seqs.size() < 1)
3290         {
3291           continue;
3292         }
3293
3294         SequenceGroup sg = new SequenceGroup(seqs, jGroup.getName(), cs,
3295                 jGroup.getDisplayBoxes(), jGroup.getDisplayText(),
3296                 jGroup.getColourText(), jGroup.getStart(), jGroup.getEnd());
3297
3298         sg.setOutlineColour(new java.awt.Color(jGroup.getOutlineColour()));
3299
3300         sg.textColour = new java.awt.Color(jGroup.getTextCol1());
3301         sg.textColour2 = new java.awt.Color(jGroup.getTextCol2());
3302         sg.setShowNonconserved(jGroup.hasShowUnconserved() ? jGroup
3303                 .isShowUnconserved() : false);
3304         sg.thresholdTextColour = jGroup.getTextColThreshold();
3305         if (jGroup.hasShowConsensusHistogram())
3306         {
3307           sg.setShowConsensusHistogram(jGroup.isShowConsensusHistogram());
3308         }
3309         ;
3310         if (jGroup.hasShowSequenceLogo())
3311         {
3312           sg.setshowSequenceLogo(jGroup.isShowSequenceLogo());
3313         }
3314         if (jGroup.hasNormaliseSequenceLogo())
3315         {
3316           sg.setNormaliseSequenceLogo(jGroup.isNormaliseSequenceLogo());
3317         }
3318         if (jGroup.hasIgnoreGapsinConsensus())
3319         {
3320           sg.setIgnoreGapsConsensus(jGroup.getIgnoreGapsinConsensus());
3321         }
3322         if (jGroup.getConsThreshold() != 0)
3323         {
3324           jalview.analysis.Conservation c = new jalview.analysis.Conservation(
3325                   "All", ResidueProperties.propHash, 3,
3326                   sg.getSequences(null), 0, sg.getWidth() - 1);
3327           c.calculate();
3328           c.verdict(false, 25);
3329           sg.cs.setConservation(c);
3330         }
3331
3332         if (jGroup.getId() != null && groupAnnotRefs.size() > 0)
3333         {
3334           // re-instate unique group/annotation row reference
3335           List<AlignmentAnnotation> jaal = groupAnnotRefs.get(jGroup
3336                   .getId());
3337           if (jaal != null)
3338           {
3339             for (AlignmentAnnotation jaa : jaal)
3340             {
3341               jaa.groupRef = sg;
3342               if (jaa.autoCalculated)
3343               {
3344                 // match up and try to set group autocalc alignment row for this
3345                 // annotation
3346                 if (jaa.label.startsWith("Consensus for "))
3347                 {
3348                   sg.setConsensus(jaa);
3349                 }
3350                 // match up and try to set group autocalc alignment row for this
3351                 // annotation
3352                 if (jaa.label.startsWith("Conservation for "))
3353                 {
3354                   sg.setConservationRow(jaa);
3355                 }
3356               }
3357             }
3358           }
3359         }
3360         al.addGroup(sg);
3361         if (addAnnotSchemeGroup)
3362         {
3363           // reconstruct the annotation colourscheme
3364           sg.cs = constructAnnotationColour(jGroup.getAnnotationColours(),
3365                   null, al, jms, false);
3366         }
3367       }
3368     }
3369     if (view == null)
3370     {
3371       // only dataset in this model, so just return.
3372       return null;
3373     }
3374     // ///////////////////////////////
3375     // LOAD VIEWPORT
3376
3377     // If we just load in the same jar file again, the sequenceSetId
3378     // will be the same, and we end up with multiple references
3379     // to the same sequenceSet. We must modify this id on load
3380     // so that each load of the file gives a unique id
3381     String uniqueSeqSetId = view.getSequenceSetId() + uniqueSetSuffix;
3382     String viewId = (view.getId() == null ? null : view.getId()
3383             + uniqueSetSuffix);
3384     AlignFrame af = null;
3385     AlignViewport av = null;
3386     // now check to see if we really need to create a new viewport.
3387     if (multipleView && viewportsAdded.size() == 0)
3388     {
3389       // We recovered an alignment for which a viewport already exists.
3390       // TODO: fix up any settings necessary for overlaying stored state onto
3391       // state recovered from another document. (may not be necessary).
3392       // we may need a binding from a viewport in memory to one recovered from
3393       // XML.
3394       // and then recover its containing af to allow the settings to be applied.
3395       // TODO: fix for vamsas demo
3396       System.err
3397               .println("About to recover a viewport for existing alignment: Sequence set ID is "
3398                       + uniqueSeqSetId);
3399       Object seqsetobj = retrieveExistingObj(uniqueSeqSetId);
3400       if (seqsetobj != null)
3401       {
3402         if (seqsetobj instanceof String)
3403         {
3404           uniqueSeqSetId = (String) seqsetobj;
3405           System.err
3406                   .println("Recovered extant sequence set ID mapping for ID : New Sequence set ID is "
3407                           + uniqueSeqSetId);
3408         }
3409         else
3410         {
3411           System.err
3412                   .println("Warning : Collision between sequence set ID string and existing jalview object mapping.");
3413         }
3414
3415       }
3416     }
3417     /**
3418      * indicate that annotation colours are applied across all groups (pre
3419      * Jalview 2.8.1 behaviour)
3420      */
3421     boolean doGroupAnnColour = Jalview2XML.isVersionStringLaterThan(
3422             "2.8.1", object.getVersion());
3423
3424     AlignmentPanel ap = null;
3425     boolean isnewview = true;
3426     if (viewId != null)
3427     {
3428       // Check to see if this alignment already has a view id == viewId
3429       jalview.gui.AlignmentPanel views[] = Desktop
3430               .getAlignmentPanels(uniqueSeqSetId);
3431       if (views != null && views.length > 0)
3432       {
3433         for (int v = 0; v < views.length; v++)
3434         {
3435           if (views[v].av.getViewId().equalsIgnoreCase(viewId))
3436           {
3437             // recover the existing alignpanel, alignframe, viewport
3438             af = views[v].alignFrame;
3439             av = views[v].av;
3440             ap = views[v];
3441             // TODO: could even skip resetting view settings if we don't want to
3442             // change the local settings from other jalview processes
3443             isnewview = false;
3444           }
3445         }
3446       }
3447     }
3448
3449     if (isnewview)
3450     {
3451       af = loadViewport(file, jseqs, hiddenSeqs, al, jms, view,
3452               uniqueSeqSetId, viewId, autoAlan);
3453       av = af.viewport;
3454       ap = af.alignPanel;
3455     }
3456
3457     /*
3458      * Load any trees, PDB structures and viewers
3459      * 
3460      * Not done if flag is false (when this method is used for New View)
3461      */
3462     if (loadTreesAndStructures)
3463     {
3464       loadTrees(jms, view, af, av, ap);
3465       loadPDBStructures(jprovider, jseqs, af, ap);
3466       loadRnaViewers(jprovider, jseqs, ap);
3467     }
3468     // and finally return.
3469     return af;
3470   }
3471
3472   /**
3473    * Instantiate and link any saved RNA (Varna) viewers. The state of the Varna
3474    * panel is restored from separate jar entries, two (gapped and trimmed) per
3475    * sequence and secondary structure.
3476    * 
3477    * Currently each viewer shows just one sequence and structure (gapped and
3478    * trimmed), however this method is designed to support multiple sequences or
3479    * structures in viewers if wanted in future.
3480    * 
3481    * @param jprovider
3482    * @param jseqs
3483    * @param ap
3484    */
3485   private void loadRnaViewers(jarInputStreamProvider jprovider,
3486           JSeq[] jseqs, AlignmentPanel ap)
3487   {
3488     /*
3489      * scan the sequences for references to viewers; create each one the first
3490      * time it is referenced, add Rna models to existing viewers
3491      */
3492     for (JSeq jseq : jseqs)
3493     {
3494       for (int i = 0; i < jseq.getRnaViewerCount(); i++)
3495       {
3496         RnaViewer viewer = jseq.getRnaViewer(i);
3497         AppVarna appVarna = findOrCreateVarnaViewer(viewer,
3498                 uniqueSetSuffix, ap);
3499
3500         for (int j = 0; j < viewer.getSecondaryStructureCount(); j++)
3501         {
3502           SecondaryStructure ss = viewer.getSecondaryStructure(j);
3503           SequenceI seq = seqRefIds.get(jseq.getId());
3504           AlignmentAnnotation ann = this.annotationIds.get(ss
3505                   .getAnnotationId());
3506
3507           /*
3508            * add the structure to the Varna display (with session state copied
3509            * from the jar to a temporary file)
3510            */
3511           boolean gapped = ss.isGapped();
3512           String rnaTitle = ss.getTitle();
3513           String sessionState = ss.getViewerState();
3514           String tempStateFile = copyJarEntry(jprovider, sessionState,
3515                   "varna");
3516           RnaModel rna = new RnaModel(rnaTitle, ann, seq, null, gapped);
3517           appVarna.addModelSession(rna, rnaTitle, tempStateFile);
3518         }
3519         appVarna.setInitialSelection(viewer.getSelectedRna());
3520       }
3521     }
3522   }
3523
3524   /**
3525    * Locate and return an already instantiated matching AppVarna, or create one
3526    * if not found
3527    * 
3528    * @param viewer
3529    * @param viewIdSuffix
3530    * @param ap
3531    * @return
3532    */
3533   protected AppVarna findOrCreateVarnaViewer(RnaViewer viewer,
3534           String viewIdSuffix, AlignmentPanel ap)
3535   {
3536     /*
3537      * on each load a suffix is appended to the saved viewId, to avoid conflicts
3538      * if load is repeated
3539      */
3540     String postLoadId = viewer.getViewId() + viewIdSuffix;
3541     for (JInternalFrame frame : getAllFrames())
3542     {
3543       if (frame instanceof AppVarna)
3544       {
3545         AppVarna varna = (AppVarna) frame;
3546         if (postLoadId.equals(varna.getViewId()))
3547         {
3548           // this viewer is already instantiated
3549           // could in future here add ap as another 'parent' of the
3550           // AppVarna window; currently just 1-to-many
3551           return varna;
3552         }
3553       }
3554     }
3555
3556     /*
3557      * viewer not found - make it
3558      */
3559     RnaViewerModel model = new RnaViewerModel(postLoadId,
3560             viewer.getTitle(), viewer.getXpos(), viewer.getYpos(),
3561             viewer.getWidth(), viewer.getHeight(),
3562             viewer.getDividerLocation());
3563     AppVarna varna = new AppVarna(model, ap);
3564
3565     return varna;
3566   }
3567
3568   /**
3569    * Load any saved trees
3570    * 
3571    * @param jms
3572    * @param view
3573    * @param af
3574    * @param av
3575    * @param ap
3576    */
3577   protected void loadTrees(JalviewModelSequence jms, Viewport view,
3578           AlignFrame af, AlignViewport av, AlignmentPanel ap)
3579   {
3580     // TODO result of automated refactoring - are all these parameters needed?
3581     try
3582     {
3583       for (int t = 0; t < jms.getTreeCount(); t++)
3584       {
3585
3586         Tree tree = jms.getTree(t);
3587
3588         TreePanel tp = (TreePanel) retrieveExistingObj(tree.getId());
3589         if (tp == null)
3590         {
3591           tp = af.ShowNewickTree(
3592                   new jalview.io.NewickFile(tree.getNewick()),
3593                   tree.getTitle(), tree.getWidth(), tree.getHeight(),
3594                   tree.getXpos(), tree.getYpos());
3595           if (tree.getId() != null)
3596           {
3597             // perhaps bind the tree id to something ?
3598           }
3599         }
3600         else
3601         {
3602           // update local tree attributes ?
3603           // TODO: should check if tp has been manipulated by user - if so its
3604           // settings shouldn't be modified
3605           tp.setTitle(tree.getTitle());
3606           tp.setBounds(new Rectangle(tree.getXpos(), tree.getYpos(), tree
3607                   .getWidth(), tree.getHeight()));
3608           tp.av = av; // af.viewport; // TODO: verify 'associate with all
3609           // views'
3610           // works still
3611           tp.treeCanvas.av = av; // af.viewport;
3612           tp.treeCanvas.ap = ap; // af.alignPanel;
3613
3614         }
3615         if (tp == null)
3616         {
3617           warn("There was a problem recovering stored Newick tree: \n"
3618                   + tree.getNewick());
3619           continue;
3620         }
3621
3622         tp.fitToWindow.setState(tree.getFitToWindow());
3623         tp.fitToWindow_actionPerformed(null);
3624
3625         if (tree.getFontName() != null)
3626         {
3627           tp.setTreeFont(new java.awt.Font(tree.getFontName(), tree
3628                   .getFontStyle(), tree.getFontSize()));
3629         }
3630         else
3631         {
3632           tp.setTreeFont(new java.awt.Font(view.getFontName(), view
3633                   .getFontStyle(), tree.getFontSize()));
3634         }
3635
3636         tp.showPlaceholders(tree.getMarkUnlinked());
3637         tp.showBootstrap(tree.getShowBootstrap());
3638         tp.showDistances(tree.getShowDistances());
3639
3640         tp.treeCanvas.threshold = tree.getThreshold();
3641
3642         if (tree.getCurrentTree())
3643         {
3644           af.viewport.setCurrentTree(tp.getTree());
3645         }
3646       }
3647
3648     } catch (Exception ex)
3649     {
3650       ex.printStackTrace();
3651     }
3652   }
3653
3654   /**
3655    * Load and link any saved structure viewers.
3656    * 
3657    * @param jprovider
3658    * @param jseqs
3659    * @param af
3660    * @param ap
3661    */
3662   protected void loadPDBStructures(jarInputStreamProvider jprovider,
3663           JSeq[] jseqs, AlignFrame af, AlignmentPanel ap)
3664   {
3665     /*
3666      * Run through all PDB ids on the alignment, and collect mappings between
3667      * distinct view ids and all sequences referring to that view.
3668      */
3669     Map<String, StructureViewerModel> structureViewers = new LinkedHashMap<String, StructureViewerModel>();
3670
3671     for (int i = 0; i < jseqs.length; i++)
3672     {
3673       if (jseqs[i].getPdbidsCount() > 0)
3674       {
3675         Pdbids[] ids = jseqs[i].getPdbids();
3676         for (int p = 0; p < ids.length; p++)
3677         {
3678           final int structureStateCount = ids[p].getStructureStateCount();
3679           for (int s = 0; s < structureStateCount; s++)
3680           {
3681             // check to see if we haven't already created this structure view
3682             final StructureState structureState = ids[p]
3683                     .getStructureState(s);
3684             String sviewid = (structureState.getViewId() == null) ? null
3685                     : structureState.getViewId() + uniqueSetSuffix;
3686             jalview.datamodel.PDBEntry jpdb = new jalview.datamodel.PDBEntry();
3687             // Originally : ids[p].getFile()
3688             // : TODO: verify external PDB file recovery still works in normal
3689             // jalview project load
3690             jpdb.setFile(loadPDBFile(jprovider, ids[p].getId()));
3691             jpdb.setId(ids[p].getId());
3692
3693             int x = structureState.getXpos();
3694             int y = structureState.getYpos();
3695             int width = structureState.getWidth();
3696             int height = structureState.getHeight();
3697
3698             // Probably don't need to do this anymore...
3699             // Desktop.desktop.getComponentAt(x, y);
3700             // TODO: NOW: check that this recovers the PDB file correctly.
3701             String pdbFile = loadPDBFile(jprovider, ids[p].getId());
3702             jalview.datamodel.SequenceI seq = seqRefIds.get(jseqs[i]
3703                     .getId() + "");
3704             if (sviewid == null)
3705             {
3706               sviewid = "_jalview_pre2_4_" + x + "," + y + "," + width
3707                       + "," + height;
3708             }
3709             if (!structureViewers.containsKey(sviewid))
3710             {
3711               structureViewers.put(sviewid,
3712                       new StructureViewerModel(x, y, width, height, false,
3713                               false, true, structureState.getViewId(),
3714                               structureState.getType()));
3715               // Legacy pre-2.7 conversion JAL-823 :
3716               // do not assume any view has to be linked for colour by
3717               // sequence
3718             }
3719
3720             // assemble String[] { pdb files }, String[] { id for each
3721             // file }, orig_fileloc, SequenceI[][] {{ seqs_file 1 }, {
3722             // seqs_file 2}, boolean[] {
3723             // linkAlignPanel,superposeWithAlignpanel}} from hash
3724             StructureViewerModel jmoldat = structureViewers.get(sviewid);
3725             jmoldat.setAlignWithPanel(jmoldat.isAlignWithPanel()
3726                     | (structureState.hasAlignwithAlignPanel() ? structureState
3727                             .getAlignwithAlignPanel() : false));
3728
3729             /*
3730              * Default colour by linked panel to false if not specified (e.g.
3731              * for pre-2.7 projects)
3732              */
3733             boolean colourWithAlignPanel = jmoldat.isColourWithAlignPanel();
3734             colourWithAlignPanel |= (structureState
3735                     .hasColourwithAlignPanel() ? structureState
3736                     .getColourwithAlignPanel() : false);
3737             jmoldat.setColourWithAlignPanel(colourWithAlignPanel);
3738
3739             /*
3740              * Default colour by viewer to true if not specified (e.g. for
3741              * pre-2.7 projects)
3742              */
3743             boolean colourByViewer = jmoldat.isColourByViewer();
3744             colourByViewer &= structureState.hasColourByJmol() ? structureState
3745                     .getColourByJmol() : true;
3746             jmoldat.setColourByViewer(colourByViewer);
3747
3748             if (jmoldat.getStateData().length() < structureState
3749                     .getContent().length())
3750             {
3751               {
3752                 jmoldat.setStateData(structureState.getContent());
3753               }
3754             }
3755             if (ids[p].getFile() != null)
3756             {
3757               File mapkey = new File(ids[p].getFile());
3758               StructureData seqstrmaps = jmoldat.getFileData().get(mapkey);
3759               if (seqstrmaps == null)
3760               {
3761                 jmoldat.getFileData().put(
3762                         mapkey,
3763                         seqstrmaps = jmoldat.new StructureData(pdbFile,
3764                                 ids[p].getId()));
3765               }
3766               if (!seqstrmaps.getSeqList().contains(seq))
3767               {
3768                 seqstrmaps.getSeqList().add(seq);
3769                 // TODO and chains?
3770               }
3771             }
3772             else
3773             {
3774               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");
3775               warn(errorMessage);
3776             }
3777           }
3778         }
3779       }
3780     }
3781     // Instantiate the associated structure views
3782     for (Entry<String, StructureViewerModel> entry : structureViewers
3783             .entrySet())
3784     {
3785       try
3786       {
3787         createOrLinkStructureViewer(entry, af, ap, jprovider);
3788       } catch (Exception e)
3789       {
3790         System.err.println("Error loading structure viewer: "
3791                 + e.getMessage());
3792         // failed - try the next one
3793       }
3794     }
3795   }
3796
3797   /**
3798    * 
3799    * @param viewerData
3800    * @param af
3801    * @param ap
3802    * @param jprovider
3803    */
3804   protected void createOrLinkStructureViewer(
3805           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
3806           AlignmentPanel ap, jarInputStreamProvider jprovider)
3807   {
3808     final StructureViewerModel stateData = viewerData.getValue();
3809
3810     /*
3811      * Search for any viewer windows already open from other alignment views
3812      * that exactly match the stored structure state
3813      */
3814     StructureViewerBase comp = findMatchingViewer(viewerData);
3815
3816     if (comp != null)
3817     {
3818       linkStructureViewer(ap, comp, stateData);
3819       return;
3820     }
3821
3822     /*
3823      * From 2.9: stateData.type contains JMOL or CHIMERA, data is in jar entry
3824      * "viewer_"+stateData.viewId
3825      */
3826     if (ViewerType.CHIMERA.toString().equals(stateData.getType()))
3827     {
3828       createChimeraViewer(viewerData, af, jprovider);
3829     }
3830     else
3831     {
3832       /*
3833        * else Jmol (if pre-2.9, stateData contains JMOL state string)
3834        */
3835       createJmolViewer(viewerData, af, jprovider);
3836     }
3837   }
3838
3839   /**
3840    * Create a new Chimera viewer.
3841    * 
3842    * @param data
3843    * @param af
3844    * @param jprovider
3845    */
3846   protected void createChimeraViewer(
3847           Entry<String, StructureViewerModel> viewerData, AlignFrame af,
3848           jarInputStreamProvider jprovider)
3849   {
3850     StructureViewerModel data = viewerData.getValue();
3851     String chimeraSessionFile = data.getStateData();
3852
3853     /*
3854      * Copy Chimera session from jar entry "viewer_"+viewId to a temporary file
3855      * 
3856      * NB this is the 'saved' viewId as in the project file XML, _not_ the
3857      * 'uniquified' sviewid used to reconstruct the viewer here
3858      */
3859     String viewerJarEntryName = getViewerJarEntryName(data.getViewId());
3860     chimeraSessionFile = copyJarEntry(jprovider, viewerJarEntryName,
3861             "chimera");
3862
3863     Set<Entry<File, StructureData>> fileData = data.getFileData()
3864             .entrySet();
3865     List<PDBEntry> pdbs = new ArrayList<PDBEntry>();
3866     List<SequenceI[]> allseqs = new ArrayList<SequenceI[]>();
3867     for (Entry<File, StructureData> pdb : fileData)
3868     {
3869       String filePath = pdb.getValue().getFilePath();
3870       String pdbId = pdb.getValue().getPdbId();
3871       // pdbs.add(new PDBEntry(filePath, pdbId));
3872       pdbs.add(new PDBEntry(pdbId, null, PDBEntry.Type.PDB, filePath));
3873       final List<SequenceI> seqList = pdb.getValue().getSeqList();
3874       SequenceI[] seqs = seqList.toArray(new SequenceI[seqList.size()]);
3875       allseqs.add(seqs);
3876     }
3877
3878     boolean colourByChimera = data.isColourByViewer();
3879     boolean colourBySequence = data.isColourWithAlignPanel();
3880
3881     // TODO use StructureViewer as a factory here, see JAL-1761
3882     final PDBEntry[] pdbArray = pdbs.toArray(new PDBEntry[pdbs.size()]);
3883     final SequenceI[][] seqsArray = allseqs.toArray(new SequenceI[allseqs
3884             .size()][]);
3885     String newViewId = viewerData.getKey();
3886
3887     ChimeraViewFrame cvf = new ChimeraViewFrame(chimeraSessionFile,
3888             af.alignPanel, pdbArray, seqsArray, colourByChimera,
3889             colourBySequence, newViewId);
3890     cvf.setSize(data.getWidth(), data.getHeight());
3891     cvf.setLocation(data.getX(), data.getY());
3892   }
3893
3894   /**
3895    * Create a new Jmol window. First parse the Jmol state to translate filenames
3896    * loaded into the view, and record the order in which files are shown in the
3897    * Jmol view, so we can add the sequence mappings in same order.
3898    * 
3899    * @param viewerData
3900    * @param af
3901    * @param jprovider
3902    */
3903   protected void createJmolViewer(
3904           final Entry<String, StructureViewerModel> viewerData,
3905           AlignFrame af, jarInputStreamProvider jprovider)
3906   {
3907     final StructureViewerModel svattrib = viewerData.getValue();
3908     String state = svattrib.getStateData();
3909
3910     /*
3911      * Pre-2.9: state element value is the Jmol state string
3912      * 
3913      * 2.9+: @type is "JMOL", state data is in a Jar file member named "viewer_"
3914      * + viewId
3915      */
3916     if (ViewerType.JMOL.toString().equals(svattrib.getType()))
3917     {
3918       state = readJarEntry(jprovider,
3919               getViewerJarEntryName(svattrib.getViewId()));
3920     }
3921
3922     List<String> pdbfilenames = new ArrayList<String>();
3923     List<SequenceI[]> seqmaps = new ArrayList<SequenceI[]>();
3924     List<String> pdbids = new ArrayList<String>();
3925     StringBuilder newFileLoc = new StringBuilder(64);
3926     int cp = 0, ncp, ecp;
3927     Map<File, StructureData> oldFiles = svattrib.getFileData();
3928     while ((ncp = state.indexOf("load ", cp)) > -1)
3929     {
3930       do
3931       {
3932         // look for next filename in load statement
3933         newFileLoc.append(state.substring(cp,
3934                 ncp = (state.indexOf("\"", ncp + 1) + 1)));
3935         String oldfilenam = state.substring(ncp,
3936                 ecp = state.indexOf("\"", ncp));
3937         // recover the new mapping data for this old filename
3938         // have to normalize filename - since Jmol and jalview do
3939         // filename
3940         // translation differently.
3941         StructureData filedat = oldFiles.get(new File(oldfilenam));
3942         newFileLoc.append(Platform.escapeString(filedat.getFilePath()));
3943         pdbfilenames.add(filedat.getFilePath());
3944         pdbids.add(filedat.getPdbId());
3945         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
3946         newFileLoc.append("\"");
3947         cp = ecp + 1; // advance beyond last \" and set cursor so we can
3948                       // look for next file statement.
3949       } while ((ncp = state.indexOf("/*file*/", cp)) > -1);
3950     }
3951     if (cp > 0)
3952     {
3953       // just append rest of state
3954       newFileLoc.append(state.substring(cp));
3955     }
3956     else
3957     {
3958       System.err.print("Ignoring incomplete Jmol state for PDB ids: ");
3959       newFileLoc = new StringBuilder(state);
3960       newFileLoc.append("; load append ");
3961       for (File id : oldFiles.keySet())
3962       {
3963         // add this and any other pdb files that should be present in
3964         // the viewer
3965         StructureData filedat = oldFiles.get(id);
3966         newFileLoc.append(filedat.getFilePath());
3967         pdbfilenames.add(filedat.getFilePath());
3968         pdbids.add(filedat.getPdbId());
3969         seqmaps.add(filedat.getSeqList().toArray(new SequenceI[0]));
3970         newFileLoc.append(" \"");
3971         newFileLoc.append(filedat.getFilePath());
3972         newFileLoc.append("\"");
3973
3974       }
3975       newFileLoc.append(";");
3976     }
3977
3978     if (newFileLoc.length() == 0)
3979     {
3980       return;
3981     }
3982     int histbug = newFileLoc.indexOf("history = ");
3983     if (histbug > -1)
3984     {
3985       /*
3986        * change "history = [true|false];" to "history = [1|0];"
3987        */
3988       histbug += 10;
3989       int diff = histbug == -1 ? -1 : newFileLoc.indexOf(";", histbug);
3990       String val = (diff == -1) ? null : newFileLoc
3991               .substring(histbug, diff);
3992       if (val != null && val.length() >= 4)
3993       {
3994         if (val.contains("e")) // eh? what can it be?
3995         {
3996           if (val.trim().equals("true"))
3997           {
3998             val = "1";
3999           }
4000           else
4001           {
4002             val = "0";
4003           }
4004           newFileLoc.replace(histbug, diff, val);
4005         }
4006       }
4007     }
4008
4009     final String[] pdbf = pdbfilenames.toArray(new String[pdbfilenames
4010             .size()]);
4011     final String[] id = pdbids.toArray(new String[pdbids.size()]);
4012     final SequenceI[][] sq = seqmaps
4013             .toArray(new SequenceI[seqmaps.size()][]);
4014     final String fileloc = newFileLoc.toString();
4015     final String sviewid = viewerData.getKey();
4016     final AlignFrame alf = af;
4017     final Rectangle rect = new Rectangle(svattrib.getX(), svattrib.getY(),
4018             svattrib.getWidth(), svattrib.getHeight());
4019     try
4020     {
4021       javax.swing.SwingUtilities.invokeAndWait(new Runnable()
4022       {
4023         @Override
4024         public void run()
4025         {
4026           JalviewStructureDisplayI sview = null;
4027           try
4028           {
4029             sview = new StructureViewer(alf.alignPanel
4030                     .getStructureSelectionManager()).createView(
4031                     StructureViewer.ViewerType.JMOL, pdbf, id, sq,
4032                     alf.alignPanel, svattrib, fileloc, rect, sviewid);
4033             addNewStructureViewer(sview);
4034           } catch (OutOfMemoryError ex)
4035           {
4036             new OOMWarning("restoring structure view for PDB id " + id,
4037                     (OutOfMemoryError) ex.getCause());
4038             if (sview != null && sview.isVisible())
4039             {
4040               sview.closeViewer(false);
4041               sview.setVisible(false);
4042               sview.dispose();
4043             }
4044           }
4045         }
4046       });
4047     } catch (InvocationTargetException ex)
4048     {
4049       warn("Unexpected error when opening Jmol view.", ex);
4050
4051     } catch (InterruptedException e)
4052     {
4053       // e.printStackTrace();
4054     }
4055
4056   }
4057
4058   /**
4059    * Generates a name for the entry in the project jar file to hold state
4060    * information for a structure viewer
4061    * 
4062    * @param viewId
4063    * @return
4064    */
4065   protected String getViewerJarEntryName(String viewId)
4066   {
4067     return VIEWER_PREFIX + viewId;
4068   }
4069
4070   /**
4071    * Returns any open frame that matches given structure viewer data. The match
4072    * is based on the unique viewId, or (for older project versions) the frame's
4073    * geometry.
4074    * 
4075    * @param viewerData
4076    * @return
4077    */
4078   protected StructureViewerBase findMatchingViewer(
4079           Entry<String, StructureViewerModel> viewerData)
4080   {
4081     final String sviewid = viewerData.getKey();
4082     final StructureViewerModel svattrib = viewerData.getValue();
4083     StructureViewerBase comp = null;
4084     JInternalFrame[] frames = getAllFrames();
4085     for (JInternalFrame frame : frames)
4086     {
4087       if (frame instanceof StructureViewerBase)
4088       {
4089         /*
4090          * Post jalview 2.4 schema includes structure view id
4091          */
4092         if (sviewid != null
4093                 && ((StructureViewerBase) frame).getViewId()
4094                         .equals(sviewid))
4095         {
4096           comp = (StructureViewerBase) frame;
4097           break; // break added in 2.9
4098         }
4099         /*
4100          * Otherwise test for matching position and size of viewer frame
4101          */
4102         else if (frame.getX() == svattrib.getX()
4103                 && frame.getY() == svattrib.getY()
4104                 && frame.getHeight() == svattrib.getHeight()
4105                 && frame.getWidth() == svattrib.getWidth())
4106         {
4107           comp = (StructureViewerBase) frame;
4108           // no break in faint hope of an exact match on viewId
4109         }
4110       }
4111     }
4112     return comp;
4113   }
4114
4115   /**
4116    * Link an AlignmentPanel to an existing structure viewer.
4117    * 
4118    * @param ap
4119    * @param viewer
4120    * @param oldFiles
4121    * @param useinViewerSuperpos
4122    * @param usetoColourbyseq
4123    * @param viewerColouring
4124    */
4125   protected void linkStructureViewer(AlignmentPanel ap,
4126           StructureViewerBase viewer, StructureViewerModel stateData)
4127   {
4128     // NOTE: if the jalview project is part of a shared session then
4129     // view synchronization should/could be done here.
4130
4131     final boolean useinViewerSuperpos = stateData.isAlignWithPanel();
4132     final boolean usetoColourbyseq = stateData.isColourWithAlignPanel();
4133     final boolean viewerColouring = stateData.isColourByViewer();
4134     Map<File, StructureData> oldFiles = stateData.getFileData();
4135
4136     /*
4137      * Add mapping for sequences in this view to an already open viewer
4138      */
4139     final AAStructureBindingModel binding = viewer.getBinding();
4140     for (File id : oldFiles.keySet())
4141     {
4142       // add this and any other pdb files that should be present in the
4143       // viewer
4144       StructureData filedat = oldFiles.get(id);
4145       String pdbFile = filedat.getFilePath();
4146       SequenceI[] seq = filedat.getSeqList().toArray(new SequenceI[0]);
4147       binding.getSsm().setMapping(seq, null, pdbFile,
4148               jalview.io.AppletFormatAdapter.FILE);
4149       binding.addSequenceForStructFile(pdbFile, seq);
4150     }
4151     // and add the AlignmentPanel's reference to the view panel
4152     viewer.addAlignmentPanel(ap);
4153     if (useinViewerSuperpos)
4154     {
4155       viewer.useAlignmentPanelForSuperposition(ap);
4156     }
4157     else
4158     {
4159       viewer.excludeAlignmentPanelForSuperposition(ap);
4160     }
4161     if (usetoColourbyseq)
4162     {
4163       viewer.useAlignmentPanelForColourbyseq(ap, !viewerColouring);
4164     }
4165     else
4166     {
4167       viewer.excludeAlignmentPanelForColourbyseq(ap);
4168     }
4169   }
4170
4171   /**
4172    * Get all frames within the Desktop.
4173    * 
4174    * @return
4175    */
4176   protected JInternalFrame[] getAllFrames()
4177   {
4178     JInternalFrame[] frames = null;
4179     // TODO is this necessary - is it safe - risk of hanging?
4180     do
4181     {
4182       try
4183       {
4184         frames = Desktop.desktop.getAllFrames();
4185       } catch (ArrayIndexOutOfBoundsException e)
4186       {
4187         // occasional No such child exceptions are thrown here...
4188         try
4189         {
4190           Thread.sleep(10);
4191         } catch (InterruptedException f)
4192         {
4193         }
4194       }
4195     } while (frames == null);
4196     return frames;
4197   }
4198
4199   /**
4200    * Answers true if 'version' is equal to or later than 'supported', where each
4201    * is formatted as major/minor versions like "2.8.3" or "2.3.4b1" for bugfix
4202    * changes. Development and test values for 'version' are leniently treated
4203    * i.e. answer true.
4204    * 
4205    * @param supported
4206    *          - minimum version we are comparing against
4207    * @param version
4208    *          - version of data being processsed
4209    * @return
4210    */
4211   public static boolean isVersionStringLaterThan(String supported,
4212           String version)
4213   {
4214     if (supported == null || version == null
4215             || version.equalsIgnoreCase("DEVELOPMENT BUILD")
4216             || version.equalsIgnoreCase("Test")
4217             || version.equalsIgnoreCase("AUTOMATED BUILD"))
4218     {
4219       System.err.println("Assuming project file with "
4220               + (version == null ? "null" : version)
4221               + " is compatible with Jalview version " + supported);
4222       return true;
4223     }
4224     else
4225     {
4226       return StringUtils.compareVersions(version, supported, "b") >= 0;
4227     }
4228   }
4229
4230   Vector<JalviewStructureDisplayI> newStructureViewers = null;
4231
4232   protected void addNewStructureViewer(JalviewStructureDisplayI sview)
4233   {
4234     if (newStructureViewers != null)
4235     {
4236       sview.getBinding().setFinishedLoadingFromArchive(false);
4237       newStructureViewers.add(sview);
4238     }
4239   }
4240
4241   protected void setLoadingFinishedForNewStructureViewers()
4242   {
4243     if (newStructureViewers != null)
4244     {
4245       for (JalviewStructureDisplayI sview : newStructureViewers)
4246       {
4247         sview.getBinding().setFinishedLoadingFromArchive(true);
4248       }
4249       newStructureViewers.clear();
4250       newStructureViewers = null;
4251     }
4252   }
4253
4254   AlignFrame loadViewport(String file, JSeq[] JSEQ,
4255           List<SequenceI> hiddenSeqs, AlignmentI al,
4256           JalviewModelSequence jms, Viewport view, String uniqueSeqSetId,
4257           String viewId, List<JvAnnotRow> autoAlan)
4258   {
4259     AlignFrame af = null;
4260     af = new AlignFrame(al, view.getWidth(), view.getHeight(),
4261             uniqueSeqSetId, viewId);
4262
4263     af.setFileName(file, "Jalview");
4264
4265     for (int i = 0; i < JSEQ.length; i++)
4266     {
4267       af.viewport.setSequenceColour(af.viewport.getAlignment()
4268               .getSequenceAt(i), new java.awt.Color(JSEQ[i].getColour()));
4269     }
4270
4271     if (al.hasSeqrep())
4272     {
4273       af.getViewport().setColourByReferenceSeq(true);
4274       af.getViewport().setDisplayReferenceSeq(true);
4275     }
4276
4277     af.viewport.setGatherViewsHere(view.getGatheredViews());
4278
4279     if (view.getSequenceSetId() != null)
4280     {
4281       AlignmentViewport av = viewportsAdded.get(uniqueSeqSetId);
4282
4283       af.viewport.setSequenceSetId(uniqueSeqSetId);
4284       if (av != null)
4285       {
4286         // propagate shared settings to this new view
4287         af.viewport.setHistoryList(av.getHistoryList());
4288         af.viewport.setRedoList(av.getRedoList());
4289       }
4290       else
4291       {
4292         viewportsAdded.put(uniqueSeqSetId, af.viewport);
4293       }
4294       // TODO: check if this method can be called repeatedly without
4295       // side-effects if alignpanel already registered.
4296       PaintRefresher.Register(af.alignPanel, uniqueSeqSetId);
4297     }
4298     // apply Hidden regions to view.
4299     if (hiddenSeqs != null)
4300     {
4301       for (int s = 0; s < JSEQ.length; s++)
4302       {
4303         SequenceGroup hidden = new SequenceGroup();
4304         boolean isRepresentative = false;
4305         for (int r = 0; r < JSEQ[s].getHiddenSequencesCount(); r++)
4306         {
4307           isRepresentative = true;
4308           SequenceI sequenceToHide = al.getSequenceAt(JSEQ[s]
4309                   .getHiddenSequences(r));
4310           hidden.addSequence(sequenceToHide, false);
4311           // remove from hiddenSeqs list so we don't try to hide it twice
4312           hiddenSeqs.remove(sequenceToHide);
4313         }
4314         if (isRepresentative)
4315         {
4316           SequenceI representativeSequence = al.getSequenceAt(s);
4317           hidden.addSequence(representativeSequence, false);
4318           af.viewport.hideRepSequences(representativeSequence, hidden);
4319         }
4320       }
4321
4322       SequenceI[] hseqs = hiddenSeqs.toArray(new SequenceI[hiddenSeqs
4323               .size()]);
4324       af.viewport.hideSequence(hseqs);
4325
4326     }
4327     // recover view properties and display parameters
4328     if (view.getViewName() != null)
4329     {
4330       af.viewport.viewName = view.getViewName();
4331       af.setInitialTabVisible();
4332     }
4333     af.setBounds(view.getXpos(), view.getYpos(), view.getWidth(),
4334             view.getHeight());
4335
4336     af.viewport.setShowAnnotation(view.getShowAnnotation());
4337     af.viewport.setAbovePIDThreshold(view.getPidSelected());
4338
4339     af.viewport.setColourText(view.getShowColourText());
4340
4341     af.viewport.setConservationSelected(view.getConservationSelected());
4342     af.viewport.setShowJVSuffix(view.getShowFullId());
4343     af.viewport.setRightAlignIds(view.getRightAlignIds());
4344     af.viewport.setFont(
4345             new java.awt.Font(view.getFontName(), view.getFontStyle(), view
4346                     .getFontSize()), true);
4347     ViewStyleI vs = af.viewport.getViewStyle();
4348     vs.setScaleProteinAsCdna(view.isScaleProteinAsCdna());
4349     af.viewport.setViewStyle(vs);
4350     // TODO: allow custom charWidth/Heights to be restored by updating them
4351     // after setting font - which means set above to false
4352     af.viewport.setRenderGaps(view.getRenderGaps());
4353     af.viewport.setWrapAlignment(view.getWrapAlignment());
4354     af.viewport.setShowAnnotation(view.getShowAnnotation());
4355
4356     af.viewport.setShowBoxes(view.getShowBoxes());
4357
4358     af.viewport.setShowText(view.getShowText());
4359
4360     af.viewport.setTextColour(new java.awt.Color(view.getTextCol1()));
4361     af.viewport.setTextColour2(new java.awt.Color(view.getTextCol2()));
4362     af.viewport.setThresholdTextColour(view.getTextColThreshold());
4363     af.viewport.setShowUnconserved(view.hasShowUnconserved() ? view
4364             .isShowUnconserved() : false);
4365     af.viewport.setStartRes(view.getStartRes());
4366     af.viewport.setStartSeq(view.getStartSeq());
4367     af.alignPanel.updateLayout();
4368     ColourSchemeI cs = null;
4369     // apply colourschemes
4370     if (view.getBgColour() != null)
4371     {
4372       if (view.getBgColour().startsWith("ucs"))
4373       {
4374         cs = getUserColourScheme(jms, view.getBgColour());
4375       }
4376       else if (view.getBgColour().startsWith("Annotation"))
4377       {
4378         AnnotationColours viewAnnColour = view.getAnnotationColours();
4379         cs = constructAnnotationColour(viewAnnColour, af, al, jms, true);
4380
4381         // annpos
4382
4383       }
4384       else
4385       {
4386         cs = ColourSchemeProperty.getColour(al, view.getBgColour());
4387       }
4388
4389       if (cs != null)
4390       {
4391         cs.setThreshold(view.getPidThreshold(), true);
4392         cs.setConsensus(af.viewport.getSequenceConsensusHash());
4393       }
4394     }
4395
4396     af.viewport.setGlobalColourScheme(cs);
4397     af.viewport.setColourAppliesToAllGroups(false);
4398
4399     if (view.getConservationSelected() && cs != null)
4400     {
4401       cs.setConservationInc(view.getConsThreshold());
4402     }
4403
4404     af.changeColour(cs);
4405
4406     af.viewport.setColourAppliesToAllGroups(true);
4407
4408     af.viewport.setShowSequenceFeatures(view.getShowSequenceFeatures());
4409
4410     if (view.hasCentreColumnLabels())
4411     {
4412       af.viewport.setCentreColumnLabels(view.getCentreColumnLabels());
4413     }
4414     if (view.hasIgnoreGapsinConsensus())
4415     {
4416       af.viewport.setIgnoreGapsConsensus(view.getIgnoreGapsinConsensus(),
4417               null);
4418     }
4419     if (view.hasFollowHighlight())
4420     {
4421       af.viewport.setFollowHighlight(view.getFollowHighlight());
4422     }
4423     if (view.hasFollowSelection())
4424     {
4425       af.viewport.followSelection = view.getFollowSelection();
4426     }
4427     if (view.hasShowConsensusHistogram())
4428     {
4429       af.viewport.setShowConsensusHistogram(view
4430               .getShowConsensusHistogram());
4431     }
4432     else
4433     {
4434       af.viewport.setShowConsensusHistogram(true);
4435     }
4436     if (view.hasShowSequenceLogo())
4437     {
4438       af.viewport.setShowSequenceLogo(view.getShowSequenceLogo());
4439     }
4440     else
4441     {
4442       af.viewport.setShowSequenceLogo(false);
4443     }
4444     if (view.hasNormaliseSequenceLogo())
4445     {
4446       af.viewport.setNormaliseSequenceLogo(view.getNormaliseSequenceLogo());
4447     }
4448     if (view.hasShowDbRefTooltip())
4449     {
4450       af.viewport.setShowDBRefs(view.getShowDbRefTooltip());
4451     }
4452     if (view.hasShowNPfeatureTooltip())
4453     {
4454       af.viewport.setShowNPFeats(view.hasShowNPfeatureTooltip());
4455     }
4456     if (view.hasShowGroupConsensus())
4457     {
4458       af.viewport.setShowGroupConsensus(view.getShowGroupConsensus());
4459     }
4460     else
4461     {
4462       af.viewport.setShowGroupConsensus(false);
4463     }
4464     if (view.hasShowGroupConservation())
4465     {
4466       af.viewport.setShowGroupConservation(view.getShowGroupConservation());
4467     }
4468     else
4469     {
4470       af.viewport.setShowGroupConservation(false);
4471     }
4472
4473     // recover featre settings
4474     if (jms.getFeatureSettings() != null)
4475     {
4476       FeaturesDisplayed fdi;
4477       af.viewport.setFeaturesDisplayed(fdi = new FeaturesDisplayed());
4478       String[] renderOrder = new String[jms.getFeatureSettings()
4479               .getSettingCount()];
4480       Map<String, FeatureColourI> featureColours = new Hashtable<String, FeatureColourI>();
4481       Map<String, Float> featureOrder = new Hashtable<String, Float>();
4482
4483       for (int fs = 0; fs < jms.getFeatureSettings().getSettingCount(); fs++)
4484       {
4485         Setting setting = jms.getFeatureSettings().getSetting(fs);
4486         if (setting.hasMincolour())
4487         {
4488           FeatureColourI gc = setting.hasMin() ? new FeatureColour(
4489                   new Color(setting.getMincolour()), new Color(
4490                           setting.getColour()), setting.getMin(),
4491                   setting.getMax()) : new FeatureColour(new Color(
4492                   setting.getMincolour()), new Color(setting.getColour()),
4493                   0, 1);
4494           if (setting.hasThreshold())
4495           {
4496             gc.setThreshold(setting.getThreshold());
4497             int threshstate = setting.getThreshstate();
4498             // -1 = None, 0 = Below, 1 = Above threshold
4499             if (threshstate == 0)
4500             {
4501               gc.setBelowThreshold(true);
4502             }
4503             else if (threshstate == 1)
4504             {
4505               gc.setAboveThreshold(true);
4506             }
4507           }
4508           gc.setAutoScaled(true); // default
4509           if (setting.hasAutoScale())
4510           {
4511             gc.setAutoScaled(setting.getAutoScale());
4512           }
4513           if (setting.hasColourByLabel())
4514           {
4515             gc.setColourByLabel(setting.getColourByLabel());
4516           }
4517           // and put in the feature colour table.
4518           featureColours.put(setting.getType(), gc);
4519         }
4520         else
4521         {
4522           featureColours.put(setting.getType(), new FeatureColour(
4523                   new Color(setting.getColour())));
4524         }
4525         renderOrder[fs] = setting.getType();
4526         if (setting.hasOrder())
4527         {
4528           featureOrder.put(setting.getType(), setting.getOrder());
4529         }
4530         else
4531         {
4532           featureOrder.put(setting.getType(), new Float(fs
4533                   / jms.getFeatureSettings().getSettingCount()));
4534         }
4535         if (setting.getDisplay())
4536         {
4537           fdi.setVisible(setting.getType());
4538         }
4539       }
4540       Map<String, Boolean> fgtable = new Hashtable<String, Boolean>();
4541       for (int gs = 0; gs < jms.getFeatureSettings().getGroupCount(); gs++)
4542       {
4543         Group grp = jms.getFeatureSettings().getGroup(gs);
4544         fgtable.put(grp.getName(), new Boolean(grp.getDisplay()));
4545       }
4546       // FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
4547       // fgtable, featureColours, jms.getFeatureSettings().hasTransparency() ?
4548       // jms.getFeatureSettings().getTransparency() : 0.0, featureOrder);
4549       FeatureRendererSettings frs = new FeatureRendererSettings(
4550               renderOrder, fgtable, featureColours, 1.0f, featureOrder);
4551       af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer()
4552               .transferSettings(frs);
4553
4554     }
4555
4556     if (view.getHiddenColumnsCount() > 0)
4557     {
4558       for (int c = 0; c < view.getHiddenColumnsCount(); c++)
4559       {
4560         af.viewport.hideColumns(view.getHiddenColumns(c).getStart(), view
4561                 .getHiddenColumns(c).getEnd() // +1
4562                 );
4563       }
4564     }
4565     if (view.getCalcIdParam() != null)
4566     {
4567       for (CalcIdParam calcIdParam : view.getCalcIdParam())
4568       {
4569         if (calcIdParam != null)
4570         {
4571           if (recoverCalcIdParam(calcIdParam, af.viewport))
4572           {
4573           }
4574           else
4575           {
4576             warn("Couldn't recover parameters for "
4577                     + calcIdParam.getCalcId());
4578           }
4579         }
4580       }
4581     }
4582     af.setMenusFromViewport(af.viewport);
4583     af.setTitle(view.getTitle());
4584     // TODO: we don't need to do this if the viewport is aready visible.
4585     /*
4586      * Add the AlignFrame to the desktop (it may be 'gathered' later), unless it
4587      * has a 'cdna/protein complement' view, in which case save it in order to
4588      * populate a SplitFrame once all views have been read in.
4589      */
4590     String complementaryViewId = view.getComplementId();
4591     if (complementaryViewId == null)
4592     {
4593       Desktop.addInternalFrame(af, view.getTitle(), view.getWidth(),
4594               view.getHeight());
4595       // recompute any autoannotation
4596       af.alignPanel.updateAnnotation(false, true);
4597       reorderAutoannotation(af, al, autoAlan);
4598       af.alignPanel.alignmentChanged();
4599     }
4600     else
4601     {
4602       splitFrameCandidates.put(view, af);
4603     }
4604     return af;
4605   }
4606
4607   private ColourSchemeI constructAnnotationColour(
4608           AnnotationColours viewAnnColour, AlignFrame af, AlignmentI al,
4609           JalviewModelSequence jms, boolean checkGroupAnnColour)
4610   {
4611     boolean propagateAnnColour = false;
4612     ColourSchemeI cs = null;
4613     AlignmentI annAlignment = af != null ? af.viewport.getAlignment() : al;
4614     if (checkGroupAnnColour && al.getGroups() != null
4615             && al.getGroups().size() > 0)
4616     {
4617       // pre 2.8.1 behaviour
4618       // check to see if we should transfer annotation colours
4619       propagateAnnColour = true;
4620       for (jalview.datamodel.SequenceGroup sg : al.getGroups())
4621       {
4622         if (sg.cs instanceof AnnotationColourGradient)
4623         {
4624           propagateAnnColour = false;
4625         }
4626       }
4627     }
4628     // int find annotation
4629     if (annAlignment.getAlignmentAnnotation() != null)
4630     {
4631       for (int i = 0; i < annAlignment.getAlignmentAnnotation().length; i++)
4632       {
4633         if (annAlignment.getAlignmentAnnotation()[i].label
4634                 .equals(viewAnnColour.getAnnotation()))
4635         {
4636           if (annAlignment.getAlignmentAnnotation()[i].getThreshold() == null)
4637           {
4638             annAlignment.getAlignmentAnnotation()[i]
4639                     .setThreshold(new jalview.datamodel.GraphLine(
4640                             viewAnnColour.getThreshold(), "Threshold",
4641                             java.awt.Color.black)
4642
4643                     );
4644           }
4645
4646           if (viewAnnColour.getColourScheme().equals("None"))
4647           {
4648             cs = new AnnotationColourGradient(
4649                     annAlignment.getAlignmentAnnotation()[i],
4650                     new java.awt.Color(viewAnnColour.getMinColour()),
4651                     new java.awt.Color(viewAnnColour.getMaxColour()),
4652                     viewAnnColour.getAboveThreshold());
4653           }
4654           else if (viewAnnColour.getColourScheme().startsWith("ucs"))
4655           {
4656             cs = new AnnotationColourGradient(
4657                     annAlignment.getAlignmentAnnotation()[i],
4658                     getUserColourScheme(jms,
4659                             viewAnnColour.getColourScheme()),
4660                     viewAnnColour.getAboveThreshold());
4661           }
4662           else
4663           {
4664             cs = new AnnotationColourGradient(
4665                     annAlignment.getAlignmentAnnotation()[i],
4666                     ColourSchemeProperty.getColour(al,
4667                             viewAnnColour.getColourScheme()),
4668                     viewAnnColour.getAboveThreshold());
4669           }
4670           if (viewAnnColour.hasPerSequence())
4671           {
4672             ((AnnotationColourGradient) cs).setSeqAssociated(viewAnnColour
4673                     .isPerSequence());
4674           }
4675           if (viewAnnColour.hasPredefinedColours())
4676           {
4677             ((AnnotationColourGradient) cs)
4678                     .setPredefinedColours(viewAnnColour
4679                             .isPredefinedColours());
4680           }
4681           if (propagateAnnColour && al.getGroups() != null)
4682           {
4683             // Also use these settings for all the groups
4684             for (int g = 0; g < al.getGroups().size(); g++)
4685             {
4686               jalview.datamodel.SequenceGroup sg = al.getGroups().get(g);
4687
4688               if (sg.cs == null)
4689               {
4690                 continue;
4691               }
4692
4693               /*
4694                * if (viewAnnColour.getColourScheme().equals("None" )) { sg.cs =
4695                * new AnnotationColourGradient(
4696                * annAlignment.getAlignmentAnnotation()[i], new
4697                * java.awt.Color(viewAnnColour. getMinColour()), new
4698                * java.awt.Color(viewAnnColour. getMaxColour()),
4699                * viewAnnColour.getAboveThreshold()); } else
4700                */
4701               {
4702                 sg.cs = new AnnotationColourGradient(
4703                         annAlignment.getAlignmentAnnotation()[i], sg.cs,
4704                         viewAnnColour.getAboveThreshold());
4705                 if (cs instanceof AnnotationColourGradient)
4706                 {
4707                   if (viewAnnColour.hasPerSequence())
4708                   {
4709                     ((AnnotationColourGradient) cs)
4710                             .setSeqAssociated(viewAnnColour.isPerSequence());
4711                   }
4712                   if (viewAnnColour.hasPredefinedColours())
4713                   {
4714                     ((AnnotationColourGradient) cs)
4715                             .setPredefinedColours(viewAnnColour
4716                                     .isPredefinedColours());
4717                   }
4718                 }
4719               }
4720
4721             }
4722           }
4723
4724           break;
4725         }
4726
4727       }
4728     }
4729     return cs;
4730   }
4731
4732   private void reorderAutoannotation(AlignFrame af, AlignmentI al,
4733           List<JvAnnotRow> autoAlan)
4734   {
4735     // copy over visualization settings for autocalculated annotation in the
4736     // view
4737     if (al.getAlignmentAnnotation() != null)
4738     {
4739       /**
4740        * Kludge for magic autoannotation names (see JAL-811)
4741        */
4742       String[] magicNames = new String[] { "Consensus", "Quality",
4743           "Conservation" };
4744       JvAnnotRow nullAnnot = new JvAnnotRow(-1, null);
4745       Hashtable<String, JvAnnotRow> visan = new Hashtable<String, JvAnnotRow>();
4746       for (String nm : magicNames)
4747       {
4748         visan.put(nm, nullAnnot);
4749       }
4750       for (JvAnnotRow auan : autoAlan)
4751       {
4752         visan.put(auan.template.label
4753                 + (auan.template.getCalcId() == null ? "" : "\t"
4754                         + auan.template.getCalcId()), auan);
4755       }
4756       int hSize = al.getAlignmentAnnotation().length;
4757       List<JvAnnotRow> reorder = new ArrayList<JvAnnotRow>();
4758       // work through any autoCalculated annotation already on the view
4759       // removing it if it should be placed in a different location on the
4760       // annotation panel.
4761       List<String> remains = new ArrayList<String>(visan.keySet());
4762       for (int h = 0; h < hSize; h++)
4763       {
4764         jalview.datamodel.AlignmentAnnotation jalan = al
4765                 .getAlignmentAnnotation()[h];
4766         if (jalan.autoCalculated)
4767         {
4768           String k;
4769           JvAnnotRow valan = visan.get(k = jalan.label);
4770           if (jalan.getCalcId() != null)
4771           {
4772             valan = visan.get(k = jalan.label + "\t" + jalan.getCalcId());
4773           }
4774
4775           if (valan != null)
4776           {
4777             // delete the auto calculated row from the alignment
4778             al.deleteAnnotation(jalan, false);
4779             remains.remove(k);
4780             hSize--;
4781             h--;
4782             if (valan != nullAnnot)
4783             {
4784               if (jalan != valan.template)
4785               {
4786                 // newly created autoannotation row instance
4787                 // so keep a reference to the visible annotation row
4788                 // and copy over all relevant attributes
4789                 if (valan.template.graphHeight >= 0)
4790
4791                 {
4792                   jalan.graphHeight = valan.template.graphHeight;
4793                 }
4794                 jalan.visible = valan.template.visible;
4795               }
4796               reorder.add(new JvAnnotRow(valan.order, jalan));
4797             }
4798           }
4799         }
4800       }
4801       // Add any (possibly stale) autocalculated rows that were not appended to
4802       // the view during construction
4803       for (String other : remains)
4804       {
4805         JvAnnotRow othera = visan.get(other);
4806         if (othera != nullAnnot && othera.template.getCalcId() != null
4807                 && othera.template.getCalcId().length() > 0)
4808         {
4809           reorder.add(othera);
4810         }
4811       }
4812       // now put the automatic annotation in its correct place
4813       int s = 0, srt[] = new int[reorder.size()];
4814       JvAnnotRow[] rws = new JvAnnotRow[reorder.size()];
4815       for (JvAnnotRow jvar : reorder)
4816       {
4817         rws[s] = jvar;
4818         srt[s++] = jvar.order;
4819       }
4820       reorder.clear();
4821       jalview.util.QuickSort.sort(srt, rws);
4822       // and re-insert the annotation at its correct position
4823       for (JvAnnotRow jvar : rws)
4824       {
4825         al.addAnnotation(jvar.template, jvar.order);
4826       }
4827       af.alignPanel.adjustAnnotationHeight();
4828     }
4829   }
4830
4831   Hashtable skipList = null;
4832
4833   /**
4834    * TODO remove this method
4835    * 
4836    * @param view
4837    * @return AlignFrame bound to sequenceSetId from view, if one exists. private
4838    *         AlignFrame getSkippedFrame(Viewport view) { if (skipList==null) {
4839    *         throw new Error("Implementation Error. No skipList defined for this
4840    *         Jalview2XML instance."); } return (AlignFrame)
4841    *         skipList.get(view.getSequenceSetId()); }
4842    */
4843
4844   /**
4845    * Check if the Jalview view contained in object should be skipped or not.
4846    * 
4847    * @param object
4848    * @return true if view's sequenceSetId is a key in skipList
4849    */
4850   private boolean skipViewport(JalviewModel object)
4851   {
4852     if (skipList == null)
4853     {
4854       return false;
4855     }
4856     String id;
4857     if (skipList.containsKey(id = object.getJalviewModelSequence()
4858             .getViewport()[0].getSequenceSetId()))
4859     {
4860       if (Cache.log != null && Cache.log.isDebugEnabled())
4861       {
4862         Cache.log.debug("Skipping seuqence set id " + id);
4863       }
4864       return true;
4865     }
4866     return false;
4867   }
4868
4869   public void addToSkipList(AlignFrame af)
4870   {
4871     if (skipList == null)
4872     {
4873       skipList = new Hashtable();
4874     }
4875     skipList.put(af.getViewport().getSequenceSetId(), af);
4876   }
4877
4878   public void clearSkipList()
4879   {
4880     if (skipList != null)
4881     {
4882       skipList.clear();
4883       skipList = null;
4884     }
4885   }
4886
4887   private void recoverDatasetFor(SequenceSet vamsasSet, AlignmentI al,
4888           boolean ignoreUnrefed)
4889   {
4890     jalview.datamodel.AlignmentI ds = getDatasetFor(vamsasSet
4891             .getDatasetId());
4892     Vector dseqs = null;
4893     if (ds == null)
4894     {
4895       // create a list of new dataset sequences
4896       dseqs = new Vector();
4897     }
4898     for (int i = 0, iSize = vamsasSet.getSequenceCount(); i < iSize; i++)
4899     {
4900       Sequence vamsasSeq = vamsasSet.getSequence(i);
4901       ensureJalviewDatasetSequence(vamsasSeq, ds, dseqs, ignoreUnrefed);
4902     }
4903     // create a new dataset
4904     if (ds == null)
4905     {
4906       SequenceI[] dsseqs = new SequenceI[dseqs.size()];
4907       dseqs.copyInto(dsseqs);
4908       ds = new jalview.datamodel.Alignment(dsseqs);
4909       debug("Created new dataset " + vamsasSet.getDatasetId()
4910               + " for alignment " + System.identityHashCode(al));
4911       addDatasetRef(vamsasSet.getDatasetId(), ds);
4912     }
4913     // set the dataset for the newly imported alignment.
4914     if (al.getDataset() == null && !ignoreUnrefed)
4915     {
4916       al.setDataset(ds);
4917     }
4918   }
4919
4920   /**
4921    * 
4922    * @param vamsasSeq
4923    *          sequence definition to create/merge dataset sequence for
4924    * @param ds
4925    *          dataset alignment
4926    * @param dseqs
4927    *          vector to add new dataset sequence to
4928    */
4929   private void ensureJalviewDatasetSequence(Sequence vamsasSeq,
4930           AlignmentI ds, Vector dseqs, boolean ignoreUnrefed)
4931   {
4932     // JBP TODO: Check this is called for AlCodonFrames to support recovery of
4933     // xRef Codon Maps
4934     SequenceI sq = seqRefIds.get(vamsasSeq.getId());
4935     SequenceI dsq = null;
4936     if (sq != null && sq.getDatasetSequence() != null)
4937     {
4938       dsq = sq.getDatasetSequence();
4939     }
4940     if (sq == null && ignoreUnrefed)
4941     {
4942       return;
4943     }
4944     String sqid = vamsasSeq.getDsseqid();
4945     if (dsq == null)
4946     {
4947       // need to create or add a new dataset sequence reference to this sequence
4948       if (sqid != null)
4949       {
4950         dsq = seqRefIds.get(sqid);
4951       }
4952       // check again
4953       if (dsq == null)
4954       {
4955         // make a new dataset sequence
4956         dsq = sq.createDatasetSequence();
4957         if (sqid == null)
4958         {
4959           // make up a new dataset reference for this sequence
4960           sqid = seqHash(dsq);
4961         }
4962         dsq.setVamsasId(uniqueSetSuffix + sqid);
4963         seqRefIds.put(sqid, dsq);
4964         if (ds == null)
4965         {
4966           if (dseqs != null)
4967           {
4968             dseqs.addElement(dsq);
4969           }
4970         }
4971         else
4972         {
4973           ds.addSequence(dsq);
4974         }
4975       }
4976       else
4977       {
4978         if (sq != dsq)
4979         { // make this dataset sequence sq's dataset sequence
4980           sq.setDatasetSequence(dsq);
4981           // and update the current dataset alignment
4982           if (ds == null)
4983           {
4984             if (dseqs != null)
4985             {
4986               if (!dseqs.contains(dsq))
4987               {
4988                 dseqs.add(dsq);
4989               }
4990             }
4991             else
4992             {
4993               if (ds.findIndex(dsq) < 0)
4994               {
4995                 ds.addSequence(dsq);
4996               }
4997             }
4998           }
4999         }
5000       }
5001     }
5002     // TODO: refactor this as a merge dataset sequence function
5003     // now check that sq (the dataset sequence) sequence really is the union of
5004     // all references to it
5005     // boolean pre = sq.getStart() < dsq.getStart();
5006     // boolean post = sq.getEnd() > dsq.getEnd();
5007     // if (pre || post)
5008     if (sq != dsq)
5009     {
5010       // StringBuffer sb = new StringBuffer();
5011       String newres = jalview.analysis.AlignSeq.extractGaps(
5012               jalview.util.Comparison.GapChars, sq.getSequenceAsString());
5013       if (!newres.equalsIgnoreCase(dsq.getSequenceAsString())
5014               && newres.length() > dsq.getLength())
5015       {
5016         // Update with the longer sequence.
5017         synchronized (dsq)
5018         {
5019           /*
5020            * if (pre) { sb.insert(0, newres .substring(0, dsq.getStart() -
5021            * sq.getStart())); dsq.setStart(sq.getStart()); } if (post) {
5022            * sb.append(newres.substring(newres.length() - sq.getEnd() -
5023            * dsq.getEnd())); dsq.setEnd(sq.getEnd()); }
5024            */
5025           dsq.setSequence(newres);
5026         }
5027         // TODO: merges will never happen if we 'know' we have the real dataset
5028         // sequence - this should be detected when id==dssid
5029         System.err
5030                 .println("DEBUG Notice:  Merged dataset sequence (if you see this often, post at http://issues.jalview.org/browse/JAL-1474)"); // ("
5031         // + (pre ? "prepended" : "") + " "
5032         // + (post ? "appended" : ""));
5033       }
5034     }
5035   }
5036
5037   /*
5038    * TODO use AlignmentI here and in related methods - needs
5039    * AlignmentI.getDataset() changed to return AlignmentI instead of Alignment
5040    */
5041   Hashtable<String, AlignmentI> datasetIds = null;
5042
5043   IdentityHashMap<AlignmentI, String> dataset2Ids = null;
5044
5045   private AlignmentI getDatasetFor(String datasetId)
5046   {
5047     if (datasetIds == null)
5048     {
5049       datasetIds = new Hashtable<String, AlignmentI>();
5050       return null;
5051     }
5052     if (datasetIds.containsKey(datasetId))
5053     {
5054       return datasetIds.get(datasetId);
5055     }
5056     return null;
5057   }
5058
5059   private void addDatasetRef(String datasetId, AlignmentI dataset)
5060   {
5061     if (datasetIds == null)
5062     {
5063       datasetIds = new Hashtable<String, AlignmentI>();
5064     }
5065     datasetIds.put(datasetId, dataset);
5066   }
5067
5068   /**
5069    * make a new dataset ID for this jalview dataset alignment
5070    * 
5071    * @param dataset
5072    * @return
5073    */
5074   private String getDatasetIdRef(AlignmentI dataset)
5075   {
5076     if (dataset.getDataset() != null)
5077     {
5078       warn("Serious issue!  Dataset Object passed to getDatasetIdRef is not a Jalview DATASET alignment...");
5079     }
5080     String datasetId = makeHashCode(dataset, null);
5081     if (datasetId == null)
5082     {
5083       // make a new datasetId and record it
5084       if (dataset2Ids == null)
5085       {
5086         dataset2Ids = new IdentityHashMap<AlignmentI, String>();
5087       }
5088       else
5089       {
5090         datasetId = dataset2Ids.get(dataset);
5091       }
5092       if (datasetId == null)
5093       {
5094         datasetId = "ds" + dataset2Ids.size() + 1;
5095         dataset2Ids.put(dataset, datasetId);
5096       }
5097     }
5098     return datasetId;
5099   }
5100
5101   private void addDBRefs(SequenceI datasetSequence, Sequence sequence)
5102   {
5103     for (int d = 0; d < sequence.getDBRefCount(); d++)
5104     {
5105       DBRef dr = sequence.getDBRef(d);
5106       jalview.datamodel.DBRefEntry entry = new jalview.datamodel.DBRefEntry(
5107               sequence.getDBRef(d).getSource(), sequence.getDBRef(d)
5108                       .getVersion(), sequence.getDBRef(d).getAccessionId());
5109       if (dr.getMapping() != null)
5110       {
5111         entry.setMap(addMapping(dr.getMapping()));
5112       }
5113       datasetSequence.addDBRef(entry);
5114     }
5115   }
5116
5117   private jalview.datamodel.Mapping addMapping(Mapping m)
5118   {
5119     SequenceI dsto = null;
5120     // Mapping m = dr.getMapping();
5121     int fr[] = new int[m.getMapListFromCount() * 2];
5122     Enumeration f = m.enumerateMapListFrom();
5123     for (int _i = 0; f.hasMoreElements(); _i += 2)
5124     {
5125       MapListFrom mf = (MapListFrom) f.nextElement();
5126       fr[_i] = mf.getStart();
5127       fr[_i + 1] = mf.getEnd();
5128     }
5129     int fto[] = new int[m.getMapListToCount() * 2];
5130     f = m.enumerateMapListTo();
5131     for (int _i = 0; f.hasMoreElements(); _i += 2)
5132     {
5133       MapListTo mf = (MapListTo) f.nextElement();
5134       fto[_i] = mf.getStart();
5135       fto[_i + 1] = mf.getEnd();
5136     }
5137     jalview.datamodel.Mapping jmap = new jalview.datamodel.Mapping(dsto,
5138             fr, fto, (int) m.getMapFromUnit(), (int) m.getMapToUnit());
5139     if (m.getMappingChoice() != null)
5140     {
5141       MappingChoice mc = m.getMappingChoice();
5142       if (mc.getDseqFor() != null)
5143       {
5144         String dsfor = "" + mc.getDseqFor();
5145         if (seqRefIds.containsKey(dsfor))
5146         {
5147           /**
5148            * recover from hash
5149            */
5150           jmap.setTo(seqRefIds.get(dsfor));
5151         }
5152         else
5153         {
5154           frefedSequence.add(newMappingRef(dsfor, jmap));
5155         }
5156       }
5157       else
5158       {
5159         /**
5160          * local sequence definition
5161          */
5162         Sequence ms = mc.getSequence();
5163         SequenceI djs = null;
5164         String sqid = ms.getDsseqid();
5165         if (sqid != null && sqid.length() > 0)
5166         {
5167           /*
5168            * recover dataset sequence
5169            */
5170           djs = seqRefIds.get(sqid);
5171         }
5172         else
5173         {
5174           System.err
5175                   .println("Warning - making up dataset sequence id for DbRef sequence map reference");
5176           sqid = ((Object) ms).toString(); // make up a new hascode for
5177           // undefined dataset sequence hash
5178           // (unlikely to happen)
5179         }
5180
5181         if (djs == null)
5182         {
5183           /**
5184            * make a new dataset sequence and add it to refIds hash
5185            */
5186           djs = new jalview.datamodel.Sequence(ms.getName(),
5187                   ms.getSequence());
5188           djs.setStart(jmap.getMap().getToLowest());
5189           djs.setEnd(jmap.getMap().getToHighest());
5190           djs.setVamsasId(uniqueSetSuffix + sqid);
5191           jmap.setTo(djs);
5192           incompleteSeqs.put(sqid, djs);
5193           seqRefIds.put(sqid, djs);
5194
5195         }
5196         jalview.bin.Cache.log.debug("about to recurse on addDBRefs.");
5197         addDBRefs(djs, ms);
5198
5199       }
5200     }
5201     return (jmap);
5202
5203   }
5204
5205   public jalview.gui.AlignmentPanel copyAlignPanel(AlignmentPanel ap,
5206           boolean keepSeqRefs)
5207   {
5208     initSeqRefs();
5209     JalviewModel jm = saveState(ap, null, null, null);
5210
5211     if (!keepSeqRefs)
5212     {
5213       clearSeqRefs();
5214       jm.getJalviewModelSequence().getViewport(0).setSequenceSetId(null);
5215     }
5216     else
5217     {
5218       uniqueSetSuffix = "";
5219       jm.getJalviewModelSequence().getViewport(0).setId(null); // we don't
5220       // overwrite the
5221       // view we just
5222       // copied
5223     }
5224     if (this.frefedSequence == null)
5225     {
5226       frefedSequence = new Vector();
5227     }
5228
5229     viewportsAdded.clear();
5230
5231     AlignFrame af = loadFromObject(jm, null, false, null);
5232     af.alignPanels.clear();
5233     af.closeMenuItem_actionPerformed(true);
5234
5235     /*
5236      * if(ap.av.getAlignment().getAlignmentAnnotation()!=null) { for(int i=0;
5237      * i<ap.av.getAlignment().getAlignmentAnnotation().length; i++) {
5238      * if(!ap.av.getAlignment().getAlignmentAnnotation()[i].autoCalculated) {
5239      * af.alignPanel.av.getAlignment().getAlignmentAnnotation()[i] =
5240      * ap.av.getAlignment().getAlignmentAnnotation()[i]; } } }
5241      */
5242
5243     return af.alignPanel;
5244   }
5245
5246   /**
5247    * flag indicating if hashtables should be cleared on finalization TODO this
5248    * flag may not be necessary
5249    */
5250   private final boolean _cleartables = true;
5251
5252   private Hashtable jvids2vobj;
5253
5254   /*
5255    * (non-Javadoc)
5256    * 
5257    * @see java.lang.Object#finalize()
5258    */
5259   @Override
5260   protected void finalize() throws Throwable
5261   {
5262     // really make sure we have no buried refs left.
5263     if (_cleartables)
5264     {
5265       clearSeqRefs();
5266     }
5267     this.seqRefIds = null;
5268     this.seqsToIds = null;
5269     super.finalize();
5270   }
5271
5272   private void warn(String msg)
5273   {
5274     warn(msg, null);
5275   }
5276
5277   private void warn(String msg, Exception e)
5278   {
5279     if (Cache.log != null)
5280     {
5281       if (e != null)
5282       {
5283         Cache.log.warn(msg, e);
5284       }
5285       else
5286       {
5287         Cache.log.warn(msg);
5288       }
5289     }
5290     else
5291     {
5292       System.err.println("Warning: " + msg);
5293       if (e != null)
5294       {
5295         e.printStackTrace();
5296       }
5297     }
5298   }
5299
5300   private void debug(String string)
5301   {
5302     debug(string, null);
5303   }
5304
5305   private void debug(String msg, Exception e)
5306   {
5307     if (Cache.log != null)
5308     {
5309       if (e != null)
5310       {
5311         Cache.log.debug(msg, e);
5312       }
5313       else
5314       {
5315         Cache.log.debug(msg);
5316       }
5317     }
5318     else
5319     {
5320       System.err.println("Warning: " + msg);
5321       if (e != null)
5322       {
5323         e.printStackTrace();
5324       }
5325     }
5326   }
5327
5328   /**
5329    * set the object to ID mapping tables used to write/recover objects and XML
5330    * ID strings for the jalview project. If external tables are provided then
5331    * finalize and clearSeqRefs will not clear the tables when the Jalview2XML
5332    * object goes out of scope. - also populates the datasetIds hashtable with
5333    * alignment objects containing dataset sequences
5334    * 
5335    * @param vobj2jv
5336    *          Map from ID strings to jalview datamodel
5337    * @param jv2vobj
5338    *          Map from jalview datamodel to ID strings
5339    * 
5340    * 
5341    */
5342   public void setObjectMappingTables(Hashtable vobj2jv,
5343           IdentityHashMap jv2vobj)
5344   {
5345     this.jv2vobj = jv2vobj;
5346     this.vobj2jv = vobj2jv;
5347     Iterator ds = jv2vobj.keySet().iterator();
5348     String id;
5349     while (ds.hasNext())
5350     {
5351       Object jvobj = ds.next();
5352       id = jv2vobj.get(jvobj).toString();
5353       if (jvobj instanceof jalview.datamodel.Alignment)
5354       {
5355         if (((jalview.datamodel.Alignment) jvobj).getDataset() == null)
5356         {
5357           addDatasetRef(id, (jalview.datamodel.Alignment) jvobj);
5358         }
5359       }
5360       else if (jvobj instanceof jalview.datamodel.Sequence)
5361       {
5362         // register sequence object so the XML parser can recover it.
5363         if (seqRefIds == null)
5364         {
5365           seqRefIds = new HashMap<String, SequenceI>();
5366         }
5367         if (seqsToIds == null)
5368         {
5369           seqsToIds = new IdentityHashMap<SequenceI, String>();
5370         }
5371         seqRefIds.put(jv2vobj.get(jvobj).toString(), (SequenceI) jvobj);
5372         seqsToIds.put((SequenceI) jvobj, id);
5373       }
5374       else if (jvobj instanceof jalview.datamodel.AlignmentAnnotation)
5375       {
5376         String anid;
5377         AlignmentAnnotation jvann = (AlignmentAnnotation) jvobj;
5378         annotationIds.put(anid = jv2vobj.get(jvobj).toString(), jvann);
5379         if (jvann.annotationId == null)
5380         {
5381           jvann.annotationId = anid;
5382         }
5383         if (!jvann.annotationId.equals(anid))
5384         {
5385           // TODO verify that this is the correct behaviour
5386           this.warn("Overriding Annotation ID for " + anid
5387                   + " from different id : " + jvann.annotationId);
5388           jvann.annotationId = anid;
5389         }
5390       }
5391       else if (jvobj instanceof String)
5392       {
5393         if (jvids2vobj == null)
5394         {
5395           jvids2vobj = new Hashtable();
5396           jvids2vobj.put(jvobj, jv2vobj.get(jvobj).toString());
5397         }
5398       }
5399       else
5400       {
5401         Cache.log.debug("Ignoring " + jvobj.getClass() + " (ID = " + id);
5402       }
5403     }
5404   }
5405
5406   /**
5407    * set the uniqueSetSuffix used to prefix/suffix object IDs for jalview
5408    * objects created from the project archive. If string is null (default for
5409    * construction) then suffix will be set automatically.
5410    * 
5411    * @param string
5412    */
5413   public void setUniqueSetSuffix(String string)
5414   {
5415     uniqueSetSuffix = string;
5416
5417   }
5418
5419   /**
5420    * uses skipList2 as the skipList for skipping views on sequence sets
5421    * associated with keys in the skipList
5422    * 
5423    * @param skipList2
5424    */
5425   public void setSkipList(Hashtable skipList2)
5426   {
5427     skipList = skipList2;
5428   }
5429
5430   /**
5431    * Reads the jar entry of given name and returns its contents, or null if the
5432    * entry is not found.
5433    * 
5434    * @param jprovider
5435    * @param jarEntryName
5436    * @return
5437    */
5438   protected String readJarEntry(jarInputStreamProvider jprovider,
5439           String jarEntryName)
5440   {
5441     String result = null;
5442     BufferedReader in = null;
5443
5444     try
5445     {
5446       /*
5447        * Reopen the jar input stream and traverse its entries to find a matching
5448        * name
5449        */
5450       JarInputStream jin = jprovider.getJarInputStream();
5451       JarEntry entry = null;
5452       do
5453       {
5454         entry = jin.getNextJarEntry();
5455       } while (entry != null && !entry.getName().equals(jarEntryName));
5456
5457       if (entry != null)
5458       {
5459         StringBuilder out = new StringBuilder(256);
5460         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
5461         String data;
5462
5463         while ((data = in.readLine()) != null)
5464         {
5465           out.append(data);
5466         }
5467         result = out.toString();
5468       }
5469       else
5470       {
5471         warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
5472       }
5473     } catch (Exception ex)
5474     {
5475       ex.printStackTrace();
5476     } finally
5477     {
5478       if (in != null)
5479       {
5480         try
5481         {
5482           in.close();
5483         } catch (IOException e)
5484         {
5485           // ignore
5486         }
5487       }
5488     }
5489
5490     return result;
5491   }
5492
5493   /**
5494    * Returns an incrementing counter (0, 1, 2...)
5495    * 
5496    * @return
5497    */
5498   private synchronized int nextCounter()
5499   {
5500     return counter++;
5501   }
5502 }