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