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