JAL-2196 refactor PDBEntry.getProperty,setProperty,getProperties
[jalview.git] / src / jalview / gui / Jalview2XML.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.gui;
22
23 import jalview.api.FeatureColourI;
24 import jalview.api.ViewStyleI;
25 import jalview.api.structures.JalviewStructureDisplayI;
26 import jalview.bin.Cache;
27 import jalview.datamodel.AlignedCodonFrame;
28 import jalview.datamodel.Alignment;
29 import jalview.datamodel.AlignmentAnnotation;
30 import jalview.datamodel.AlignmentI;
31 import jalview.datamodel.PDBEntry;
32 import jalview.datamodel.RnaViewerModel;
33 import jalview.datamodel.SequenceGroup;
34 import jalview.datamodel.SequenceI;
35 import jalview.datamodel.StructureViewerModel;
36 import jalview.datamodel.StructureViewerModel.StructureData;
37 import jalview.ext.varna.RnaModel;
38 import jalview.gui.StructureViewer.ViewerType;
39 import jalview.schemabinding.version2.AlcodMap;
40 import jalview.schemabinding.version2.AlcodonFrame;
41 import jalview.schemabinding.version2.Annotation;
42 import jalview.schemabinding.version2.AnnotationColours;
43 import jalview.schemabinding.version2.AnnotationElement;
44 import jalview.schemabinding.version2.CalcIdParam;
45 import jalview.schemabinding.version2.DBRef;
46 import jalview.schemabinding.version2.Features;
47 import jalview.schemabinding.version2.Group;
48 import jalview.schemabinding.version2.HiddenColumns;
49 import jalview.schemabinding.version2.JGroup;
50 import jalview.schemabinding.version2.JSeq;
51 import jalview.schemabinding.version2.JalviewModel;
52 import jalview.schemabinding.version2.JalviewModelSequence;
53 import jalview.schemabinding.version2.MapListFrom;
54 import jalview.schemabinding.version2.MapListTo;
55 import jalview.schemabinding.version2.Mapping;
56 import jalview.schemabinding.version2.MappingChoice;
57 import jalview.schemabinding.version2.OtherData;
58 import jalview.schemabinding.version2.PdbentryItem;
59 import jalview.schemabinding.version2.Pdbids;
60 import jalview.schemabinding.version2.Property;
61 import jalview.schemabinding.version2.RnaViewer;
62 import jalview.schemabinding.version2.SecondaryStructure;
63 import jalview.schemabinding.version2.Sequence;
64 import jalview.schemabinding.version2.SequenceSet;
65 import jalview.schemabinding.version2.SequenceSetProperties;
66 import jalview.schemabinding.version2.Setting;
67 import jalview.schemabinding.version2.StructureState;
68 import jalview.schemabinding.version2.ThresholdLine;
69 import jalview.schemabinding.version2.Tree;
70 import jalview.schemabinding.version2.UserColours;
71 import jalview.schemabinding.version2.Viewport;
72 import jalview.schemes.AnnotationColourGradient;
73 import jalview.schemes.ColourSchemeI;
74 import jalview.schemes.ColourSchemeProperty;
75 import jalview.schemes.FeatureColour;
76 import jalview.schemes.ResidueColourScheme;
77 import jalview.schemes.ResidueProperties;
78 import jalview.schemes.UserColourScheme;
79 import jalview.structure.StructureSelectionManager;
80 import jalview.structures.models.AAStructureBindingModel;
81 import jalview.util.MessageManager;
82 import jalview.util.Platform;
83 import jalview.util.StringUtils;
84 import jalview.util.jarInputStreamProvider;
85 import jalview.viewmodel.AlignmentViewport;
86 import jalview.viewmodel.seqfeatures.FeatureRendererSettings;
87 import jalview.viewmodel.seqfeatures.FeaturesDisplayed;
88 import jalview.ws.jws2.Jws2Discoverer;
89 import jalview.ws.jws2.dm.AAConSettings;
90 import jalview.ws.jws2.jabaws2.Jws2Instance;
91 import jalview.ws.params.ArgumentI;
92 import jalview.ws.params.AutoCalcSetting;
93 import jalview.ws.params.WsParamSetI;
94
95 import java.awt.Color;
96 import java.awt.Rectangle;
97 import java.io.BufferedReader;
98 import java.io.DataInputStream;
99 import java.io.DataOutputStream;
100 import java.io.File;
101 import java.io.FileInputStream;
102 import java.io.FileOutputStream;
103 import java.io.IOException;
104 import java.io.InputStreamReader;
105 import java.io.OutputStreamWriter;
106 import java.io.PrintWriter;
107 import java.lang.reflect.InvocationTargetException;
108 import java.net.MalformedURLException;
109 import java.net.URL;
110 import java.util.ArrayList;
111 import java.util.Arrays;
112 import java.util.Enumeration;
113 import java.util.HashMap;
114 import java.util.HashSet;
115 import java.util.Hashtable;
116 import java.util.IdentityHashMap;
117 import java.util.Iterator;
118 import java.util.LinkedHashMap;
119 import java.util.List;
120 import java.util.Map;
121 import java.util.Map.Entry;
122 import java.util.Set;
123 import java.util.Vector;
124 import java.util.jar.JarEntry;
125 import java.util.jar.JarInputStream;
126 import java.util.jar.JarOutputStream;
127
128 import javax.swing.JInternalFrame;
129 import javax.swing.JOptionPane;
130 import javax.swing.SwingUtilities;
131
132 import org.exolab.castor.xml.Marshaller;
133 import org.exolab.castor.xml.Unmarshaller;
134
135 /**
136  * Write out the current jalview desktop state as a Jalview XML stream.
137  * 
138  * Note: the vamsas objects referred to here are primitive versions of the
139  * VAMSAS project schema elements - they are not the same and most likely never
140  * will be :)
141  * 
142  * @author $author$
143  * @version $Revision: 1.134 $
144  */
145 public class Jalview2XML
146 {
147   private static final String VIEWER_PREFIX = "viewer_";
148
149   private static final String RNA_PREFIX = "rna_";
150
151   private static final String UTF_8 = "UTF-8";
152
153   // use this with nextCounter() to make unique names for entities
154   private int counter = 0;
155
156   /*
157    * SequenceI reference -> XML ID string in jalview XML. Populated as XML reps
158    * of sequence objects are created.
159    */
160   IdentityHashMap<SequenceI, String> seqsToIds = null;
161
162   /**
163    * jalview XML Sequence ID to jalview sequence object reference (both dataset
164    * and alignment sequences. Populated as XML reps of sequence objects are
165    * created.)
166    */
167   Map<String, SequenceI> seqRefIds = null;
168
169   Map<String, SequenceI> incompleteSeqs = null;
170
171   List<SeqFref> frefedSequence = null;
172
173   boolean raiseGUI = true; // whether errors are raised in dialog boxes or not
174
175   /*
176    * Map of reconstructed AlignFrame objects that appear to have come from
177    * SplitFrame objects (have a dna/protein complement view).
178    */
179   private Map<Viewport, AlignFrame> splitFrameCandidates = new HashMap<Viewport, AlignFrame>();
180
181   /*
182    * Map from displayed rna structure models to their saved session state jar
183    * entry names
184    */
185   private Map<RnaModel, String> rnaSessions = new HashMap<RnaModel, String>();
186
187   /**
188    * create/return unique hash string for sq
189    * 
190    * @param sq
191    * @return new or existing unique string for sq
192    */
193   String seqHash(SequenceI sq)
194   {
195     if (seqsToIds == null)
196     {
197       initSeqRefs();
198     }
199     if (seqsToIds.containsKey(sq))
200     {
201       return seqsToIds.get(sq);
202     }
203     else
204     {
205       // create sequential key
206       String key = "sq" + (seqsToIds.size() + 1);
207       key = makeHashCode(sq, key); // check we don't have an external reference
208       // for it already.
209       seqsToIds.put(sq, key);
210       return key;
211     }
212   }
213
214   void clearSeqRefs()
215   {
216     if (_cleartables)
217     {
218       if (seqRefIds != null)
219       {
220         seqRefIds.clear();
221       }
222       if (seqsToIds != null)
223       {
224         seqsToIds.clear();
225       }
226       if (incompleteSeqs != null)
227       {
228         incompleteSeqs.clear();
229       }
230       // seqRefIds = null;
231       // seqsToIds = null;
232     }
233     else
234     {
235       // do nothing
236       warn("clearSeqRefs called when _cleartables was not set. Doing nothing.");
237       // seqRefIds = new Hashtable();
238       // seqsToIds = new IdentityHashMap();
239     }
240   }
241
242   void initSeqRefs()
243   {
244     if (seqsToIds == null)
245     {
246       seqsToIds = new IdentityHashMap<SequenceI, String>();
247     }
248     if (seqRefIds == null)
249     {
250       seqRefIds = new HashMap<String, SequenceI>();
251     }
252     if (incompleteSeqs == null)
253     {
254       incompleteSeqs = new HashMap<String, SequenceI>();
255     }
256     if (frefedSequence == null)
257     {
258       frefedSequence = new ArrayList<SeqFref>();
259     }
260   }
261
262   public Jalview2XML()
263   {
264   }
265
266   public Jalview2XML(boolean raiseGUI)
267   {
268     this.raiseGUI = raiseGUI;
269   }
270
271   /**
272    * base class for resolving forward references to sequences by their ID
273    * 
274    * @author jprocter
275    *
276    */
277   abstract class SeqFref
278   {
279     String sref;
280
281     String type;
282
283     public SeqFref(String _sref, String type)
284     {
285       sref = _sref;
286       this.type = type;
287     }
288
289     public String getSref()
290     {
291       return sref;
292     }
293
294     public SequenceI getSrefSeq()
295     {
296       return seqRefIds.get(sref);
297     }
298
299     public boolean isResolvable()
300     {
301       return seqRefIds.get(sref) != null;
302     }
303
304     public SequenceI getSrefDatasetSeq()
305     {
306       SequenceI sq = seqRefIds.get(sref);
307       if (sq != null)
308       {
309         while (sq.getDatasetSequence() != null)
310         {
311           sq = sq.getDatasetSequence();
312         }
313       }
314       return sq;
315     }
316     /**
317      * @return true if the forward reference was fully resolved
318      */
319     abstract boolean resolve();
320
321     @Override
322     public String toString()
323     {
324       return type + " reference to " + sref;
325     }
326   }
327
328   /**
329    * create forward reference for a mapping
330    * 
331    * @param sref
332    * @param _jmap
333    * @return
334    */
335   public SeqFref newMappingRef(final String sref,
336           final jalview.datamodel.Mapping _jmap)
337   {
338     SeqFref fref = new SeqFref(sref, "Mapping")
339     {
340       public jalview.datamodel.Mapping jmap = _jmap;
341
342       @Override
343       boolean resolve()
344       {
345         SequenceI seq = getSrefDatasetSeq();
346         if (seq == null)
347         {
348           return false;
349         }
350         jmap.setTo(seq);
351         return true;
352       }
353     };
354     return fref;
355   }
356
357   public SeqFref newAlcodMapRef(final String sref,
358           final AlignedCodonFrame _cf, final jalview.datamodel.Mapping _jmap)
359   {
360
361     SeqFref fref = new SeqFref(sref, "Codon Frame")
362     {
363       AlignedCodonFrame cf = _cf;
364
365       public jalview.datamodel.Mapping mp = _jmap;
366
367       @Override
368       public boolean isResolvable()
369       {
370         return super.isResolvable() && mp.getTo() != null;
371       };
372
373       @Override
374       boolean resolve()
375       {
376         SequenceI seq = getSrefDatasetSeq();
377         if (seq == null)
378         {
379           return false;
380         }
381         cf.addMap(seq, mp.getTo(), mp.getMap());
382         return true;
383       }
384     };
385     return fref;
386   }
387
388   public void resolveFrefedSequences()
389   {
390     Iterator<SeqFref> nextFref=frefedSequence.iterator();
391     int toresolve=frefedSequence.size();
392     int unresolved=0,failedtoresolve=0;
393     while (nextFref.hasNext()) {
394       SeqFref ref = nextFref.next();
395       if (ref.isResolvable())
396       {
397         try {
398           if (ref.resolve())
399           {
400             nextFref.remove();
401           } else {
402             failedtoresolve++;
403           }
404         } catch (Exception x) {
405           System.err.println("IMPLEMENTATION ERROR: Failed to resolve forward reference for sequence "+ref.getSref());
406           x.printStackTrace();
407           failedtoresolve++;
408         } 
409       } else {
410         unresolved++;
411       }
412     }
413     if (unresolved>0)
414     {
415       System.err.println("Jalview Project Import: There were " + unresolved
416               + " forward references left unresolved on the stack.");
417     }
418     if (failedtoresolve>0)
419     {
420       System.err.println("SERIOUS! " + failedtoresolve
421               + " resolvable forward references failed to resolve.");
422     }
423     if (incompleteSeqs != null && incompleteSeqs.size() > 0)
424     {
425       System.err.println("Jalview Project Import: There are "
426               + incompleteSeqs.size()
427               + " sequences which may have incomplete metadata.");
428       if (incompleteSeqs.size() < 10)
429       {
430         for (SequenceI s : incompleteSeqs.values())
431         {
432           System.err.println(s.toString());
433         }
434       }
435       else
436       {
437         System.err
438                 .println("Too many to report. Skipping output of incomplete sequences.");
439       }
440     }
441   }
442
443   /**
444    * This maintains a map of viewports, the key being the seqSetId. Important to
445    * set historyItem and redoList for multiple views
446    */
447   Map<String, AlignViewport> viewportsAdded = new HashMap<String, AlignViewport>();
448
449   Map<String, AlignmentAnnotation> annotationIds = new HashMap<String, AlignmentAnnotation>();
450
451   String uniqueSetSuffix = "";
452
453   /**
454    * List of pdbfiles added to Jar
455    */
456   List<String> pdbfiles = null;
457
458   // SAVES SEVERAL ALIGNMENT WINDOWS TO SAME JARFILE
459   public void saveState(File statefile)
460   {
461     FileOutputStream fos = null;
462     try
463     {
464       fos = new FileOutputStream(statefile);
465       JarOutputStream jout = new JarOutputStream(fos);
466       saveState(jout);
467
468     } catch (Exception e)
469     {
470       // TODO: inform user of the problem - they need to know if their data was
471       // not saved !
472       if (errorMessage == null)
473       {
474         errorMessage = "Couldn't write Jalview Archive to output file '"
475                 + statefile + "' - See console error log for details";
476       }
477       else
478       {
479         errorMessage += "(output file was '" + statefile + "')";
480       }
481       e.printStackTrace();
482     } finally
483     {
484       if (fos != null)
485       {
486         try
487         {
488           fos.close();
489         } catch (IOException e)
490         {
491           // ignore
492         }
493       }
494     }
495     reportErrors();
496   }
497
498   /**
499    * Writes a jalview project archive to the given Jar output stream.
500    * 
501    * @param jout
502    */
503   public void saveState(JarOutputStream jout)
504   {
505     AlignFrame[] frames = Desktop.getAlignFrames();
506
507     if (frames == null)
508     {
509       return;
510     }
511     saveAllFrames(Arrays.asList(frames), jout);
512   }
513
514   /**
515    * core method for storing state for a set of AlignFrames.
516    * 
517    * @param frames
518    *          - frames involving all data to be exported (including containing
519    *          splitframes)
520    * @param jout
521    *          - project output stream
522    */
523   private void saveAllFrames(List<AlignFrame> frames, JarOutputStream jout)
524   {
525     Hashtable<String, AlignFrame> dsses = new Hashtable<String, AlignFrame>();
526
527     /*
528      * ensure cached data is clear before starting
529      */
530     // todo tidy up seqRefIds, seqsToIds initialisation / reset
531     rnaSessions.clear();
532     splitFrameCandidates.clear();
533
534     try
535     {
536
537       // NOTE UTF-8 MUST BE USED FOR WRITING UNICODE CHARS
538       // //////////////////////////////////////////////////
539
540       List<String> shortNames = new ArrayList<String>();
541       List<String> viewIds = new ArrayList<String>();
542
543       // REVERSE ORDER
544       for (int i = frames.size() - 1; i > -1; i--)
545       {
546         AlignFrame af = frames.get(i);
547         // skip ?
548         if (skipList != null
549                 && skipList
550                         .containsKey(af.getViewport().getSequenceSetId()))
551         {
552           continue;
553         }
554
555         String shortName = makeFilename(af, shortNames);
556
557         int ap, apSize = af.alignPanels.size();
558
559         for (ap = 0; ap < apSize; ap++)
560         {
561           AlignmentPanel apanel = af.alignPanels.get(ap);
562           String fileName = apSize == 1 ? shortName : ap + shortName;
563           if (!fileName.endsWith(".xml"))
564           {
565             fileName = fileName + ".xml";
566           }
567
568           saveState(apanel, fileName, jout, viewIds);
569
570           String dssid = getDatasetIdRef(af.getViewport().getAlignment()
571                   .getDataset());
572           if (!dsses.containsKey(dssid))
573           {
574             dsses.put(dssid, af);
575           }
576         }
577       }
578
579       writeDatasetFor(dsses, "" + jout.hashCode() + " " + uniqueSetSuffix,
580               jout);
581
582       try
583       {
584         jout.flush();
585       } catch (Exception foo)
586       {
587       }
588       ;
589       jout.close();
590     } catch (Exception ex)
591     {
592       // TODO: inform user of the problem - they need to know if their data was
593       // not saved !
594       if (errorMessage == null)
595       {
596         errorMessage = "Couldn't write Jalview Archive - see error output for details";
597       }
598       ex.printStackTrace();
599     }
600   }
601
602   /**
603    * Generates a distinct file name, based on the title of the AlignFrame, by
604    * appending _n for increasing n until an unused name is generated. The new
605    * name (without its extension) is added to the list.
606    * 
607    * @param af
608    * @param namesUsed
609    * @return the generated name, with .xml extension
610    */
611   protected String makeFilename(AlignFrame af, List<String> namesUsed)
612   {
613     String shortName = af.getTitle();
614
615     if (shortName.indexOf(File.separatorChar) > -1)
616     {
617       shortName = shortName.substring(shortName
618               .lastIndexOf(File.separatorChar) + 1);
619     }
620
621     int count = 1;
622
623     while (namesUsed.contains(shortName))
624     {
625       if (shortName.endsWith("_" + (count - 1)))
626       {
627         shortName = shortName.substring(0, shortName.lastIndexOf("_"));
628       }
629
630       shortName = shortName.concat("_" + count);
631       count++;
632     }
633
634     namesUsed.add(shortName);
635
636     if (!shortName.endsWith(".xml"))
637     {
638       shortName = shortName + ".xml";
639     }
640     return shortName;
641   }
642
643   // USE THIS METHOD TO SAVE A SINGLE ALIGNMENT WINDOW
644   public boolean saveAlignment(AlignFrame af, String jarFile,
645           String fileName)
646   {
647     try
648     {
649       FileOutputStream fos = new FileOutputStream(jarFile);
650       JarOutputStream jout = new JarOutputStream(fos);
651       List<AlignFrame> frames = new ArrayList<AlignFrame>();
652
653       // resolve splitframes
654       if (af.getViewport().getCodingComplement() != null)
655       {
656         frames = ((SplitFrame) af.getSplitViewContainer()).getAlignFrames();
657       }
658       else
659       {
660         frames.add(af);
661       }
662       saveAllFrames(frames, jout);
663       try
664       {
665         jout.flush();
666       } catch (Exception foo)
667       {
668       }
669       ;
670       jout.close();
671       return true;
672     } catch (Exception ex)
673     {
674       errorMessage = "Couldn't Write alignment view to Jalview Archive - see error output for details";
675       ex.printStackTrace();
676       return false;
677     }
678   }
679
680   private void writeDatasetFor(Hashtable<String, AlignFrame> dsses,
681           String fileName, JarOutputStream jout)
682   {
683
684     for (String dssids : dsses.keySet())
685     {
686       AlignFrame _af = dsses.get(dssids);
687       String jfileName = fileName + " Dataset for " + _af.getTitle();
688       if (!jfileName.endsWith(".xml"))
689       {
690         jfileName = jfileName + ".xml";
691       }
692       saveState(_af.alignPanel, jfileName, true, jout, null);
693     }
694   }
695
696   /**
697    * create a JalviewModel from an alignment view and marshall it to a
698    * JarOutputStream
699    * 
700    * @param ap
701    *          panel to create jalview model for
702    * @param fileName
703    *          name of alignment panel written to output stream
704    * @param jout
705    *          jar output stream
706    * @param viewIds
707    * @param out
708    *          jar entry name
709    */
710   public JalviewModel saveState(AlignmentPanel ap, String fileName,
711           JarOutputStream jout, List<String> viewIds)
712   {
713     return saveState(ap, fileName, false, jout, viewIds);
714   }
715
716   /**
717    * create a JalviewModel from an alignment view and marshall it to a
718    * JarOutputStream
719    * 
720    * @param ap
721    *          panel to create jalview model for
722    * @param fileName
723    *          name of alignment panel written to output stream
724    * @param storeDS
725    *          when true, only write the dataset for the alignment, not the data
726    *          associated with the view.
727    * @param jout
728    *          jar output stream
729    * @param out
730    *          jar entry name
731    */
732   public JalviewModel saveState(AlignmentPanel ap, String fileName,
733           boolean storeDS, JarOutputStream jout, List<String> viewIds)
734   {
735     if (viewIds == null)
736     {
737       viewIds = new ArrayList<String>();
738     }
739
740     initSeqRefs();
741
742     List<UserColourScheme> userColours = new ArrayList<UserColourScheme>();
743
744     AlignViewport av = ap.av;
745
746     JalviewModel object = new JalviewModel();
747     object.setVamsasModel(new jalview.schemabinding.version2.VamsasModel());
748
749     object.setCreationDate(new java.util.Date(System.currentTimeMillis()));
750     object.setVersion(jalview.bin.Cache.getDefault("VERSION",
751             "Development Build"));
752
753     /**
754      * rjal is full height alignment, jal is actual alignment with full metadata
755      * but excludes hidden sequences.
756      */
757     jalview.datamodel.AlignmentI rjal = av.getAlignment(), jal = rjal;
758
759     if (av.hasHiddenRows())
760     {
761       rjal = jal.getHiddenSequences().getFullAlignment();
762     }
763
764     SequenceSet vamsasSet = new SequenceSet();
765     Sequence vamsasSeq;
766     JalviewModelSequence jms = new JalviewModelSequence();
767
768     vamsasSet.setGapChar(jal.getGapCharacter() + "");
769
770     if (jal.getDataset() != null)
771     {
772       // dataset id is the dataset's hashcode
773       vamsasSet.setDatasetId(getDatasetIdRef(jal.getDataset()));
774       if (storeDS)
775       {
776         // switch jal and the dataset
777         jal = jal.getDataset();
778         rjal = jal;
779       }
780     }
781     if (jal.getProperties() != null)
782     {
783       Enumeration en = jal.getProperties().keys();
784       while (en.hasMoreElements())
785       {
786         String key = en.nextElement().toString();
787         SequenceSetProperties ssp = new SequenceSetProperties();
788         ssp.setKey(key);
789         ssp.setValue(jal.getProperties().get(key).toString());
790         vamsasSet.addSequenceSetProperties(ssp);
791       }
792     }
793
794     JSeq jseq;
795     Set<String> calcIdSet = new HashSet<String>();
796     // record the set of vamsas sequence XML POJO we create.
797     HashMap<String,Sequence> vamsasSetIds = new HashMap<String,Sequence>(); 
798     // SAVE SEQUENCES
799     for (final SequenceI jds : rjal.getSequences())
800     {
801       final SequenceI jdatasq = jds.getDatasetSequence() == null ? jds
802               : jds.getDatasetSequence();
803       String id = seqHash(jds);
804       if (vamsasSetIds.get(id) == null)
805       {
806         if (seqRefIds.get(id) != null && !storeDS)
807         {
808           // This happens for two reasons: 1. multiple views are being
809           // serialised.
810           // 2. the hashCode has collided with another sequence's code. This
811           // DOES
812           // HAPPEN! (PF00072.15.stk does this)
813           // JBPNote: Uncomment to debug writing out of files that do not read
814           // back in due to ArrayOutOfBoundExceptions.
815           // System.err.println("vamsasSeq backref: "+id+"");
816           // System.err.println(jds.getName()+"
817           // "+jds.getStart()+"-"+jds.getEnd()+" "+jds.getSequenceAsString());
818           // System.err.println("Hashcode: "+seqHash(jds));
819           // SequenceI rsq = (SequenceI) seqRefIds.get(id + "");
820           // System.err.println(rsq.getName()+"
821           // "+rsq.getStart()+"-"+rsq.getEnd()+" "+rsq.getSequenceAsString());
822           // System.err.println("Hashcode: "+seqHash(rsq));
823         }
824         else
825         {
826           vamsasSeq = createVamsasSequence(id, jds);
827           vamsasSet.addSequence(vamsasSeq);
828           vamsasSetIds.put(id, vamsasSeq);
829           seqRefIds.put(id, jds);
830         }
831       }
832       jseq = new JSeq();
833       jseq.setStart(jds.getStart());
834       jseq.setEnd(jds.getEnd());
835       jseq.setColour(av.getSequenceColour(jds).getRGB());
836
837       jseq.setId(id); // jseq id should be a string not a number
838       if (!storeDS)
839       {
840         // Store any sequences this sequence represents
841         if (av.hasHiddenRows())
842         {
843           // use rjal, contains the full height alignment
844           jseq.setHidden(av.getAlignment().getHiddenSequences()
845                   .isHidden(jds));
846
847           if (av.isHiddenRepSequence(jds))
848           {
849             jalview.datamodel.SequenceI[] reps = av
850                     .getRepresentedSequences(jds)
851                     .getSequencesInOrder(rjal);
852
853             for (int h = 0; h < reps.length; h++)
854             {
855               if (reps[h] != jds)
856               {
857                 jseq.addHiddenSequences(rjal.findIndex(reps[h]));
858               }
859             }
860           }
861         }
862         // mark sequence as reference - if it is the reference for this view
863         if (jal.hasSeqrep())
864         {
865           jseq.setViewreference(jds == jal.getSeqrep());
866         }
867       }
868
869       // TODO: omit sequence features from each alignment view's XML dump if we
870       // are storing dataset
871       if (jds.getSequenceFeatures() != null)
872       {
873         jalview.datamodel.SequenceFeature[] sf = jds.getSequenceFeatures();
874         int index = 0;
875         while (index < sf.length)
876         {
877           Features features = new Features();
878
879           features.setBegin(sf[index].getBegin());
880           features.setEnd(sf[index].getEnd());
881           features.setDescription(sf[index].getDescription());
882           features.setType(sf[index].getType());
883           features.setFeatureGroup(sf[index].getFeatureGroup());
884           features.setScore(sf[index].getScore());
885           if (sf[index].links != null)
886           {
887             for (int l = 0; l < sf[index].links.size(); l++)
888             {
889               OtherData keyValue = new OtherData();
890               keyValue.setKey("LINK_" + l);
891               keyValue.setValue(sf[index].links.elementAt(l).toString());
892               features.addOtherData(keyValue);
893             }
894           }
895           if (sf[index].otherDetails != null)
896           {
897             String key;
898             Iterator<String> keys = sf[index].otherDetails.keySet()
899                     .iterator();
900             while (keys.hasNext())
901             {
902               key = keys.next();
903               OtherData keyValue = new OtherData();
904               keyValue.setKey(key);
905               keyValue.setValue(sf[index].otherDetails.get(key).toString());
906               features.addOtherData(keyValue);
907             }
908           }
909
910           jseq.addFeatures(features);
911           index++;
912         }
913       }
914
915       if (jdatasq.getAllPDBEntries() != null)
916       {
917         Enumeration en = jdatasq.getAllPDBEntries().elements();
918         while (en.hasMoreElements())
919         {
920           Pdbids pdb = new Pdbids();
921           jalview.datamodel.PDBEntry entry = (jalview.datamodel.PDBEntry) en
922                   .nextElement();
923
924           String pdbId = entry.getId();
925           pdb.setId(pdbId);
926           pdb.setType(entry.getType());
927
928           /*
929            * Store any structure views associated with this sequence. This
930            * section copes with duplicate entries in the project, so a dataset
931            * only view *should* be coped with sensibly.
932            */
933           // This must have been loaded, is it still visible?
934           JInternalFrame[] frames = Desktop.desktop.getAllFrames();
935           String matchedFile = null;
936           for (int f = frames.length - 1; f > -1; f--)
937           {
938             if (frames[f] instanceof StructureViewerBase)
939             {
940               StructureViewerBase viewFrame = (StructureViewerBase) frames[f];
941               matchedFile = saveStructureState(ap, jds, pdb, entry,
942                       viewIds, matchedFile, viewFrame);
943               /*
944                * Only store each structure viewer's state once in the project
945                * jar. First time through only (storeDS==false)
946                */
947               String viewId = viewFrame.getViewId();
948               if (!storeDS && !viewIds.contains(viewId))
949               {
950                 viewIds.add(viewId);
951                 try
952                 {
953                   String viewerState = viewFrame.getStateInfo();
954                   writeJarEntry(jout, getViewerJarEntryName(viewId),
955                           viewerState.getBytes());
956                 } catch (IOException e)
957                 {
958                   System.err.println("Error saving viewer state: "
959                           + e.getMessage());
960                 }
961               }
962             }
963           }
964
965           if (matchedFile != null || entry.getFile() != null)
966           {
967             if (entry.getFile() != null)
968             {
969               // use entry's file
970               matchedFile = entry.getFile();
971             }
972             pdb.setFile(matchedFile); // entry.getFile());
973             if (pdbfiles == null)
974             {
975               pdbfiles = new ArrayList<String>();
976             }
977
978             if (!pdbfiles.contains(pdbId))
979             {
980               pdbfiles.add(pdbId);
981               copyFileToJar(jout, matchedFile, pdbId);
982             }
983           }
984
985           Enumeration<String> props = entry.getProperties();
986           if (props.hasMoreElements())
987           {
988             PdbentryItem item = new PdbentryItem();
989             while (props.hasMoreElements())
990             {
991               Property prop = new Property();
992               String key = props.nextElement();
993               prop.setName(key);
994               prop.setValue(entry.getProperty(key).toString());
995               item.addProperty(prop);
996             }
997             pdb.addPdbentryItem(item);
998           }
999
1000           jseq.addPdbids(pdb);
1001         }
1002       }
1003
1004       saveRnaViewers(jout, jseq, jds, viewIds, ap, storeDS);
1005
1006       jms.addJSeq(jseq);
1007     }
1008
1009     if (!storeDS && av.hasHiddenRows())
1010     {
1011       jal = av.getAlignment();
1012     }
1013     // SAVE MAPPINGS
1014     // FOR DATASET
1015     if (storeDS && jal.getCodonFrames() != null)
1016     {
1017       List<AlignedCodonFrame> jac = jal.getCodonFrames();
1018       for (AlignedCodonFrame acf : jac)
1019       {
1020         AlcodonFrame alc = new AlcodonFrame();
1021         if (acf.getProtMappings() != null
1022                 && acf.getProtMappings().length > 0)
1023         {
1024           boolean hasMap = false;
1025           SequenceI[] dnas = acf.getdnaSeqs();
1026           jalview.datamodel.Mapping[] pmaps = acf.getProtMappings();
1027           for (int m = 0; m < pmaps.length; m++)
1028           {
1029             AlcodMap alcmap = new AlcodMap();
1030             alcmap.setDnasq(seqHash(dnas[m]));
1031             alcmap.setMapping(createVamsasMapping(pmaps[m], dnas[m], null,
1032                     false));
1033             alc.addAlcodMap(alcmap);
1034             hasMap = true;
1035           }
1036           if (hasMap)
1037           {
1038             vamsasSet.addAlcodonFrame(alc);
1039           }
1040         }
1041         // TODO: delete this ? dead code from 2.8.3->2.9 ?
1042         // {
1043         // AlcodonFrame alc = new AlcodonFrame();
1044         // vamsasSet.addAlcodonFrame(alc);
1045         // for (int p = 0; p < acf.aaWidth; p++)
1046         // {
1047         // Alcodon cmap = new Alcodon();
1048         // if (acf.codons[p] != null)
1049         // {
1050         // // Null codons indicate a gapped column in the translated peptide
1051         // // alignment.
1052         // cmap.setPos1(acf.codons[p][0]);
1053         // cmap.setPos2(acf.codons[p][1]);
1054         // cmap.setPos3(acf.codons[p][2]);
1055         // }
1056         // alc.addAlcodon(cmap);
1057         // }
1058         // if (acf.getProtMappings() != null
1059         // && acf.getProtMappings().length > 0)
1060         // {
1061         // SequenceI[] dnas = acf.getdnaSeqs();
1062         // jalview.datamodel.Mapping[] pmaps = acf.getProtMappings();
1063         // for (int m = 0; m < pmaps.length; m++)
1064         // {
1065         // AlcodMap alcmap = new AlcodMap();
1066         // alcmap.setDnasq(seqHash(dnas[m]));
1067         // alcmap.setMapping(createVamsasMapping(pmaps[m], dnas[m], null,
1068         // false));
1069         // alc.addAlcodMap(alcmap);
1070         // }
1071         // }
1072       }
1073     }
1074
1075     // SAVE TREES
1076     // /////////////////////////////////
1077     if (!storeDS && av.currentTree != null)
1078     {
1079       // FIND ANY ASSOCIATED TREES
1080       // NOT IMPLEMENTED FOR HEADLESS STATE AT PRESENT
1081       if (Desktop.desktop != null)
1082       {
1083         JInternalFrame[] frames = Desktop.desktop.getAllFrames();
1084
1085         for (int t = 0; t < frames.length; t++)
1086         {
1087           if (frames[t] instanceof TreePanel)
1088           {
1089             TreePanel tp = (TreePanel) frames[t];
1090
1091             if (tp.treeCanvas.av.getAlignment() == jal)
1092             {
1093               Tree tree = new Tree();
1094               tree.setTitle(tp.getTitle());
1095               tree.setCurrentTree((av.currentTree == tp.getTree()));
1096               tree.setNewick(tp.getTree().toString());
1097               tree.setThreshold(tp.treeCanvas.threshold);
1098
1099               tree.setFitToWindow(tp.fitToWindow.getState());
1100               tree.setFontName(tp.getTreeFont().getName());
1101               tree.setFontSize(tp.getTreeFont().getSize());
1102               tree.setFontStyle(tp.getTreeFont().getStyle());
1103               tree.setMarkUnlinked(tp.placeholdersMenu.getState());
1104
1105               tree.setShowBootstrap(tp.bootstrapMenu.getState());
1106               tree.setShowDistances(tp.distanceMenu.getState());
1107
1108               tree.setHeight(tp.getHeight());
1109               tree.setWidth(tp.getWidth());
1110               tree.setXpos(tp.getX());
1111               tree.setYpos(tp.getY());
1112               tree.setId(makeHashCode(tp, null));
1113               jms.addTree(tree);
1114             }
1115           }
1116         }
1117       }
1118     }
1119
1120     // SAVE ANNOTATIONS
1121     /**
1122      * store forward refs from an annotationRow to any groups
1123      */
1124     IdentityHashMap<SequenceGroup, String> groupRefs = new IdentityHashMap<SequenceGroup, String>();
1125     if (storeDS)
1126     {
1127       for (SequenceI sq : jal.getSequences())
1128       {
1129         // Store annotation on dataset sequences only
1130         AlignmentAnnotation[] aa = sq.getAnnotation();
1131         if (aa != null && aa.length > 0)
1132         {
1133           storeAlignmentAnnotation(aa, groupRefs, av, calcIdSet, storeDS,
1134                   vamsasSet);
1135         }
1136       }
1137     }
1138     else
1139     {
1140       if (jal.getAlignmentAnnotation() != null)
1141       {
1142         // Store the annotation shown on the alignment.
1143         AlignmentAnnotation[] aa = jal.getAlignmentAnnotation();
1144         storeAlignmentAnnotation(aa, groupRefs, av, calcIdSet, storeDS,
1145                 vamsasSet);
1146       }
1147     }
1148     // SAVE GROUPS
1149     if (jal.getGroups() != null)
1150     {
1151       JGroup[] groups = new JGroup[jal.getGroups().size()];
1152       int i = -1;
1153       for (jalview.datamodel.SequenceGroup sg : jal.getGroups())
1154       {
1155         JGroup jGroup = new JGroup();
1156         groups[++i] = jGroup;
1157
1158         jGroup.setStart(sg.getStartRes());
1159         jGroup.setEnd(sg.getEndRes());
1160         jGroup.setName(sg.getName());
1161         if (groupRefs.containsKey(sg))
1162         {
1163           // group has references so set its ID field
1164           jGroup.setId(groupRefs.get(sg));
1165         }
1166         if (sg.cs != null)
1167         {
1168           if (sg.cs.conservationApplied())
1169           {
1170             jGroup.setConsThreshold(sg.cs.getConservationInc());
1171
1172             if (sg.cs instanceof jalview.schemes.UserColourScheme)
1173             {
1174               jGroup.setColour(setUserColourScheme(sg.cs, userColours, jms));
1175             }
1176             else
1177             {
1178               jGroup.setColour(ColourSchemeProperty.getColourName(sg.cs));
1179             }
1180           }
1181           else if (sg.cs instanceof jalview.schemes.AnnotationColourGradient)
1182           {
1183             jGroup.setColour("AnnotationColourGradient");
1184             jGroup.setAnnotationColours(constructAnnotationColours(
1185                     (jalview.schemes.AnnotationColourGradient) sg.cs,
1186                     userColours, jms));
1187           }
1188           else if (sg.cs instanceof jalview.schemes.UserColourScheme)
1189           {
1190             jGroup.setColour(setUserColourScheme(sg.cs, userColours, jms));
1191           }
1192           else
1193           {
1194             jGroup.setColour(ColourSchemeProperty.getColourName(sg.cs));
1195           }
1196
1197           jGroup.setPidThreshold(sg.cs.getThreshold());
1198         }
1199
1200         jGroup.setOutlineColour(sg.getOutlineColour().getRGB());
1201         jGroup.setDisplayBoxes(sg.getDisplayBoxes());
1202         jGroup.setDisplayText(sg.getDisplayText());
1203         jGroup.setColourText(sg.getColourText());
1204         jGroup.setTextCol1(sg.textColour.getRGB());
1205         jGroup.setTextCol2(sg.textColour2.getRGB());
1206         jGroup.setTextColThreshold(sg.thresholdTextColour);
1207         jGroup.setShowUnconserved(sg.getShowNonconserved());
1208         jGroup.setIgnoreGapsinConsensus(sg.getIgnoreGapsConsensus());
1209         jGroup.setShowConsensusHistogram(sg.isShowConsensusHistogram());
1210         jGroup.setShowSequenceLogo(sg.isShowSequenceLogo());
1211         jGroup.setNormaliseSequenceLogo(sg.isNormaliseSequenceLogo());
1212         for (SequenceI seq : sg.getSequences())
1213         {
1214           jGroup.addSeq(seqHash(seq));
1215         }
1216       }
1217
1218       jms.setJGroup(groups);
1219     }
1220     if (!storeDS)
1221     {
1222       // /////////SAVE VIEWPORT
1223       Viewport view = new Viewport();
1224       view.setTitle(ap.alignFrame.getTitle());
1225       view.setSequenceSetId(makeHashCode(av.getSequenceSetId(),
1226               av.getSequenceSetId()));
1227       view.setId(av.getViewId());
1228       if (av.getCodingComplement() != null)
1229       {
1230         view.setComplementId(av.getCodingComplement().getViewId());
1231       }
1232       view.setViewName(av.viewName);
1233       view.setGatheredViews(av.isGatherViewsHere());
1234
1235       Rectangle size = ap.av.getExplodedGeometry();
1236       Rectangle position = size;
1237       if (size == null)
1238       {
1239         size = ap.alignFrame.getBounds();
1240         if (av.getCodingComplement() != null)
1241         {
1242           position = ((SplitFrame) ap.alignFrame.getSplitViewContainer())
1243                   .getBounds();
1244         }
1245         else
1246         {
1247           position = size;
1248         }
1249       }
1250       view.setXpos(position.x);
1251       view.setYpos(position.y);
1252
1253       view.setWidth(size.width);
1254       view.setHeight(size.height);
1255
1256       view.setStartRes(av.startRes);
1257       view.setStartSeq(av.startSeq);
1258
1259       if (av.getGlobalColourScheme() instanceof jalview.schemes.UserColourScheme)
1260       {
1261         view.setBgColour(setUserColourScheme(av.getGlobalColourScheme(),
1262                 userColours, jms));
1263       }
1264       else if (av.getGlobalColourScheme() instanceof jalview.schemes.AnnotationColourGradient)
1265       {
1266         AnnotationColours ac = constructAnnotationColours(
1267                 (jalview.schemes.AnnotationColourGradient) av
1268                         .getGlobalColourScheme(),
1269                 userColours, jms);
1270
1271         view.setAnnotationColours(ac);
1272         view.setBgColour("AnnotationColourGradient");
1273       }
1274       else
1275       {
1276         view.setBgColour(ColourSchemeProperty.getColourName(av
1277                 .getGlobalColourScheme()));
1278       }
1279
1280       ColourSchemeI cs = av.getGlobalColourScheme();
1281
1282       if (cs != null)
1283       {
1284         if (cs.conservationApplied())
1285         {
1286           view.setConsThreshold(cs.getConservationInc());
1287           if (cs instanceof jalview.schemes.UserColourScheme)
1288           {
1289             view.setBgColour(setUserColourScheme(cs, userColours, jms));
1290           }
1291         }
1292
1293         if (cs instanceof ResidueColourScheme)
1294         {
1295           view.setPidThreshold(cs.getThreshold());
1296         }
1297       }
1298
1299       view.setConservationSelected(av.getConservationSelected());
1300       view.setPidSelected(av.getAbovePIDThreshold());
1301       view.setFontName(av.font.getName());
1302       view.setFontSize(av.font.getSize());
1303       view.setFontStyle(av.font.getStyle());
1304       view.setScaleProteinAsCdna(av.getViewStyle().isScaleProteinAsCdna());
1305       view.setRenderGaps(av.isRenderGaps());
1306       view.setShowAnnotation(av.isShowAnnotation());
1307       view.setShowBoxes(av.getShowBoxes());
1308       view.setShowColourText(av.getColourText());
1309       view.setShowFullId(av.getShowJVSuffix());
1310       view.setRightAlignIds(av.isRightAlignIds());
1311       view.setShowSequenceFeatures(av.isShowSequenceFeatures());
1312       view.setShowText(av.getShowText());
1313       view.setShowUnconserved(av.getShowUnconserved());
1314       view.setWrapAlignment(av.getWrapAlignment());
1315       view.setTextCol1(av.getTextColour().getRGB());
1316       view.setTextCol2(av.getTextColour2().getRGB());
1317       view.setTextColThreshold(av.getThresholdTextColour());
1318       view.setShowConsensusHistogram(av.isShowConsensusHistogram());
1319       view.setShowSequenceLogo(av.isShowSequenceLogo());
1320       view.setNormaliseSequenceLogo(av.isNormaliseSequenceLogo());
1321       view.setShowGroupConsensus(av.isShowGroupConsensus());
1322       view.setShowGroupConservation(av.isShowGroupConservation());
1323       view.setShowNPfeatureTooltip(av.isShowNPFeats());
1324       view.setShowDbRefTooltip(av.isShowDBRefs());
1325       view.setFollowHighlight(av.isFollowHighlight());
1326       view.setFollowSelection(av.followSelection);
1327       view.setIgnoreGapsinConsensus(av.isIgnoreGapsConsensus());
1328       if (av.getFeaturesDisplayed() != null)
1329       {
1330         jalview.schemabinding.version2.FeatureSettings fs = new jalview.schemabinding.version2.FeatureSettings();
1331
1332         String[] renderOrder = ap.getSeqPanel().seqCanvas
1333                 .getFeatureRenderer().getRenderOrder()
1334                 .toArray(new String[0]);
1335
1336         Vector<String> settingsAdded = new Vector<String>();
1337         if (renderOrder != null)
1338         {
1339           for (String featureType : renderOrder)
1340           {
1341             FeatureColourI fcol = ap.getSeqPanel().seqCanvas
1342                     .getFeatureRenderer()
1343                     .getFeatureStyle(featureType);
1344             Setting setting = new Setting();
1345             setting.setType(featureType);
1346             if (!fcol.isSimpleColour())
1347             {
1348               setting.setColour(fcol.getMaxColour().getRGB());
1349               setting.setMincolour(fcol.getMinColour().getRGB());
1350               setting.setMin(fcol.getMin());
1351               setting.setMax(fcol.getMax());
1352               setting.setColourByLabel(fcol.isColourByLabel());
1353               setting.setAutoScale(fcol.isAutoScaled());
1354               setting.setThreshold(fcol.getThreshold());
1355               // -1 = No threshold, 0 = Below, 1 = Above
1356               setting.setThreshstate(fcol.isAboveThreshold() ? 1
1357                       : (fcol.isBelowThreshold() ? 0 : -1));
1358             }
1359             else
1360             {
1361               setting.setColour(fcol.getColour().getRGB());
1362             }
1363
1364             setting.setDisplay(av.getFeaturesDisplayed().isVisible(
1365                     featureType));
1366             float rorder = ap.getSeqPanel().seqCanvas.getFeatureRenderer()
1367                     .getOrder(featureType);
1368             if (rorder > -1)
1369             {
1370               setting.setOrder(rorder);
1371             }
1372             fs.addSetting(setting);
1373             settingsAdded.addElement(featureType);
1374           }
1375         }
1376
1377         // is groups actually supposed to be a map here ?
1378         Iterator<String> en = ap.getSeqPanel().seqCanvas
1379                 .getFeatureRenderer()
1380                 .getFeatureGroups().iterator();
1381         Vector<String> groupsAdded = new Vector<String>();
1382         while (en.hasNext())
1383         {
1384           String grp = en.next();
1385           if (groupsAdded.contains(grp))
1386           {
1387             continue;
1388           }
1389           Group g = new Group();
1390           g.setName(grp);
1391           g.setDisplay(((Boolean) ap.getSeqPanel().seqCanvas
1392                   .getFeatureRenderer().checkGroupVisibility(grp, false))
1393                   .booleanValue());
1394           fs.addGroup(g);
1395           groupsAdded.addElement(grp);
1396         }
1397         jms.setFeatureSettings(fs);
1398       }
1399
1400       if (av.hasHiddenColumns())
1401       {
1402         if (av.getColumnSelection() == null
1403                 || av.getColumnSelection().getHiddenColumns() == null)
1404         {
1405           warn("REPORT BUG: avoided null columnselection bug (DMAM reported). Please contact Jim about this.");
1406         }
1407         else
1408         {
1409           for (int c = 0; c < av.getColumnSelection().getHiddenColumns()
1410                   .size(); c++)
1411           {
1412             int[] region = av.getColumnSelection().getHiddenColumns()
1413                     .get(c);
1414             HiddenColumns hc = new HiddenColumns();
1415             hc.setStart(region[0]);
1416             hc.setEnd(region[1]);
1417             view.addHiddenColumns(hc);
1418           }
1419         }
1420       }
1421       if (calcIdSet.size() > 0)
1422       {
1423         for (String calcId : calcIdSet)
1424         {
1425           if (calcId.trim().length() > 0)
1426           {
1427             CalcIdParam cidp = createCalcIdParam(calcId, av);
1428             // Some calcIds have no parameters.
1429             if (cidp != null)
1430             {
1431               view.addCalcIdParam(cidp);
1432             }
1433           }
1434         }
1435       }
1436
1437       jms.addViewport(view);
1438     }
1439     object.setJalviewModelSequence(jms);
1440     object.getVamsasModel().addSequenceSet(vamsasSet);
1441
1442     if (jout != null && fileName != null)
1443     {
1444       // We may not want to write the object to disk,
1445       // eg we can copy the alignViewport to a new view object
1446       // using save and then load
1447       try
1448       {
1449         System.out.println("Writing jar entry " + fileName);
1450         JarEntry entry = new JarEntry(fileName);
1451         jout.putNextEntry(entry);
1452         PrintWriter pout = new PrintWriter(new OutputStreamWriter(jout,
1453                 UTF_8));
1454         Marshaller marshaller = new Marshaller(pout);
1455         marshaller.marshal(object);
1456         pout.flush();
1457         jout.closeEntry();
1458       } catch (Exception ex)
1459       {
1460         // TODO: raise error in GUI if marshalling failed.
1461         ex.printStackTrace();
1462       }
1463     }
1464     return object;
1465   }
1466
1467   /**
1468    * Save any Varna viewers linked to this sequence. Writes an rnaViewer element
1469    * for each viewer, with
1470    * <ul>
1471    * <li>viewer geometry (position, size, split pane divider location)</li>
1472    * <li>index of the selected structure in the viewer (currently shows gapped
1473    * or ungapped)</li>
1474    * <li>the id of the annotation holding RNA secondary structure</li>
1475    * <li>(currently only one SS is shown per viewer, may be more in future)</li>
1476    * </ul>
1477    * Varna viewer state is also written out (in native Varna XML) to separate
1478    * project jar entries. A separate entry is written for each RNA structure
1479    * displayed, with the naming convention
1480    * <ul>
1481    * <li>rna_viewId_sequenceId_annotationId_[gapped|trimmed]</li>
1482    * </ul>
1483    * 
1484    * @param jout
1485    * @param jseq
1486    * @param jds
1487    * @param viewIds
1488    * @param ap
1489    * @param storeDataset
1490    */
1491   protected void saveRnaViewers(JarOutputStream jout, JSeq jseq,
1492           final SequenceI jds, List<String> viewIds, AlignmentPanel ap,
1493           boolean storeDataset)
1494   {
1495     if (Desktop.desktop == null)
1496     {
1497       return;
1498     }
1499     JInternalFrame[] frames = Desktop.desktop.getAllFrames();
1500     for (int f = frames.length - 1; f > -1; f--)
1501     {
1502       if (frames[f] instanceof AppVarna)
1503       {
1504         AppVarna varna = (AppVarna) frames[f];
1505         /*
1506          * link the sequence to every viewer that is showing it and is linked to
1507          * its alignment panel
1508          */
1509         if (varna.isListeningFor(jds) && ap == varna.getAlignmentPanel())
1510         {
1511           String viewId = varna.getViewId();
1512           RnaViewer rna = new RnaViewer();
1513           rna.setViewId(viewId);
1514           rna.setTitle(varna.getTitle());
1515           rna.setXpos(varna.getX());
1516           rna.setYpos(varna.getY());
1517           rna.setWidth(varna.getWidth());
1518           rna.setHeight(varna.getHeight());
1519           rna.setDividerLocation(varna.getDividerLocation());
1520           rna.setSelectedRna(varna.getSelectedIndex());
1521           jseq.addRnaViewer(rna);
1522
1523           /*
1524            * Store each Varna panel's state once in the project per sequence.
1525            * First time through only (storeDataset==false)
1526            */
1527           // boolean storeSessions = false;
1528           // String sequenceViewId = viewId + seqsToIds.get(jds);
1529           // if (!storeDataset && !viewIds.contains(sequenceViewId))
1530           // {
1531           // viewIds.add(sequenceViewId);
1532           // storeSessions = true;
1533           // }
1534           for (RnaModel model : varna.getModels())
1535           {
1536             if (model.seq == jds)
1537             {
1538               /*
1539                * VARNA saves each view (sequence or alignment secondary
1540                * structure, gapped or trimmed) as a separate XML file
1541                */
1542               String jarEntryName = rnaSessions.get(model);
1543               if (jarEntryName == null)
1544               {
1545
1546                 String varnaStateFile = varna.getStateInfo(model.rna);
1547                 jarEntryName = RNA_PREFIX + viewId + "_" + nextCounter();
1548                 copyFileToJar(jout, varnaStateFile, jarEntryName);
1549                 rnaSessions.put(model, jarEntryName);
1550               }
1551               SecondaryStructure ss = new SecondaryStructure();
1552               String annotationId = varna.getAnnotation(jds).annotationId;
1553               ss.setAnnotationId(annotationId);
1554               ss.setViewerState(jarEntryName);
1555               ss.setGapped(model.gapped);
1556               ss.setTitle(model.title);
1557               rna.addSecondaryStructure(ss);
1558             }
1559           }
1560         }
1561       }
1562     }
1563   }
1564
1565   /**
1566    * Copy the contents of a file to a new entry added to the output jar
1567    * 
1568    * @param jout
1569    * @param infilePath
1570    * @param jarEntryName
1571    */
1572   protected void copyFileToJar(JarOutputStream jout, String infilePath,
1573           String jarEntryName)
1574   {
1575     DataInputStream dis = null;
1576     try
1577     {
1578       File file = new File(infilePath);
1579       if (file.exists() && jout != null)
1580       {
1581         dis = new DataInputStream(new FileInputStream(file));
1582         byte[] data = new byte[(int) file.length()];
1583         dis.readFully(data);
1584         writeJarEntry(jout, jarEntryName, data);
1585       }
1586     } catch (Exception ex)
1587     {
1588       ex.printStackTrace();
1589     } finally
1590     {
1591       if (dis != null)
1592       {
1593         try
1594         {
1595           dis.close();
1596         } catch (IOException e)
1597         {
1598           // ignore
1599         }
1600       }
1601     }
1602   }
1603
1604   /**
1605    * Write the data to a new entry of given name in the output jar file
1606    * 
1607    * @param jout
1608    * @param jarEntryName
1609    * @param data
1610    * @throws IOException
1611    */
1612   protected void writeJarEntry(JarOutputStream jout, String jarEntryName,
1613           byte[] data) throws IOException
1614   {
1615     if (jout != null)
1616     {
1617       System.out.println("Writing jar entry " + jarEntryName);
1618       jout.putNextEntry(new JarEntry(jarEntryName));
1619       DataOutputStream dout = new DataOutputStream(jout);
1620       dout.write(data, 0, data.length);
1621       dout.flush();
1622       jout.closeEntry();
1623     }
1624   }
1625
1626   /**
1627    * Save the state of a structure viewer
1628    * 
1629    * @param ap
1630    * @param jds
1631    * @param pdb
1632    *          the archive XML element under which to save the state
1633    * @param entry
1634    * @param viewIds
1635    * @param matchedFile
1636    * @param viewFrame
1637    * @return
1638    */
1639   protected String saveStructureState(AlignmentPanel ap, SequenceI jds,
1640           Pdbids pdb, PDBEntry entry, List<String> viewIds,
1641           String matchedFile, StructureViewerBase viewFrame)
1642   {
1643     final AAStructureBindingModel bindingModel = viewFrame.getBinding();
1644
1645     /*
1646      * Look for any bindings for this viewer to the PDB file of interest
1647      * (including part matches excluding chain id)
1648      */
1649     for (int peid = 0; peid < bindingModel.getPdbCount(); peid++)
1650     {
1651       final PDBEntry pdbentry = bindingModel.getPdbEntry(peid);
1652       final String pdbId = pdbentry.getId();
1653       if (!pdbId.equals(entry.getId())
1654               && !(entry.getId().length() > 4 && entry.getId()
1655                       .toLowerCase().startsWith(pdbId.toLowerCase())))
1656       {
1657         /*
1658          * not interested in a binding to a different PDB entry here
1659          */
1660         continue;
1661       }
1662       if (matchedFile == null)
1663       {
1664         matchedFile = pdbentry.getFile();
1665       }
1666       else if (!matchedFile.equals(pdbentry.getFile()))
1667       {
1668         Cache.log
1669                 .warn("Probably lost some PDB-Sequence mappings for this structure file (which apparently has same PDB Entry code): "
1670                         + pdbentry.getFile());
1671       }
1672       // record the
1673       // file so we
1674       // can get at it if the ID
1675       // match is ambiguous (e.g.
1676       // 1QIP==1qipA)
1677
1678       for (int smap = 0; smap < viewFrame.getBinding().getSequence()[peid].length; smap++)
1679       {
1680         // if (jal.findIndex(jmol.jmb.sequence[peid][smap]) > -1)
1681         if (jds == viewFrame.getBinding().getSequence()[peid][smap])
1682         {
1683           StructureState state = new StructureState();
1684           state.setVisible(true);
1685           state.setXpos(viewFrame.getX());
1686           state.setYpos(viewFrame.getY());
1687           state.setWidth(viewFrame.getWidth());
1688           state.setHeight(viewFrame.getHeight());
1689           final String viewId = viewFrame.getViewId();
1690           state.setViewId(viewId);
1691           state.setAlignwithAlignPanel(viewFrame.isUsedforaligment(ap));
1692           state.setColourwithAlignPanel(viewFrame.isUsedforcolourby(ap));
1693           state.setColourByJmol(viewFrame.isColouredByViewer());
1694           state.setType(viewFrame.getViewerType().toString());
1695           pdb.addStructureState(state);
1696         }
1697       }
1698     }
1699     return matchedFile;
1700   }
1701
1702   private AnnotationColours constructAnnotationColours(
1703           AnnotationColourGradient acg, List<UserColourScheme> userColours,
1704           JalviewModelSequence jms)
1705   {
1706     AnnotationColours ac = new AnnotationColours();
1707     ac.setAboveThreshold(acg.getAboveThreshold());
1708     ac.setThreshold(acg.getAnnotationThreshold());
1709     ac.setAnnotation(acg.getAnnotation());
1710     if (acg.getBaseColour() instanceof jalview.schemes.UserColourScheme)
1711     {
1712       ac.setColourScheme(setUserColourScheme(acg.getBaseColour(),
1713               userColours, jms));
1714     }
1715     else
1716     {
1717       ac.setColourScheme(ColourSchemeProperty.getColourName(acg
1718               .getBaseColour()));
1719     }
1720
1721     ac.setMaxColour(acg.getMaxColour().getRGB());
1722     ac.setMinColour(acg.getMinColour().getRGB());
1723     ac.setPerSequence(acg.isSeqAssociated());
1724     ac.setPredefinedColours(acg.isPredefinedColours());
1725     return ac;
1726   }
1727
1728   private void storeAlignmentAnnotation(AlignmentAnnotation[] aa,
1729           IdentityHashMap<SequenceGroup, String> groupRefs,
1730           AlignmentViewport av, Set<String> calcIdSet, boolean storeDS,
1731           SequenceSet vamsasSet)
1732   {
1733
1734     for (int i = 0; i < aa.length; i++)
1735     {
1736       Annotation an = new Annotation();
1737
1738       AlignmentAnnotation annotation = aa[i];
1739       if (annotation.annotationId != null)
1740       {
1741         annotationIds.put(annotation.annotationId, annotation);
1742       }
1743
1744       an.setId(annotation.annotationId);
1745
1746       an.setVisible(annotation.visible);
1747
1748       an.setDescription(annotation.description);
1749
1750       if (annotation.sequenceRef != null)
1751       {
1752         // 2.9 JAL-1781 xref on sequence id rather than name
1753         an.setSequenceRef(seqsToIds.get(annotation.sequenceRef));
1754       }
1755       if (annotation.groupRef != null)
1756       {
1757         String groupIdr = groupRefs.get(annotation.groupRef);
1758         if (groupIdr == null)
1759         {
1760           // make a locally unique String
1761           groupRefs.put(
1762                   annotation.groupRef,
1763                   groupIdr = ("" + System.currentTimeMillis()
1764                           + annotation.groupRef.getName() + groupRefs
1765                           .size()));
1766         }
1767         an.setGroupRef(groupIdr.toString());
1768       }
1769
1770       // store all visualization attributes for annotation
1771       an.setGraphHeight(annotation.graphHeight);
1772       an.setCentreColLabels(annotation.centreColLabels);
1773       an.setScaleColLabels(annotation.scaleColLabel);
1774       an.setShowAllColLabels(annotation.showAllColLabels);
1775       an.setBelowAlignment(annotation.belowAlignment);
1776
1777       if (annotation.graph > 0)
1778       {
1779         an.setGraph(true);
1780         an.setGraphType(annotation.graph);
1781         an.setGraphGroup(annotation.graphGroup);
1782         if (annotation.getThreshold() != null)
1783         {
1784           ThresholdLine line = new ThresholdLine();
1785           line.setLabel(annotation.getThreshold().label);
1786           line.setValue(annotation.getThreshold().value);
1787           line.setColour(annotation.getThreshold().colour.getRGB());
1788           an.setThresholdLine(line);
1789         }
1790       }
1791       else
1792       {
1793         an.setGraph(false);
1794       }
1795
1796       an.setLabel(annotation.label);
1797
1798       if (annotation == av.getAlignmentQualityAnnot()
1799               || annotation == av.getAlignmentConservationAnnotation()
1800               || annotation == av.getAlignmentConsensusAnnotation()
1801               || annotation.autoCalculated)
1802       {
1803         // new way of indicating autocalculated annotation -
1804         an.setAutoCalculated(annotation.autoCalculated);
1805       }
1806       if (annotation.hasScore())
1807       {
1808         an.setScore(annotation.getScore());
1809       }
1810
1811       if (annotation.getCalcId() != null)
1812       {
1813         calcIdSet.add(annotation.getCalcId());
1814         an.setCalcId(annotation.getCalcId());
1815       }
1816       if (annotation.hasProperties())
1817       {
1818         for (String pr : annotation.getProperties())
1819         {
1820           Property prop = new Property();
1821           prop.setName(pr);
1822           prop.setValue(annotation.getProperty(pr));
1823           an.addProperty(prop);
1824         }
1825       }
1826
1827       AnnotationElement ae;
1828       if (annotation.annotations != null)
1829       {
1830         an.setScoreOnly(false);
1831         for (int a = 0; a < annotation.annotations.length; a++)
1832         {
1833           if ((annotation == null) || (annotation.annotations[a] == null))
1834           {
1835             continue;
1836           }
1837
1838           ae = new AnnotationElement();
1839           if (annotation.annotations[a].description != null)
1840           {
1841             ae.setDescription(annotation.annotations[a].description);
1842           }
1843           if (annotation.annotations[a].displayCharacter != null)
1844           {
1845             ae.setDisplayCharacter(annotation.annotations[a].displayCharacter);
1846           }
1847
1848           if (!Float.isNaN(annotation.annotations[a].value))
1849           {
1850             ae.setValue(annotation.annotations[a].value);
1851           }
1852
1853           ae.setPosition(a);
1854           if (annotation.annotations[a].secondaryStructure > ' ')
1855           {
1856             ae.setSecondaryStructure(annotation.annotations[a].secondaryStructure
1857                     + "");
1858           }
1859
1860           if (annotation.annotations[a].colour != null
1861                   && annotation.annotations[a].colour != java.awt.Color.black)
1862           {
1863             ae.setColour(annotation.annotations[a].colour.getRGB());
1864           }
1865
1866           an.addAnnotationElement(ae);
1867           if (annotation.autoCalculated)
1868           {
1869             // only write one non-null entry into the annotation row -
1870             // sufficient to get the visualization attributes necessary to
1871             // display data
1872             continue;
1873           }
1874         }
1875       }
1876       else
1877       {
1878         an.setScoreOnly(true);
1879       }
1880       if (!storeDS || (storeDS && !annotation.autoCalculated))
1881       {
1882         // skip autocalculated annotation - these are only provided for
1883         // alignments
1884         vamsasSet.addAnnotation(an);
1885       }
1886     }
1887
1888   }
1889
1890   private CalcIdParam createCalcIdParam(String calcId, AlignViewport av)
1891   {
1892     AutoCalcSetting settings = av.getCalcIdSettingsFor(calcId);
1893     if (settings != null)
1894     {
1895       CalcIdParam vCalcIdParam = new CalcIdParam();
1896       vCalcIdParam.setCalcId(calcId);
1897       vCalcIdParam.addServiceURL(settings.getServiceURI());
1898       // generic URI allowing a third party to resolve another instance of the
1899       // service used for this calculation
1900       for (String urls : settings.getServiceURLs())
1901       {
1902         vCalcIdParam.addServiceURL(urls);
1903       }
1904       vCalcIdParam.setVersion("1.0");
1905       if (settings.getPreset() != null)
1906       {
1907         WsParamSetI setting = settings.getPreset();
1908         vCalcIdParam.setName(setting.getName());
1909         vCalcIdParam.setDescription(setting.getDescription());
1910       }
1911       else
1912       {
1913         vCalcIdParam.setName("");
1914         vCalcIdParam.setDescription("Last used parameters");
1915       }
1916       // need to be able to recover 1) settings 2) user-defined presets or
1917       // recreate settings from preset 3) predefined settings provided by
1918       // service - or settings that can be transferred (or discarded)
1919       vCalcIdParam.setParameters(settings.getWsParamFile().replace("\n",
1920               "|\\n|"));
1921       vCalcIdParam.setAutoUpdate(settings.isAutoUpdate());
1922       // todo - decide if updateImmediately is needed for any projects.
1923
1924       return vCalcIdParam;
1925     }
1926     return null;
1927   }
1928
1929   private boolean recoverCalcIdParam(CalcIdParam calcIdParam,
1930           AlignViewport av)
1931   {
1932     if (calcIdParam.getVersion().equals("1.0"))
1933     {
1934       Jws2Instance service = Jws2Discoverer.getDiscoverer()
1935               .getPreferredServiceFor(calcIdParam.getServiceURL());
1936       if (service != null)
1937       {
1938         WsParamSetI parmSet = null;
1939         try
1940         {
1941           parmSet = service.getParamStore().parseServiceParameterFile(
1942                   calcIdParam.getName(), calcIdParam.getDescription(),
1943                   calcIdParam.getServiceURL(),
1944                   calcIdParam.getParameters().replace("|\\n|", "\n"));
1945         } catch (IOException x)
1946         {
1947           warn("Couldn't parse parameter data for "
1948                   + calcIdParam.getCalcId(), x);
1949           return false;
1950         }
1951         List<ArgumentI> argList = null;
1952         if (calcIdParam.getName().length() > 0)
1953         {
1954           parmSet = service.getParamStore()
1955                   .getPreset(calcIdParam.getName());
1956           if (parmSet != null)
1957           {
1958             // TODO : check we have a good match with settings in AACon -
1959             // otherwise we'll need to create a new preset
1960           }
1961         }
1962         else
1963         {
1964           argList = parmSet.getArguments();
1965           parmSet = null;
1966         }
1967         AAConSettings settings = new AAConSettings(
1968                 calcIdParam.isAutoUpdate(), service, parmSet, argList);
1969         av.setCalcIdSettingsFor(calcIdParam.getCalcId(), settings,
1970                 calcIdParam.isNeedsUpdate());
1971         return true;
1972       }
1973       else
1974       {
1975         warn("Cannot resolve a service for the parameters used in this project. Try configuring a JABAWS server.");
1976         return false;
1977       }
1978     }
1979     throw new Error(MessageManager.formatMessage(
1980             "error.unsupported_version_calcIdparam",
1981             new Object[] { calcIdParam.toString() }));
1982   }
1983
1984   /**
1985    * External mapping between jalview objects and objects yielding a valid and
1986    * unique object ID string. This is null for normal Jalview project IO, but
1987    * non-null when a jalview project is being read or written as part of a
1988    * vamsas session.
1989    */
1990   IdentityHashMap jv2vobj = null;
1991
1992   /**
1993    * Construct a unique ID for jvobj using either existing bindings or if none
1994    * exist, the result of the hashcode call for the object.
1995    * 
1996    * @param jvobj
1997    *          jalview data object
1998    * @return unique ID for referring to jvobj
1999    */
2000   private String makeHashCode(Object jvobj, String altCode)
2001   {
2002     if (jv2vobj != null)
2003     {
2004       Object id = jv2vobj.get(jvobj);
2005       if (id != null)
2006       {
2007         return id.toString();
2008       }
2009       // check string ID mappings
2010       if (jvids2vobj != null && jvobj instanceof String)
2011       {
2012         id = jvids2vobj.get(jvobj);
2013       }
2014       if (id != null)
2015       {
2016         return id.toString();
2017       }
2018       // give up and warn that something has gone wrong
2019       warn("Cannot find ID for object in external mapping : " + jvobj);
2020     }
2021     return altCode;
2022   }
2023
2024   /**
2025    * return local jalview object mapped to ID, if it exists
2026    * 
2027    * @param idcode
2028    *          (may be null)
2029    * @return null or object bound to idcode
2030    */
2031   private Object retrieveExistingObj(String idcode)
2032   {
2033     if (idcode != null && vobj2jv != null)
2034     {
2035       return vobj2jv.get(idcode);
2036     }
2037     return null;
2038   }
2039
2040   /**
2041    * binding from ID strings from external mapping table to jalview data model
2042    * objects.
2043    */
2044   private Hashtable vobj2jv;
2045
2046   private Sequence createVamsasSequence(String id, SequenceI jds)
2047   {
2048     return createVamsasSequence(true, id, jds, null);
2049   }
2050
2051   private Sequence createVamsasSequence(boolean recurse, String id,
2052           SequenceI jds, SequenceI parentseq)
2053   {
2054     Sequence vamsasSeq = new Sequence();
2055     vamsasSeq.setId(id);
2056     vamsasSeq.setName(jds.getName());
2057     vamsasSeq.setSequence(jds.getSequenceAsString());
2058     vamsasSeq.setDescription(jds.getDescription());
2059     jalview.datamodel.DBRefEntry[] dbrefs = null;
2060     if (jds.getDatasetSequence() != null)
2061     {
2062       vamsasSeq.setDsseqid(seqHash(jds.getDatasetSequence()));
2063     }
2064     else
2065     {
2066       // seqId==dsseqid so we can tell which sequences really are
2067       // dataset sequences only
2068       vamsasSeq.setDsseqid(id);
2069       dbrefs = jds.getDBRefs();
2070       if (parentseq == null)
2071       {
2072         parentseq = jds;
2073       }
2074     }
2075     if (dbrefs != null)
2076     {
2077       for (int d = 0; d < dbrefs.length; d++)
2078       {
2079         DBRef dbref = new DBRef();
2080         dbref.setSource(dbrefs[d].getSource());
2081         dbref.setVersion(dbrefs[d].getVersion());
2082         dbref.setAccessionId(dbrefs[d].getAccessionId());
2083         if (dbrefs[d].hasMap())
2084         {
2085           Mapping mp = createVamsasMapping(dbrefs[d].getMap(), parentseq,
2086                   jds, recurse);
2087           dbref.setMapping(mp);
2088         }
2089         vamsasSeq.addDBRef(dbref);
2090       }
2091     }
2092     return vamsasSeq;
2093   }
2094
2095   private Mapping createVamsasMapping(jalview.datamodel.Mapping jmp,
2096           SequenceI parentseq, SequenceI jds, boolean recurse)
2097   {
2098     Mapping mp = null;
2099     if (jmp.getMap() != null)
2100     {
2101       mp = new Mapping();
2102
2103       jalview.util.MapList mlst = jmp.getMap();
2104       List<int[]> r = mlst.getFromRanges();
2105       for (int[] range : r)
2106       {
2107         MapListFrom mfrom = new MapListFrom();
2108         mfrom.setStart(range[0]);
2109         mfrom.setEnd(range[1]);
2110         mp.addMapListFrom(mfrom);
2111       }
2112       r = mlst.getToRanges();
2113       for (int[] range : r)
2114       {
2115         MapListTo mto = new MapListTo();
2116         mto.setStart(range[0]);
2117         mto.setEnd(range[1]);
2118         mp.addMapListTo(mto);
2119       }
2120       mp.setMapFromUnit(mlst.getFromRatio());
2121       mp.setMapToUnit(mlst.getToRatio());
2122       if (jmp.getTo() != null)
2123       {
2124         MappingChoice mpc = new MappingChoice();
2125
2126         // check/create ID for the sequence referenced by getTo()
2127
2128         String jmpid = "";
2129         SequenceI ps = null;
2130         if (parentseq != jmp.getTo()
2131                 && parentseq.getDatasetSequence() != jmp.getTo())
2132         {
2133           // chaining dbref rather than a handshaking one
2134           jmpid = seqHash(ps = jmp.getTo());
2135         }
2136         else
2137         {
2138           jmpid = seqHash(ps = parentseq);
2139         }
2140         mpc.setDseqFor(jmpid);
2141         if (!seqRefIds.containsKey(mpc.getDseqFor()))
2142         {
2143           jalview.bin.Cache.log.debug("creatign new DseqFor ID");
2144           seqRefIds.put(mpc.getDseqFor(), ps);
2145         }
2146         else
2147         {
2148           jalview.bin.Cache.log.debug("reusing DseqFor ID");
2149         }
2150
2151         mp.setMappingChoice(mpc);
2152       }
2153     }
2154     return mp;
2155   }
2156
2157   String setUserColourScheme(jalview.schemes.ColourSchemeI cs,
2158           List<UserColourScheme> userColours, JalviewModelSequence jms)
2159   {
2160     String id = null;
2161     jalview.schemes.UserColourScheme ucs = (jalview.schemes.UserColourScheme) cs;
2162     boolean newucs = false;
2163     if (!userColours.contains(ucs))
2164     {
2165       userColours.add(ucs);
2166       newucs = true;
2167     }
2168     id = "ucs" + userColours.indexOf(ucs);
2169     if (newucs)
2170     {
2171       // actually create the scheme's entry in the XML model
2172       java.awt.Color[] colours = ucs.getColours();
2173       jalview.schemabinding.version2.UserColours uc = new jalview.schemabinding.version2.UserColours();
2174       jalview.schemabinding.version2.UserColourScheme jbucs = new jalview.schemabinding.version2.UserColourScheme();
2175
2176       for (int i = 0; i < colours.length; i++)
2177       {
2178         jalview.schemabinding.version2.Colour col = new jalview.schemabinding.version2.Colour();
2179         col.setName(ResidueProperties.aa[i]);
2180         col.setRGB(jalview.util.Format.getHexString(colours[i]));
2181         jbucs.addColour(col);
2182       }
2183       if (ucs.getLowerCaseColours() != null)
2184       {
2185         colours = ucs.getLowerCaseColours();
2186         for (int i = 0; i < colours.length; i++)
2187         {
2188           jalview.schemabinding.version2.Colour col = new jalview.schemabinding.version2.Colour();
2189           col.setName(ResidueProperties.aa[i].toLowerCase());
2190           col.setRGB(jalview.util.Format.getHexString(colours[i]));
2191           jbucs.addColour(col);
2192         }
2193       }
2194
2195       uc.setId(id);
2196       uc.setUserColourScheme(jbucs);
2197       jms.addUserColours(uc);
2198     }
2199
2200     return id;
2201   }
2202
2203   jalview.schemes.UserColourScheme getUserColourScheme(
2204           JalviewModelSequence jms, String id)
2205   {
2206     UserColours[] uc = jms.getUserColours();
2207     UserColours colours = null;
2208
2209     for (int i = 0; i < uc.length; i++)
2210     {
2211       if (uc[i].getId().equals(id))
2212       {
2213         colours = uc[i];
2214
2215         break;
2216       }
2217     }
2218
2219     java.awt.Color[] newColours = new java.awt.Color[24];
2220
2221     for (int i = 0; i < 24; i++)
2222     {
2223       newColours[i] = new java.awt.Color(Integer.parseInt(colours
2224               .getUserColourScheme().getColour(i).getRGB(), 16));
2225     }
2226
2227     jalview.schemes.UserColourScheme ucs = new jalview.schemes.UserColourScheme(
2228             newColours);
2229
2230     if (colours.getUserColourScheme().getColourCount() > 24)
2231     {
2232       newColours = new java.awt.Color[23];
2233       for (int i = 0; i < 23; i++)
2234       {
2235         newColours[i] = new java.awt.Color(Integer.parseInt(colours
2236                 .getUserColourScheme().getColour(i + 24).getRGB(), 16));
2237       }
2238       ucs.setLowerCaseColours(newColours);
2239     }
2240
2241     return ucs;
2242   }
2243
2244   /**
2245    * contains last error message (if any) encountered by XML loader.
2246    */
2247   String errorMessage = null;
2248
2249   /**
2250    * flag to control whether the Jalview2XML_V1 parser should be deferred to if
2251    * exceptions are raised during project XML parsing
2252    */
2253   public boolean attemptversion1parse = true;
2254
2255   /**
2256    * Load a jalview project archive from a jar file
2257    * 
2258    * @param file
2259    *          - HTTP URL or filename
2260    */
2261   public AlignFrame loadJalviewAlign(final String file)
2262   {
2263
2264     jalview.gui.AlignFrame af = null;
2265
2266     try
2267     {
2268       // create list to store references for any new Jmol viewers created
2269       newStructureViewers = new Vector<JalviewStructureDisplayI>();
2270       // UNMARSHALLER SEEMS TO CLOSE JARINPUTSTREAM, MOST ANNOYING
2271       // Workaround is to make sure caller implements the JarInputStreamProvider
2272       // interface
2273       // so we can re-open the jar input stream for each entry.
2274
2275       jarInputStreamProvider jprovider = createjarInputStreamProvider(file);
2276       af = loadJalviewAlign(jprovider);
2277
2278     } catch (MalformedURLException e)
2279     {
2280       errorMessage = "Invalid URL format for '" + file + "'";
2281       reportErrors();
2282     } finally
2283     {
2284       try
2285       {
2286         SwingUtilities.invokeAndWait(new Runnable()
2287         {
2288           @Override
2289           public void run()
2290           {
2291             setLoadingFinishedForNewStructureViewers();
2292           };
2293         });
2294       } catch (Exception x)
2295       {
2296         System.err.println("Error loading alignment: " + x.getMessage());
2297       }
2298     }
2299     return af;
2300   }
2301
2302   private jarInputStreamProvider createjarInputStreamProvider(
2303           final String file) throws MalformedURLException
2304   {
2305     URL url = null;
2306     errorMessage = null;
2307     uniqueSetSuffix = null;
2308     seqRefIds = null;
2309     viewportsAdded.clear();
2310     frefedSequence = null;
2311
2312     if (file.startsWith("http://"))
2313     {
2314       url = new URL(file);
2315     }
2316     final URL _url = url;
2317     return new jarInputStreamProvider()
2318     {
2319
2320       @Override
2321       public JarInputStream getJarInputStream() throws IOException
2322       {
2323         if (_url != null)
2324         {
2325           return new JarInputStream(_url.openStream());
2326         }
2327         else
2328         {
2329           return new JarInputStream(new FileInputStream(file));
2330         }
2331       }
2332
2333       @Override
2334       public String getFilename()
2335       {
2336         return file;
2337       }
2338     };
2339   }
2340
2341   /**
2342    * Recover jalview session from a jalview project archive. Caller may
2343    * initialise uniqueSetSuffix, seqRefIds, viewportsAdded and frefedSequence
2344    * themselves. Any null fields will be initialised with default values,
2345    * non-null fields are left alone.
2346    * 
2347    * @param jprovider
2348    * @return
2349    */
2350   public AlignFrame loadJalviewAlign(final jarInputStreamProvider jprovider)
2351   {
2352     errorMessage = null;
2353     if (uniqueSetSuffix == null)
2354     {
2355       uniqueSetSuffix = System.currentTimeMillis() % 100000 + "";
2356     }
2357     if (seqRefIds == null)
2358     {
2359       initSeqRefs();
2360     }
2361     AlignFrame af = null, _af = null;
2362     IdentityHashMap<AlignmentI, AlignmentI> importedDatasets = new IdentityHashMap<AlignmentI, AlignmentI>();
2363     Map<String, AlignFrame> gatherToThisFrame = new HashMap<String, AlignFrame>();
2364     final String file = jprovider.getFilename();
2365     try
2366     {
2367       JarInputStream jin = null;
2368       JarEntry jarentry = null;
2369       int entryCount = 1;
2370
2371       do
2372       {
2373         jin = jprovider.getJarInputStream();
2374         for (int i = 0; i < entryCount; i++)
2375         {
2376           jarentry = jin.getNextJarEntry();
2377         }
2378
2379         if (jarentry != null && jarentry.getName().endsWith(".xml"))
2380         {
2381           InputStreamReader in = new InputStreamReader(jin, UTF_8);
2382           JalviewModel object = new JalviewModel();
2383
2384           Unmarshaller unmar = new Unmarshaller(object);
2385           unmar.setValidation(false);
2386           object = (JalviewModel) unmar.unmarshal(in);
2387           if (true) // !skipViewport(object))
2388           {
2389             _af = loadFromObject(object, file, true, jprovider);
2390             if (_af != null
2391                     && object.getJalviewModelSequence().getViewportCount() > 0)
2392             {
2393               if (af == null)
2394               {
2395                 // store a reference to the first view
2396                 af = _af;
2397               }
2398               if (_af.viewport.isGatherViewsHere())
2399               {
2400                 // if this is a gathered view, keep its reference since
2401                 // after gathering views, only this frame will remain
2402                 af = _af;
2403                 gatherToThisFrame.put(_af.viewport.getSequenceSetId(), _af);
2404               }
2405               // Save dataset to register mappings once all resolved
2406               importedDatasets.put(af.viewport.getAlignment().getDataset(),
2407                       af.viewport.getAlignment().getDataset());
2408             }
2409           }
2410           entryCount++;
2411         }
2412         else if (jarentry != null)
2413         {
2414           // Some other file here.
2415           entryCount++;
2416         }
2417       } while (jarentry != null);
2418       resolveFrefedSequences();
2419     } catch (IOException ex)
2420     {
2421       ex.printStackTrace();
2422       errorMessage = "Couldn't locate Jalview XML file : " + file;
2423       System.err.println("Exception whilst loading jalview XML file : "
2424               + ex + "\n");
2425     } catch (Exception ex)
2426     {
2427       System.err.println("Parsing as Jalview Version 2 file failed.");
2428       ex.printStackTrace(System.err);
2429       if (attemptversion1parse)
2430       {
2431         // Is Version 1 Jar file?
2432         try
2433         {
2434           af = new Jalview2XML_V1(raiseGUI).LoadJalviewAlign(jprovider);
2435         } catch (Exception ex2)
2436         {
2437           System.err.println("Exception whilst loading as jalviewXMLV1:");
2438           ex2.printStackTrace();
2439           af = null;
2440         }
2441       }
2442       if (Desktop.instance != null)
2443       {
2444         Desktop.instance.stopLoading();
2445       }
2446       if (af != null)
2447       {
2448         System.out.println("Successfully loaded archive file");
2449         return af;
2450       }
2451       ex.printStackTrace();
2452
2453       System.err.println("Exception whilst loading jalview XML file : "
2454               + ex + "\n");
2455     } catch (OutOfMemoryError e)
2456     {
2457       // Don't use the OOM Window here
2458       errorMessage = "Out of memory loading jalview XML file";
2459       System.err.println("Out of memory whilst loading jalview XML file");
2460       e.printStackTrace();
2461     }
2462
2463     /*
2464      * Regather multiple views (with the same sequence set id) to the frame (if
2465      * any) that is flagged as the one to gather to, i.e. convert them to tabbed
2466      * views instead of separate frames. Note this doesn't restore a state where
2467      * some expanded views in turn have tabbed views - the last "first tab" read
2468      * in will play the role of gatherer for all.
2469      */
2470     for (AlignFrame fr : gatherToThisFrame.values())
2471     {
2472       Desktop.instance.gatherViews(fr);
2473     }
2474
2475     restoreSplitFrames();
2476     for (AlignmentI ds : importedDatasets.keySet())
2477     {
2478       if (ds.getCodonFrames() != null)
2479       {
2480         StructureSelectionManager.getStructureSelectionManager(
2481                 Desktop.instance).registerMappings(ds.getCodonFrames());
2482       }
2483     }
2484     if (errorMessage != null)
2485     {
2486       reportErrors();
2487     }
2488
2489     if (Desktop.instance != null)
2490     {
2491       Desktop.instance.stopLoading();
2492     }
2493
2494     return af;
2495   }
2496
2497   /**
2498    * Try to reconstruct and display SplitFrame windows, where each contains
2499    * complementary dna and protein alignments. Done by pairing up AlignFrame
2500    * objects (created earlier) which have complementary viewport ids associated.
2501    */
2502   protected void restoreSplitFrames()
2503   {
2504     List<SplitFrame> gatherTo = new ArrayList<SplitFrame>();
2505     List<AlignFrame> addedToSplitFrames = new ArrayList<AlignFrame>();
2506     Map<String, AlignFrame> dna = new HashMap<String, AlignFrame>();
2507
2508     /*
2509      * Identify the DNA alignments
2510      */
2511     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
2512             .entrySet())
2513     {
2514       AlignFrame af = candidate.getValue();
2515       if (af.getViewport().getAlignment().isNucleotide())
2516       {
2517         dna.put(candidate.getKey().getId(), af);
2518       }
2519     }
2520
2521     /*
2522      * Try to match up the protein complements
2523      */
2524     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
2525             .entrySet())
2526     {
2527       AlignFrame af = candidate.getValue();
2528       if (!af.getViewport().getAlignment().isNucleotide())
2529       {
2530         String complementId = candidate.getKey().getComplementId();
2531         // only non-null complements should be in the Map
2532         if (complementId != null && dna.containsKey(complementId))
2533         {
2534           final AlignFrame dnaFrame = dna.get(complementId);
2535           SplitFrame sf = createSplitFrame(dnaFrame, af);
2536           addedToSplitFrames.add(dnaFrame);
2537           addedToSplitFrames.add(af);
2538           if (af.viewport.isGatherViewsHere())
2539           {
2540             gatherTo.add(sf);
2541           }
2542         }
2543       }
2544     }
2545
2546     /*
2547      * Open any that we failed to pair up (which shouldn't happen!) as
2548      * standalone AlignFrame's.
2549      */
2550     for (Entry<Viewport, AlignFrame> candidate : splitFrameCandidates
2551             .entrySet())
2552     {
2553       AlignFrame af = candidate.getValue();
2554       if (!addedToSplitFrames.contains(af))
2555       {
2556         Viewport view = candidate.getKey();
2557         Desktop.addInternalFrame(af, view.getTitle(), view.getWidth(),
2558                 view.getHeight());
2559         System.err.println("Failed to restore view " + view.getTitle()
2560                 + " to split frame");
2561       }
2562     }
2563
2564     /*
2565      * Gather back into tabbed views as flagged.
2566      */
2567     for (SplitFrame sf : gatherTo)
2568     {
2569       Desktop.instance.gatherViews(sf);
2570     }
2571
2572     splitFrameCandidates.clear();
2573   }
2574
2575   /**
2576    * Construct and display one SplitFrame holding DNA and protein alignments.
2577    * 
2578    * @param dnaFrame
2579    * @param proteinFrame
2580    * @return
2581    */
2582   protected SplitFrame createSplitFrame(AlignFrame dnaFrame,
2583           AlignFrame proteinFrame)
2584   {
2585     SplitFrame splitFrame = new SplitFrame(dnaFrame, proteinFrame);
2586     String title = MessageManager.getString("label.linked_view_title");
2587     int width = (int) dnaFrame.getBounds().getWidth();
2588     int height = (int) (dnaFrame.getBounds().getHeight()
2589             + proteinFrame.getBounds().getHeight() + 50);
2590
2591     /*
2592      * SplitFrame location is saved to both enclosed frames
2593      */
2594     splitFrame.setLocation(dnaFrame.getX(), dnaFrame.getY());
2595     Desktop.addInternalFrame(splitFrame, title, width, height);
2596
2597     /*
2598      * And compute cDNA consensus (couldn't do earlier with consensus as
2599      * mappings were not yet present)
2600      */
2601     proteinFrame.viewport.alignmentChanged(proteinFrame.alignPanel);
2602
2603     return splitFrame;
2604   }
2605
2606   /**
2607    * check errorMessage for a valid error message and raise an error box in the
2608    * GUI or write the current errorMessage to stderr and then clear the error
2609    * state.
2610    */
2611   protected void reportErrors()
2612   {
2613     reportErrors(false);
2614   }
2615
2616   protected void reportErrors(final boolean saving)
2617   {
2618     if (errorMessage != null)
2619     {
2620       final String finalErrorMessage = errorMessage;
2621       if (raiseGUI)
2622       {
2623         javax.swing.SwingUtilities.invokeLater(new Runnable()
2624         {
2625           @Override
2626           public void run()
2627           {
2628             JOptionPane.showInternalMessageDialog(Desktop.desktop,
2629                     finalErrorMessage, "Error "
2630                             + (saving ? "saving" : "loading")
2631                             + " Jalview file", JOptionPane.WARNING_MESSAGE);
2632           }
2633         });
2634       }
2635       else
2636       {
2637         System.err.println("Problem loading Jalview file: " + errorMessage);
2638       }
2639     }
2640     errorMessage = null;
2641   }
2642
2643   Map<String, String> alreadyLoadedPDB = new HashMap<String, String>();
2644
2645   /**
2646    * when set, local views will be updated from view stored in JalviewXML
2647    * Currently (28th Sep 2008) things will go horribly wrong in vamsas document
2648    * sync if this is set to true.
2649    */
2650   private final boolean updateLocalViews = false;
2651
2652   /**
2653    * Returns the path to a temporary file holding the PDB file for the given PDB
2654    * id. The first time of asking, searches for a file of that name in the
2655    * Jalview project jar, and copies it to a new temporary file. Any repeat
2656    * requests just return the path to the file previously created.
2657    * 
2658    * @param jprovider
2659    * @param pdbId
2660    * @return
2661    */
2662   String loadPDBFile(jarInputStreamProvider jprovider, String pdbId,
2663           String origFile)
2664   {
2665     if (alreadyLoadedPDB.containsKey(pdbId))
2666     {
2667       return alreadyLoadedPDB.get(pdbId).toString();
2668     }
2669
2670     String tempFile = copyJarEntry(jprovider, pdbId, "jalview_pdb",
2671             origFile);
2672     if (tempFile != null)
2673     {
2674       alreadyLoadedPDB.put(pdbId, tempFile);
2675     }
2676     return tempFile;
2677   }
2678
2679   /**
2680    * Copies the jar entry of given name to a new temporary file and returns the
2681    * path to the file, or null if the entry is not found.
2682    * 
2683    * @param jprovider
2684    * @param jarEntryName
2685    * @param prefix
2686    *          a prefix for the temporary file name, must be at least three
2687    *          characters long
2688    * @param origFile
2689    *          null or original file - so new file can be given the same suffix
2690    *          as the old one
2691    * @return
2692    */
2693   protected String copyJarEntry(jarInputStreamProvider jprovider,
2694           String jarEntryName, String prefix, String origFile)
2695   {
2696     BufferedReader in = null;
2697     PrintWriter out = null;
2698     String suffix = ".tmp";
2699     if (origFile == null)
2700     {
2701       origFile = jarEntryName;
2702     }
2703     int sfpos = origFile.lastIndexOf(".");
2704     if (sfpos > -1 && sfpos < (origFile.length() - 3))
2705     {
2706       suffix = "." + origFile.substring(sfpos + 1);
2707     }
2708     try
2709     {
2710       JarInputStream jin = jprovider.getJarInputStream();
2711       /*
2712        * if (jprovider.startsWith("http://")) { jin = new JarInputStream(new
2713        * URL(jprovider).openStream()); } else { jin = new JarInputStream(new
2714        * FileInputStream(jprovider)); }
2715        */
2716
2717       JarEntry entry = null;
2718       do
2719       {
2720         entry = jin.getNextJarEntry();
2721       } while (entry != null && !entry.getName().equals(jarEntryName));
2722       if (entry != null)
2723       {
2724         in = new BufferedReader(new InputStreamReader(jin, UTF_8));
2725         File outFile = File.createTempFile(prefix, suffix);
2726         outFile.deleteOnExit();
2727         out = new PrintWriter(new FileOutputStream(outFile));
2728         String data;
2729
2730         while ((data = in.readLine()) != null)
2731         {
2732           out.println(data);
2733         }
2734         out.flush();
2735         String t = outFile.getAbsolutePath();
2736         return t;
2737       }
2738       else
2739       {
2740         warn("Couldn't find entry in Jalview Jar for " + jarEntryName);
2741       }
2742     } catch (Exception ex)
2743     {
2744       ex.printStackTrace();
2745     } finally
2746     {
2747       if (in != null)
2748       {
2749         try
2750         {
2751           in.close();
2752         } catch (IOException e)
2753         {
2754           // ignore
2755         }
2756       }
2757       if (out != null)
2758       {
2759         out.close();
2760       }
2761     }
2762
2763     return null;
2764   }
2765
2766   private class JvAnnotRow
2767   {
2768     public JvAnnotRow(int i, AlignmentAnnotation jaa)
2769     {
2770       order = i;
2771       template = jaa;
2772     }
2773
2774     /**
2775      * persisted version of annotation row from which to take vis properties
2776      */
2777     public jalview.datamodel.AlignmentAnnotation template;
2778
2779     /**
2780      * original position of the annotation row in the alignment
2781      */
2782     public int order;
2783   }
2784
2785   /**
2786    * Load alignment frame from jalview XML DOM object
2787    * 
2788    * @param object
2789    *          DOM
2790    * @param file
2791    *          filename source string
2792    * @param loadTreesAndStructures
2793    *          when false only create Viewport
2794    * @param jprovider
2795    *          data source provider
2796    * @return alignment frame created from view stored in DOM
2797    */
2798   AlignFrame loadFromObject(JalviewModel object, String file,
2799           boolean loadTreesAndStructures, jarInputStreamProvider jprovider)
2800   {
2801     SequenceSet vamsasSet = object.getVamsasModel().getSequenceSet(0);
2802     Sequence[] vamsasSeq = vamsasSet.getSequence();
2803
2804     JalviewModelSequence jms = object.getJalviewModelSequence();
2805
2806     Viewport view = (jms.getViewportCount() > 0) ? jms.getViewport(0)
2807             : null;
2808
2809     // ////////////////////////////////
2810     // LOAD SEQUENCES
2811
2812     List<SequenceI> hiddenSeqs = null;
2813
2814
2815     List<SequenceI> tmpseqs = new ArrayList<SequenceI>();
2816
2817     boolean multipleView = false;
2818     SequenceI referenceseqForView = null;
2819     JSeq[] jseqs = object.getJalviewModelSequence().getJSeq();
2820     int vi = 0; // counter in vamsasSeq array
2821     for (int i = 0; i < jseqs.length; i++)
2822     {
2823       String seqId = jseqs[i].getId();
2824
2825       SequenceI tmpSeq = seqRefIds.get(seqId);
2826       if (tmpSeq != null)
2827       {
2828         if (!incompleteSeqs.containsKey(seqId))
2829         {
2830           // may not need this check, but keep it for at least 2.9,1 release
2831           if (tmpSeq.getStart()!=jseqs[i].getStart() || tmpSeq.getEnd()!=jseqs[i].getEnd())
2832           { 
2833             System.err
2834                     .println("Warning JAL-2154 regression: updating start/end for sequence "
2835                             + tmpSeq.toString() + " to " + jseqs[i]);
2836           }
2837         } else {
2838           incompleteSeqs.remove(seqId);
2839         }
2840         if (vamsasSeq.length > vi && vamsasSeq[vi].getId().equals(seqId))
2841         {
2842           // most likely we are reading a dataset XML document so
2843           // update from vamsasSeq section of XML for this sequence
2844           tmpSeq.setName(vamsasSeq[vi].getName());
2845           tmpSeq.setDescription(vamsasSeq[vi].getDescription());
2846           tmpSeq.setSequence(vamsasSeq[vi].getSequence());
2847           vi++;
2848         }
2849         else
2850         {
2851           // reading multiple views, so vamsasSeq set is a subset of JSeq
2852           multipleView = true;
2853         }
2854         tmpSeq.setStart(jseqs[i].getStart());
2855         tmpSeq.setEnd(jseqs[i].getEnd());
2856         tmpseqs.add(tmpSeq);
2857       }
2858       else
2859       {
2860         tmpSeq = new jalview.datamodel.Sequence(vamsasSeq[vi].getName(),
2861                 vamsasSeq[vi].getSequence());
2862         tmpSeq.setDescription(vamsasSeq[vi].getDescription());
2863         tmpSeq.setStart(jseqs[i].getStart());
2864         tmpSeq.setEnd(jseqs[i].getEnd());
2865         tmpSeq.setVamsasId(uniqueSetSuffix + seqId);
2866         seqRefIds.put(vamsasSeq[vi].getId(), tmpSeq);
2867         tmpseqs.add(tmpSeq);
2868         vi++;
2869       }
2870
2871       if (jseqs[i].hasViewreference() && jseqs[i].getViewreference())
2872       {
2873         referenceseqForView = tmpseqs.get(tmpseqs.size() - 1);
2874       }
2875
2876       if (jseqs[i].getHidden())
2877       {
2878         if (hiddenSeqs == null)
2879         {
2880           hiddenSeqs = new ArrayList<SequenceI>();
2881         }
2882
2883         hiddenSeqs.add(tmpSeq);
2884       }
2885     }
2886
2887     // /
2888     // Create the alignment object from the sequence set
2889     // ///////////////////////////////
2890     SequenceI[] orderedSeqs = tmpseqs
2891             .toArray(new SequenceI[tmpseqs.size()]);
2892
2893     AlignmentI al = null;
2894     // so we must create or recover the dataset alignment before going further
2895     // ///////////////////////////////
2896     if (vamsasSet.getDatasetId() == null || vamsasSet.getDatasetId() == "")
2897     {
2898       // older jalview projects do not have a dataset - so creat alignment and
2899       // dataset
2900       al = new Alignment(orderedSeqs);
2901       al.setDataset(null);
2902     }
2903     else
2904     {
2905       boolean isdsal = object.getJalviewModelSequence().getViewportCount() == 0;
2906       if (isdsal)
2907       {
2908         // we are importing a dataset record, so
2909         // recover reference to an alignment already materialsed as dataset
2910         al = getDatasetFor(vamsasSet.getDatasetId());
2911       }
2912       if (al == null)
2913       {
2914         // materialse the alignment
2915         al = new Alignment(orderedSeqs);
2916       }
2917       if (isdsal)
2918       {
2919         addDatasetRef(vamsasSet.getDatasetId(), al);
2920       }
2921
2922       // finally, verify all data in vamsasSet is actually present in al
2923       // passing on flag indicating if it is actually a stored dataset
2924       recoverDatasetFor(vamsasSet, al, isdsal);
2925     }
2926
2927     if (referenceseqForView != null)
2928     {
2929       al.setSeqrep(referenceseqForView);
2930     }
2931     // / Add the alignment properties
2932     for (int i = 0; i < vamsasSet.getSequenceSetPropertiesCount(); i++)
2933     {
2934       SequenceSetProperties ssp = vamsasSet.getSequenceSetProperties(i);
2935       al.setProperty(ssp.getKey(), ssp.getValue());
2936     }
2937
2938     // ///////////////////////////////
2939
2940     Hashtable pdbloaded = new Hashtable(); // TODO nothing writes to this??
2941     if (!multipleView)
2942     {
2943       // load sequence features, database references and any associated PDB
2944       // structures for the alignment
2945       //
2946       // prior to 2.10, this part would only be executed the first time a
2947       // sequence was encountered, but not afterwards.
2948       // now, for 2.10 projects, this is also done if the xml doc includes
2949       // dataset sequences not actually present in any particular view.
2950       //
2951       for (int i = 0; i < vamsasSeq.length; i++)
2952       {
2953         if (jseqs[i].getFeaturesCount() > 0)
2954         {
2955           Features[] features = jseqs[i].getFeatures();
2956           for (int f = 0; f < features.length; f++)
2957           {
2958             jalview.datamodel.SequenceFeature sf = new jalview.datamodel.SequenceFeature(
2959                     features[f].getType(), features[f].getDescription(),
2960                     features[f].getStatus(), features[f].getBegin(),
2961                     features[f].getEnd(), features[f].getFeatureGroup());
2962
2963             sf.setScore(features[f].getScore());
2964             for (int od = 0; od < features[f].getOtherDataCount(); od++)
2965             {
2966               OtherData keyValue = features[f].getOtherData(od);
2967               if (keyValue.getKey().startsWith("LINK"))
2968               {
2969                 sf.addLink(keyValue.getValue());
2970               }
2971               else
2972               {
2973                 sf.setValue(keyValue.getKey(), keyValue.getValue());
2974               }
2975
2976             }
2977             // adds feature to datasequence's feature set (since Jalview 2.10)
2978             al.getSequenceAt(i).addSequenceFeature(sf);
2979           }
2980         }
2981         if (vamsasSeq[i].getDBRefCount() > 0)
2982         {
2983           // adds dbrefs to datasequence's set (since Jalview 2.10)
2984           addDBRefs(
2985                   al.getSequenceAt(i).getDatasetSequence() == null ? al.getSequenceAt(i)
2986                           : al.getSequenceAt(i).getDatasetSequence(),
2987                   vamsasSeq[i]);
2988         }
2989         if (jseqs[i].getPdbidsCount() > 0)
2990         {
2991           Pdbids[] ids = jseqs[i].getPdbids();
2992           for (int p = 0; p < ids.length; p++)
2993           {
2994             jalview.datamodel.PDBEntry entry = new jalview.datamodel.PDBEntry();
2995             entry.setId(ids[p].getId());
2996             if (ids[p].getType() != null)
2997             {
2998               if (PDBEntry.Type.getType(ids[p].getType()) != null)
2999               {
3000                 entry.setType(PDBEntry.Type.getType(ids[p].getType()));
3001               }
3002               else
3003               {
3004                 entry.setType(PDBEntry.Type.FILE);
3005               }
3006             }
3007             if (ids[p].getFile() != null)
3008             {
3009               if (!pdbloaded.containsKey(ids[p].getFile()))
3010               {
3011                 entry.setFile(loadPDBFile(jprovider, ids[p].getId(),
3012                         ids[p].getFile()));
3013               }
3014               else
3015               {
3016                 entry.setFile(pdbloaded.get(ids[p].getId()).toString());
3017               }
3018             }
3019             if (ids[p].getPdbentryItem() != null)
3020             {
3021               for (PdbentryItem item : ids[p].getPdbentryItem())
3022               {
3023                 for (Property pr : item.getProperty())
3024                 {
3025                   entry.setProperty(pr.getName(), pr.getValue());
3026                 }
3027               }
3028             }
3029             StructureSelectionManager.getStructureSelectionManager(
3030                     Desktop.instance).registerPDBEntry(entry);
3031             // adds PDBEntry to datasequence's set (since Jalview 2.10)
3032             if (al.getSequenceAt(i).getDatasetSequence() != null)
3033             {
3034               al.getSequenceAt(i).getDatasetSequence().addPDBId(entry);
3035             }
3036             else
3037             {
3038               al.getSequenceAt(i).addPDBId(entry);
3039             }
3040           }
3041         }
3042       }
3043     } // end !multipleview
3044
3045     // ///////////////////////////////
3046     // LOAD SEQUENCE MAPPINGS
3047
3048     if (vamsasSet.getAlcodonFrameCount() > 0)
3049     {
3050       // TODO Potentially this should only be done once for all views of an
3051       // alignment
3052       AlcodonFrame[] alc = vamsasSet.getAlcodonFrame();
3053       for (int i = 0; i < alc.length; i++)
3054       {
3055         AlignedCodonFrame cf = new AlignedCodonFrame();
3056         if (alc[i].getAlcodMapCount() > 0)
3057         {
3058           AlcodMap[] maps = alc[i].getAlcodMap();
3059           for (int m = 0; m < maps.length; m++)
3060           {
3061             SequenceI dnaseq = seqRefIds.get(maps[m].getDnasq());
3062             // Load Mapping
3063             jalview.datamodel.Mapping mapping = null;
3064             // attach to dna sequence reference.
3065             if (maps[m].getMapping() != null)
3066             {
3067               mapping = addMapping(maps[m].getMapping());
3068               if (dnaseq != null && mapping.getTo() != null)
3069               {
3070                 cf.addMap(dnaseq, mapping.getTo(), mapping.getMap());
3071               }
3072               else
3073               {
3074                 // defer to later
3075                 frefedSequence.add(newAlcodMapRef(maps[m].getDnasq(), cf,
3076                         mapping));
3077               }
3078             }
3079           }
3080           al.addCodonFrame(cf);
3081         }
3082       }
3083     }
3084
3085     // ////////////////////////////////
3086     // LOAD ANNOTATIONS
3087     List<JvAnnotRow> autoAlan = new ArrayList<JvAnnotRow>();
3088
3089     /*
3090      * store any annotations which forward reference a group's ID
3091      */
3092     Map<String, List<AlignmentAnnotation>> groupAnnotRefs = new Hashtable<String, List<AlignmentAnnotation>>();
3093
3094     if (vamsasSet.getAnnotationCount() > 0)
3095     {
3096       Annotation[] an = vamsasSet.getAnnotation();
3097
3098       for (int i = 0; i < an.length; i++)
3099       {
3100         Annotation annotation = an[i];
3101
3102         /**
3103          * test if annotation is automatically calculated for this view only
3104          */
3105         boolean autoForView = false;
3106         if (annotation.getLabel().equals("Quality")
3107                 || annotation.getLabel().equals("Conservation")
3108                 || annotation.getLabel().equals("Consensus"))
3109         {
3110           // Kludge for pre 2.5 projects which lacked the autocalculated flag
3111           autoForView = true;
3112           if (!annotation.hasAutoCalculated())
3113           {
3114             annotation.setAutoCalculated(true);
3115           }
3116         }
3117         if (autoForView
3118                 || (annotation.hasAutoCalculated() && annotation
3119                         .isAutoCalculated()))
3120         {
3121           // remove ID - we don't recover annotation from other views for
3122           // view-specific annotation
3123           annotation.setId(null);
3124         }
3125
3126         // set visiblity for other annotation in this view
3127         String annotationId = annotation.getId();
3128         if (annotationId != null && annotationIds.containsKey(annotationId))
3129         {
3130           AlignmentAnnotation jda = annotationIds.get(annotationId);
3131           // in principle Visible should always be true for annotation displayed
3132           // in multiple views
3133           if (annotation.hasVisible())
3134           {
3135             jda.visible = annotation.getVisible();
3136           }
3137
3138           al.addAnnotation(jda);
3139
3140           continue;
3141         }
3142         // Construct new annotation from model.
3143         AnnotationElement[] ae = annotation.getAnnotationElement();
3144         jalview.datamodel.Annotation[] anot = null;
3145         java.awt.Color firstColour = null;
3146         int anpos;
3147         if (!annotation.getScoreOnly())
3148         {
3149           anot = new jalview.datamodel.Annotation[al.getWidth()];
3150           for (int aa = 0; aa < ae.length && aa < anot.length; aa++)
3151           {
3152             anpos = ae[aa].getPosition();
3153
3154             if (anpos >= anot.length)
3155             {
3156               continue;
3157             }
3158
3159             anot[anpos] = new jalview.datamodel.Annotation(
3160
3161             ae[aa].getDisplayCharacter(), ae[aa].getDescription(),
3162                     (ae[aa].getSecondaryStructure() == null || ae[aa]
3163                             .getSecondaryStructure().length() == 0) ? ' '
3164                             : ae[aa].getSecondaryStructure().charAt(0),
3165                     ae[aa].getValue()
3166
3167             );
3168             // JBPNote: Consider verifying dataflow for IO of secondary
3169             // structure annotation read from Stockholm files
3170             // this was added to try to ensure that
3171             // if (anot[ae[aa].getPosition()].secondaryStructure>' ')
3172             // {
3173             // anot[ae[aa].getPosition()].displayCharacter = "";
3174             // }
3175             anot[anpos].colour = new java.awt.Color(ae[aa].getColour());
3176             if (firstColour == null)
3177             {
3178               firstColour = anot[anpos].colour;
3179             }
3180           }
3181         }
3182         jalview.datamodel.AlignmentAnnotation jaa = null;
3183
3184         if (annotation.getGraph())
3185         {
3186           float llim = 0, hlim = 0;
3187           // if (autoForView || an[i].isAutoCalculated()) {
3188           // hlim=11f;
3189           // }
3190           jaa = new jalview.datamodel.AlignmentAnnotation(
3191                   annotation.getLabel(), annotation.getDescription(), anot,
3192                   llim, hlim, annotation.getGraphType());
3193
3194           jaa.graphGroup = annotation.getGraphGroup();
3195           jaa._linecolour = firstColour;
3196           if (annotation.getThresholdLine() != null)
3197           {
3198             jaa.setThreshold(new jalview.datamodel.GraphLine(annotation
3199                     .getThresholdLine().getValue(), annotation
3200                     .getThresholdLine().getLabel(), new java.awt.Color(
3201                     annotation.getThresholdLine().getColour())));
3202
3203           }
3204           if (autoForView || annotation.isAutoCalculated())
3205           {
3206             // Hardwire the symbol display line to ensure that labels for
3207             // histograms are displayed
3208             jaa.hasText = true;
3209           }
3210         }
3211         else
3212         {
3213           jaa = new jalview.datamodel.AlignmentAnnotation(an[i].getLabel(),
3214                   an[i].getDescription(), anot);
3215           jaa._linecolour = firstColour;
3216         }
3217         // register new annotation
3218         if (an[i].getId() != null)
3219         {
3220           annotationIds.put(an[i].getId(), jaa);
3221           jaa.annotationId = an[i].getId();
3222         }
3223         // recover sequence association
3224         String sequenceRef = an[i].getSequenceRef();
3225         if (sequenceRef != null)
3226         {
3227           // from 2.9 sequenceRef is to sequence id (JAL-1781)
3228           SequenceI sequence = seqRefIds.get(sequenceRef);
3229           if (sequence == null)
3230           {
3231             // in pre-2.9 projects sequence ref is to sequence name
3232             sequence = al.findName(sequenceRef);
3233           }
3234           if (sequence != null)
3235           {
3236             jaa.createSequenceMapping(sequence, 1, true);
3237             sequence.addAlignmentAnnotation(jaa);
3238           }
3239         }
3240         // and make a note of any group association
3241         if (an[i].getGroupRef() != null && an[i].getGroupRef().length() > 0)
3242         {
3243           List<jalview.datamodel.AlignmentAnnotation> aal = groupAnnotRefs
3244                   .get(an[i].getGroupRef());
3245           if (aal == null)
3246           {
3247             aal = new ArrayList<jalview.datamodel.AlignmentAnnotation>();
3248             groupAnnotRefs.put(an[i].getGroupRef(), aal);
3249           }
3250           aal.add(jaa);
3251         }
3252
3253         if (an[i].hasScore())
3254         {
3255           jaa.setScore(an[i].getScore());
3256         }
3257         if (an[i].hasVisible())
3258         {
3259           jaa.visible = an[i].getVisible();
3260         }
3261
3262         if (an[i].hasCentreColLabels())
3263         {
3264           jaa.centreColLabels = an[i].getCentreColLabels();
3265         }
3266
3267         if (an[i].hasScaleColLabels())
3268         {
3269           jaa.scaleColLabel = an[i].getScaleColLabels();
3270         }
3271         if (an[i].hasAutoCalculated() && an[i].isAutoCalculated())
3272         {
3273           // newer files have an 'autoCalculated' flag and store calculation
3274           // state in viewport properties
3275           jaa.autoCalculated = true; // means annotation will be marked for
3276           // update at end of load.
3277         }
3278         if (an[i].hasGraphHeight())
3279         {
3280           jaa.graphHeight = an[i].getGraphHeight();
3281         }
3282         if (an[i].hasBelowAlignment())
3283         {
3284           jaa.belowAlignment = an[i].isBelowAlignment();
3285         }
3286         jaa.setCalcId(an[i].getCalcId());
3287         if (an[i].getPropertyCount() > 0)
3288         {
3289           for (jalview.schemabinding.version2.Property prop : an[i]
3290                   .getProperty())
3291           {
3292             jaa.setProperty(prop.getName(), prop.getValue());
3293           }
3294         }
3295         if (jaa.autoCalculated)
3296         {
3297           autoAlan.add(new JvAnnotRow(i, jaa));
3298         }
3299         else
3300         // if (!autoForView)
3301         {
3302           // add autocalculated group annotation and any user created annotation
3303           // for the view
3304           al.addAnnotation(jaa);
3305         }
3306       }
3307     }
3308     // ///////////////////////
3309     // LOAD GROUPS
3310     // Create alignment markup and styles for this view
3311     if (jms.getJGroupCount() > 0)
3312     {
3313       JGroup[] groups = jms.getJGroup();
3314       boolean addAnnotSchemeGroup = false;
3315       for (int i = 0; i < groups.length; i++)
3316       {
3317         JGroup jGroup = groups[i];
3318         ColourSchemeI cs = null;
3319         if (jGroup.getColour() != null)
3320         {
3321           if (jGroup.getColour().startsWith("ucs"))
3322           {
3323             cs = getUserColourScheme(jms, jGroup.getColour());
3324           }
3325           else if (jGroup.getColour().equals("AnnotationColourGradient")
3326                   && jGroup.getAnnotationColours() != null)
3327           {
3328             addAnnotSchemeGroup = true;
3329             cs = null;
3330           }
3331           else
3332           {
3333             cs = ColourSchemeProperty.getColour(al, jGroup.getColour());
3334           }
3335
3336           if (cs != null)
3337           {
3338             cs.setThreshold(jGroup.getPidThreshold(), true);
3339           }
3340         }
3341
3342         Vector<SequenceI> seqs = new Vector<SequenceI>();
3343
3344         for (int s = 0; s < jGroup.getSeqCount(); s++)
3345         {
3346           String seqId = jGroup.getSeq(s) + "";
3347           SequenceI ts = seqRefIds.get(seqId);
3348
3349           if (ts != null)
3350           {
3351             seqs.addElement(ts);
3352           }
3353         }
3354
3355         if (seqs.size() < 1)
3356         {
3357           continue;
3358         }
3359
3360         SequenceGroup sg = new SequenceGroup(seqs, jGroup.getName(), cs,
3361                 jGroup.getDisplayBoxes(), jGroup.getDisplayText(),
3362                 jGroup.getColourText(), jGroup.getStart(), jGroup.getEnd());
3363
3364         sg.setOutlineColour(new java.awt.Color(jGroup.getOutlineColour()));
3365
3366         sg.textColour = new java.awt.Color(jGroup.getTextCol1());
3367         sg.textColour2 = new java.awt.Color(jGroup.getTextCol2());
3368         sg.setShowNonconserved(jGroup.hasShowUnconserved() ? jGroup
3369                 .isShowUnconserved() : false);
3370         sg.thresholdTextColour = jGroup.getTextColThreshold();
3371         if (jGroup.hasShowConsensusHistogram())
3372         {
3373           sg.setShowConsensusHistogram(jGroup.isShowConsensusHistogram());
3374         }
3375         ;
3376         if (jGroup.hasShowSequenceLogo())
3377         {
3378           sg.setshowSequenceLogo(jGroup.isShowSequenceLogo());
3379         }
3380         if (jGroup.hasNormaliseSequenceLogo())
3381         {
3382           sg.setNormaliseSequenceLogo(jGroup.isNormaliseSequenceLogo());
3383         }
3384         if (jGroup.hasIgnoreGapsinConsensus())
3385         {
3386           sg.setIgnoreGapsConsensus(jGroup.getIgnoreGapsinConsensus());
3387         }
3388         if (jGroup.getConsThreshold() != 0)
3389         {
3390           jalview.analysis.Conservation c = new jalview.analysis.Conservation(
3391                   "All", ResidueProperties.propHash, 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 }