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