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