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