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