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