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