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