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