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