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