JAL-3446 test for simpler key mask operation
[jalview.git] / src / jalview / bin / JalviewAppLoader.java
1 package jalview.bin;
2
3 import jalview.api.JalviewApp;
4 import jalview.api.StructureSelectionManagerProvider;
5 import jalview.datamodel.Alignment;
6 import jalview.datamodel.AlignmentI;
7 import jalview.datamodel.AlignmentOrder;
8 import jalview.datamodel.ColumnSelection;
9 import jalview.datamodel.HiddenColumns;
10 import jalview.datamodel.PDBEntry;
11 import jalview.datamodel.Sequence;
12 import jalview.datamodel.SequenceGroup;
13 import jalview.datamodel.SequenceI;
14 import jalview.gui.AlignFrame;
15 import jalview.gui.AlignViewport;
16 import jalview.gui.Desktop;
17 import jalview.io.AnnotationFile;
18 import jalview.io.AppletFormatAdapter;
19 import jalview.io.DataSourceType;
20 import jalview.io.FeaturesFile;
21 import jalview.io.FileFormat;
22 import jalview.io.FileFormatI;
23 import jalview.io.FileFormats;
24 import jalview.io.IdentifyFile;
25 import jalview.io.JPredFile;
26 import jalview.io.JnetAnnotationMaker;
27 import jalview.io.NewickFile;
28 import jalview.structure.SelectionSource;
29 import jalview.structure.StructureSelectionManager;
30 import jalview.util.HttpUtils;
31 import jalview.util.MessageManager;
32
33 import java.awt.EventQueue;
34 import java.io.IOException;
35 import java.net.URL;
36 import java.util.ArrayList;
37 import java.util.List;
38 import java.util.StringTokenizer;
39 import java.util.Vector;
40
41 /**
42  * A class to load parameters for either JalviewLite or Jalview
43  * 
44  * @author hansonr
45  *
46  */
47 public class JalviewAppLoader
48 {
49
50   private JalviewApp app; // Jalview or JalviewJS or JalviewLite
51
52   private boolean debug;
53
54   String separator = "\u00AC"; // JalviewLite note: the default used to
55                                        // be '|', but many sequence IDS include
56                                        // pipes.
57
58   public String getSeparator()
59   {
60     return separator;
61   }
62
63   public void setSeparator(String separator)
64   {
65     this.separator = separator;
66   }
67
68   public JalviewAppLoader(boolean debug)
69   {
70     this.debug = debug;
71   }
72
73   public void load(JalviewApp app)
74   {
75
76     this.app = app;
77
78     String sep = app.getParameter("separator");
79     if (sep != null)
80     {
81       if (sep.length() > 0)
82       {
83         separator = sep;
84       }
85       else
86       {
87         throw new Error(MessageManager
88                 .getString("error.invalid_separator_parameter"));
89       }
90     }
91
92     loadTree();
93     loadScoreFile();
94     loadFeatures();
95     loadAnnotations();
96     loadJnetFile();
97     loadPdbFiles();
98     callInitCallback();
99   }
100
101   /**
102    * Load PDBFiles if any specified by parameter(s). Returns true if loaded,
103    * else false.
104    * 
105    * @param loaderFrame
106    * @return
107    */
108   protected boolean loadPdbFiles()
109   {
110     boolean result = false;
111     /*
112      * <param name="alignpdbfiles" value="false/true"/> Undocumented for 2.6 -
113      * related to JAL-434
114      */
115
116     boolean doAlign = app.getDefaultParameter("alignpdbfiles", false);
117     app.setAlignPdbStructures(doAlign);
118     /*
119      * <param name="PDBfile" value="1gaq.txt PDB|1GAQ|1GAQ|A PDB|1GAQ|1GAQ|B
120      * PDB|1GAQ|1GAQ|C">
121      * 
122      * <param name="PDBfile2" value="1gaq.txt A=SEQA B=SEQB C=SEQB">
123      * 
124      * <param name="PDBfile3" value="1q0o Q45135_9MICO">
125      */
126
127     // Accumulate pdbs here if they are heading for the same view (if
128     // alignPdbStructures is true)
129     Vector<Object[]> pdbs = new Vector<>();
130     // create a lazy matcher if we're asked to
131     jalview.analysis.SequenceIdMatcher matcher = (app
132             .getDefaultParameter("relaxedidmatch", false))
133                     ? new jalview.analysis.SequenceIdMatcher(
134                             app.getViewport().getAlignment()
135                                     .getSequencesArray())
136                     : null;
137
138     int pdbFileCount = 0;
139     String param;
140     do
141     {
142       if (pdbFileCount > 0)
143       {
144         param = app.getParameter("PDBFILE" + pdbFileCount);
145       }
146       else
147       {
148         param = app.getParameter("PDBFILE");
149       }
150
151       if (param != null)
152       {
153         PDBEntry pdb = new PDBEntry();
154
155         String seqstring;
156         SequenceI[] seqs = null;
157         String[] chains = null;
158
159         StringTokenizer st = new StringTokenizer(param, " ");
160
161         if (st.countTokens() < 2)
162         {
163           String sequence = app.getParameter("PDBSEQ");
164           if (sequence != null)
165           {
166             seqs = new SequenceI[] { matcher == null
167                     ? (Sequence) app.getViewport().getAlignment()
168                             .findName(sequence)
169                     : matcher.findIdMatch(sequence) };
170           }
171
172         }
173         else
174         {
175           param = st.nextToken();
176           List<SequenceI> tmp = new ArrayList<>();
177           List<String> tmp2 = new ArrayList<>();
178
179           while (st.hasMoreTokens())
180           {
181             seqstring = st.nextToken();
182             StringTokenizer st2 = new StringTokenizer(seqstring, "=");
183             if (st2.countTokens() > 1)
184             {
185               // This is the chain
186               tmp2.add(st2.nextToken());
187               seqstring = st2.nextToken();
188             }
189             tmp.add(matcher == null
190                     ? (Sequence) app.getViewport().getAlignment()
191                             .findName(seqstring)
192                     : matcher.findIdMatch(seqstring));
193           }
194
195           seqs = tmp.toArray(new SequenceI[tmp.size()]);
196           if (tmp2.size() == tmp.size())
197           {
198             chains = tmp2.toArray(new String[tmp2.size()]);
199           }
200         }
201         pdb.setId(param);
202         ret[0] = param;
203         DataSourceType protocol = resolveFileProtocol(app, ret);
204         // TODO check JAL-357 for files in a jar (CLASSLOADER)
205         pdb.setFile(ret[0]);
206
207         if (seqs != null)
208         {
209           for (int i = 0; i < seqs.length; i++)
210           {
211             if (seqs[i] != null)
212             {
213               ((Sequence) seqs[i]).addPDBId(pdb);
214               StructureSelectionManager
215                       .getStructureSelectionManager(
216                               (StructureSelectionManagerProvider) app)
217                       .registerPDBEntry(pdb);
218             }
219             else
220             {
221               if (debug)
222               {
223                 // this may not really be a problem but we give a warning
224                 // anyway
225                 System.err.println(
226                         "Warning: Possible input parsing error: Null sequence for attachment of PDB (sequence "
227                                 + i + ")");
228               }
229             }
230           }
231
232           if (doAlign)
233           {
234             pdbs.addElement(new Object[] { pdb, seqs, chains, protocol });
235           }
236           else
237           {
238             app.newStructureView(pdb, seqs, chains, protocol);
239           }
240         }
241       }
242
243       pdbFileCount++;
244     } while (param != null || pdbFileCount < 10);
245     if (pdbs.size() > 0)
246     {
247       SequenceI[][] seqs = new SequenceI[pdbs.size()][];
248       PDBEntry[] pdb = new PDBEntry[pdbs.size()];
249       String[][] chains = new String[pdbs.size()][];
250       String[] protocols = new String[pdbs.size()];
251       for (int pdbsi = 0, pdbsiSize = pdbs
252               .size(); pdbsi < pdbsiSize; pdbsi++)
253       {
254         Object[] o = pdbs.elementAt(pdbsi);
255         pdb[pdbsi] = (PDBEntry) o[0];
256         seqs[pdbsi] = (SequenceI[]) o[1];
257         chains[pdbsi] = (String[]) o[2];
258         protocols[pdbsi] = (String) o[3];
259       }
260       app.alignedStructureView(pdb, seqs, chains, protocols);
261       result = true;
262     }
263     return result;
264   }
265
266   /**
267    * Load in a Jnetfile if specified by parameter. Returns true if loaded, else
268    * false.
269    * 
270    * @param alignFrame
271    * @return
272    */
273   protected boolean loadJnetFile()
274   {
275     boolean result = false;
276     String param = app.getParameter("jnetfile");
277     if (param == null)
278     {
279       // jnet became jpred around 2016
280       param = app.getParameter("jpredfile");
281     }
282     if (param != null)
283     {
284       try
285       {
286         ret[0] = param;
287         DataSourceType protocol = resolveFileProtocol(app, ret);
288         JPredFile predictions = new JPredFile(ret[0], protocol);
289         JnetAnnotationMaker.add_annotation(predictions,
290                 app.getViewport().getAlignment(), 0, false);
291         // false == do not add sequence profile from concise output
292         app.getViewport().getAlignment().setupJPredAlignment();
293         app.updateForAnnotations();
294         result = true;
295       } catch (Exception ex)
296       {
297         ex.printStackTrace();
298       }
299     }
300     return result;
301   }
302
303   /**
304    * Load annotations if specified by parameter. Returns true if loaded, else
305    * false.
306    * 
307    * @param alignFrame
308    * @return
309    */
310   protected boolean loadAnnotations()
311   {
312     boolean result = false;
313     String param = app.getParameter("annotations");
314     if (param != null)
315     {
316       ret[0] = param;
317       DataSourceType protocol = resolveFileProtocol(app, ret);
318       param = ret[0];
319       if (new AnnotationFile().annotateAlignmentView(app.getViewport(),
320               param, protocol))
321       {
322         app.updateForAnnotations();
323         result = true;
324       }
325       else
326       {
327         System.err
328                 .println("Annotations were not added from annotation file '"
329                         + param + "'");
330       }
331     }
332     return result;
333   }
334
335   /**
336    * Load features file and view settings as specified by parameters. Returns
337    * true if features were loaded, else false.
338    * 
339    * @param alignFrame
340    * @return
341    */
342   protected boolean loadFeatures()
343   {
344     boolean result = false;
345     // ///////////////////////////
346     // modify display of features
347     // we do this before any features have been loaded, ensuring any hidden
348     // groups are hidden when features first displayed
349     //
350     // hide specific groups
351     //
352     String param = app.getParameter("hidefeaturegroups");
353     if (param != null)
354     {
355       app.setFeatureGroupState(separatorListToArray(param, separator),
356               false);
357       // app.setFeatureGroupStateOn(newAlignFrame, param, false);
358     }
359     // show specific groups
360     param = app.getParameter("showfeaturegroups");
361     if (param != null)
362     {
363       app.setFeatureGroupState(separatorListToArray(param, separator),
364               true);
365       // app.setFeatureGroupStateOn(newAlignFrame, param, true);
366     }
367     // and now load features
368     param = app.getParameter("features");
369     if (param != null)
370     {
371       ret[0] = param;
372       DataSourceType protocol = resolveFileProtocol(app, ret);
373
374       result = app.parseFeaturesFile(ret[0], protocol);
375     }
376
377     param = app.getParameter("showFeatureSettings");
378     if (param != null && param.equalsIgnoreCase("true"))
379     {
380       app.newFeatureSettings();
381     }
382     return result;
383   }
384
385   /**
386    * Load a score file if specified by parameter. Returns true if file was
387    * loaded, else false.
388    * 
389    * @param loaderFrame
390    */
391   protected boolean loadScoreFile()
392   {
393     boolean result = false;
394     String sScoreFile = app.getParameter("scoreFile");
395     if (sScoreFile != null && !"".equals(sScoreFile))
396     {
397       try
398       {
399         if (debug)
400         {
401           System.err.println(
402                   "Attempting to load T-COFFEE score file from the scoreFile parameter");
403         }
404         result = app.loadScoreFile(sScoreFile);
405         if (!result)
406         {
407           System.err.println(
408                   "Failed to parse T-COFFEE parameter as a valid score file ('"
409                           + sScoreFile + "')");
410         }
411       } catch (Exception e)
412       {
413         System.err.printf("Cannot read score file: '%s'. Cause: %s \n",
414                 sScoreFile, e.getMessage());
415       }
416     }
417     return result;
418   }
419
420   String[] ret = new String[1];
421
422   /**
423    * Load a tree for the alignment if specified by parameter. Returns true if a
424    * tree was loaded, else false.
425    * 
426    * @param loaderFrame
427    * @return
428    */
429   protected boolean loadTree()
430   {
431     boolean result = false;
432     String treeFile = app.getParameter("tree");
433     if (treeFile == null)
434     {
435       treeFile = app.getParameter("treeFile");
436     }
437
438     if (treeFile != null)
439     {
440       try
441       {
442         ret[0] = treeFile;
443         NewickFile fin = new NewickFile(treeFile,
444                 resolveFileProtocol(app, ret));
445         fin.parse();
446
447         if (fin.getTree() != null)
448         {
449           app.loadTree(fin, ret[0]);
450           result = true;
451           if (debug)
452           {
453             System.out.println("Successfully imported tree.");
454           }
455         }
456         else
457         {
458           if (debug)
459           {
460             System.out.println(
461                     "Tree parameter did not resolve to a valid tree.");
462           }
463         }
464       } catch (Exception ex)
465       {
466         ex.printStackTrace();
467       }
468     }
469     return result;
470   }
471
472   /**
473    * form a complete URL given a path to a resource and a reference location on
474    * the same server
475    * 
476    * @param targetPath
477    *          - an absolute path on the same server as localref or a document
478    *          located relative to localref
479    * @param localref
480    *          - a URL on the same server as url
481    * @return a complete URL for the resource located by url
482    */
483   public static String resolveUrlForLocalOrAbsolute(String targetPath,
484           URL localref)
485   {
486     String resolvedPath = "";
487     if (targetPath.startsWith("/"))
488     {
489       String codebase = localref.toString();
490       String localfile = localref.getFile();
491       resolvedPath = codebase.substring(0,
492               codebase.length() - localfile.length()) + targetPath;
493       return resolvedPath;
494     }
495
496     /*
497      * get URL path and strip off any trailing file e.g.
498      * www.jalview.org/examples/index.html#applets?a=b is trimmed to
499      * www.jalview.org/examples/
500      */
501     String urlPath = localref.toString();
502     String directoryPath = urlPath;
503     int lastSeparator = directoryPath.lastIndexOf("/");
504     if (lastSeparator > 0)
505     {
506       directoryPath = directoryPath.substring(0, lastSeparator + 1);
507     }
508
509     if (targetPath.startsWith("/"))
510     {
511       /*
512        * construct absolute URL to a file on the server - this is not allowed?
513        */
514       // String localfile = localref.getFile();
515       // resolvedPath = urlPath.substring(0,
516       // urlPath.length() - localfile.length())
517       // + targetPath;
518       resolvedPath = directoryPath + targetPath.substring(1);
519     }
520     else
521     {
522       resolvedPath = directoryPath + targetPath;
523     }
524     // if (debug)
525     // {
526     // System.err.println(
527     // "resolveUrlForLocalOrAbsolute returning " + resolvedPath);
528     // }
529     return resolvedPath;
530   }
531
532   /**
533    * parse the string into a list
534    * 
535    * @param list
536    * @param separator
537    * @return elements separated by separator
538    */
539   public static String[] separatorListToArray(String list, String separator)
540   {
541     // TODO use StringUtils version (slightly different...)
542     int seplen = separator.length();
543     if (list == null || list.equals("") || list.equals(separator))
544     {
545       return null;
546     }
547     Vector<String> jv = new Vector<>();
548     int cp = 0, pos;
549     while ((pos = list.indexOf(separator, cp)) > cp)
550     {
551       jv.addElement(list.substring(cp, pos));
552       cp = pos + seplen;
553     }
554     if (cp < list.length())
555     {
556       String c = list.substring(cp);
557       if (!c.equals(separator))
558       {
559         jv.addElement(c);
560       }
561     }
562     if (jv.size() > 0)
563     {
564       String[] v = new String[jv.size()];
565       for (int i = 0; i < v.length; i++)
566       {
567         v[i] = jv.elementAt(i);
568       }
569       jv.removeAllElements();
570       // if (debug)
571       // {
572       // System.err.println("Array from '" + separator
573       // + "' separated List:\n" + v.length);
574       // for (int i = 0; i < v.length; i++)
575       // {
576       // System.err.println("item " + i + " '" + v[i] + "'");
577       // }
578       // }
579       return v;
580     }
581     // if (debug)
582     // {
583     // System.err.println(
584     // "Empty Array from '" + separator + "' separated List");
585     // }
586     return null;
587   }
588
589   public static DataSourceType resolveFileProtocol(JalviewApp app,
590           String[] retPath)
591   {
592     String path = retPath[0];
593     /*
594      * is it paste data?
595      */
596     if (path.startsWith("PASTE"))
597     {
598       retPath[0] = path.substring(5);
599       return DataSourceType.PASTE;
600     }
601
602     /*
603      * is it a URL?
604      */
605     if (path.indexOf("://") >= 0)
606     {
607       return DataSourceType.URL;
608     }
609
610     /*
611      * try relative to document root
612      */
613     URL documentBase = app.getDocumentBase();
614     String withDocBase = resolveUrlForLocalOrAbsolute(path, documentBase);
615     if (HttpUtils.isValidUrl(withDocBase))
616     {
617       // if (debug)
618       // {
619       // System.err.println("Prepended document base '" + documentBase
620       // + "' to make: '" + withDocBase + "'");
621       // }
622       retPath[0] = withDocBase;
623       return DataSourceType.URL;
624     }
625
626     /*
627      * try relative to codebase (if different to document base)
628      */
629     URL codeBase = app.getCodeBase();
630     String withCodeBase = resolveUrlForLocalOrAbsolute(path, codeBase);
631     if (!withCodeBase.equals(withDocBase)
632             && HttpUtils.isValidUrl(withCodeBase))
633     {
634       // if (debug)
635       // {
636       // System.err.println("Prepended codebase '" + codeBase
637       // + "' to make: '" + withCodeBase + "'");
638       // }
639       retPath[0] = withCodeBase;
640       return DataSourceType.URL;
641     }
642
643     /*
644      * try locating by classloader; try this last so files in the directory
645      * are resolved using document base
646      */
647     if (inArchive(app.getClass(), path))
648     {
649       return DataSourceType.CLASSLOADER;
650     }
651     return null;
652   }
653
654   /**
655    * Discovers whether the given file is in the Applet Archive
656    * 
657    * @param f
658    *          String
659    * @return boolean
660    */
661   private static boolean inArchive(Class<?> c, String f)
662   {
663     // This might throw a security exception in certain browsers
664     // Netscape Communicator for instance.
665     try
666     {
667       boolean rtn = (c.getResourceAsStream("/" + f) != null);
668       // if (debug)
669       // {
670       // System.err.println("Resource '" + f + "' was "
671       // + (rtn ? "" : "not ") + "located by classloader.");
672       // }
673       return rtn;
674     } catch (Exception ex)
675     {
676       System.out.println("Exception checking resources: " + f + " " + ex);
677       return false;
678     }
679   }
680
681   public void callInitCallback()
682   {
683     String initjscallback = app.getParameter("oninit");
684     if (initjscallback == null)
685     {
686       return;
687     }
688     initjscallback = initjscallback.trim();
689     if (initjscallback.length() > 0)
690     {
691       // TODO
692     }
693   }
694
695   /**
696    * read sequence1...sequenceN as a raw alignment
697    * 
698    * @param jalviewApp
699    * @return
700    */
701   public String getPastedSequence(JalviewApp jalviewApp)
702   {
703     StringBuffer data = new StringBuffer("PASTE");
704     int i = 1;
705     String file = null;
706     while ((file = app.getParameter("sequence" + i)) != null)
707     {
708       data.append(file.toString() + "\n");
709       i++;
710     }
711     if (data.length() > 5)
712     {
713       file = data.toString();
714     }
715     return file;
716   }
717
718   /**
719    * concatenate the list with separator
720    * 
721    * @param list
722    * @param separator
723    * @return concatenated string
724    */
725   public static String arrayToSeparatorList(String[] list, String separator)
726   {
727     // TODO use StringUtils version
728     StringBuffer v = new StringBuffer();
729     if (list != null && list.length > 0)
730     {
731       for (int i = 0, iSize = list.length; i < iSize; i++)
732       {
733         if (list[i] != null)
734         {
735           if (i > 0)
736           {
737             v.append(separator);
738           }
739           v.append(list[i]);
740         }
741       }
742       // if (debug)
743       // {
744       // System.err
745       // .println("Returning '" + separator + "' separated List:\n");
746       // System.err.println(v);
747       // }
748       return v.toString();
749     }
750     // if (debug)
751     // {
752     // System.err.println(
753     // "Returning empty '" + separator + "' separated List\n");
754     // }
755     return "" + separator;
756   }
757
758   public String arrayToSeparatorList(String[] array)
759   {
760     return arrayToSeparatorList(array, separator);
761   }
762
763   public String getSelectedSequencesFrom(AlignFrame alf, String sep)
764   {
765     StringBuffer result = new StringBuffer("");
766     if (sep == null || sep.length() == 0)
767     {
768       sep = separator; // "+0x00AC;
769     }
770     AlignViewport v = alf.getViewport();
771     if (v.getSelectionGroup() != null)
772     {
773       SequenceI[] seqs = v.getSelectionGroup()
774               .getSequencesInOrder(v.getAlignment());
775
776       for (int i = 0; i < seqs.length; i++)
777       {
778         result.append(seqs[i].getName());
779         result.append(sep);
780       }
781     }
782
783     return result.toString();
784   }
785
786   public void setFeatureGroupStateOn(final AlignFrame alf,
787           final String groups, boolean state)
788   {
789     java.awt.EventQueue.invokeLater(new Runnable()
790     {
791       @Override
792       public void run()
793       {
794         alf.setFeatureGroupState(
795                 separatorListToArray(groups, separator), state);
796       }
797     });
798   }
799
800   public String getFeatureGroupsOfStateOn(AlignFrame alf, boolean visible)
801   {
802     return arrayToSeparatorList(
803             alf.getFeatureGroupsOfState(visible));
804   }
805
806   public void scrollViewToIn(final AlignFrame alf, final String topRow,
807           final String leftHandColumn)
808   {
809     // TODO test
810     java.awt.EventQueue.invokeLater(new Runnable()
811     {
812       @Override
813       public void run()
814       {
815         try
816         {
817           alf.scrollTo(Integer.valueOf(topRow).intValue(),
818                   Integer.valueOf(leftHandColumn).intValue());
819
820         } catch (Exception ex)
821         {
822           System.err.println("Couldn't parse integer arguments (topRow='"
823                   + topRow + "' and leftHandColumn='" + leftHandColumn
824                   + "')");
825           ex.printStackTrace();
826         }
827       }
828     });
829   }
830
831   public void scrollViewToRowIn(final AlignFrame alf, final String topRow)
832   {
833     // TODO test
834
835     java.awt.EventQueue.invokeLater(new Runnable()
836     {
837       @Override
838       public void run()
839       {
840         try
841         {
842           alf.scrollToRow(Integer.valueOf(topRow).intValue());
843
844         } catch (Exception ex)
845         {
846           System.err.println("Couldn't parse integer arguments (topRow='"
847                   + topRow + "')");
848           ex.printStackTrace();
849         }
850
851       }
852     });
853   }
854
855   public void scrollViewToColumnIn(final AlignFrame alf,
856           final String leftHandColumn)
857   {
858     // TODO test
859     java.awt.EventQueue.invokeLater(new Runnable()
860     {
861
862       @Override
863       public void run()
864       {
865         try
866         {
867           alf
868                   .scrollToColumn(Integer.valueOf(leftHandColumn).intValue());
869
870         } catch (Exception ex)
871         {
872           System.err.println(
873                   "Couldn't parse integer arguments (leftHandColumn='"
874                           + leftHandColumn + "')");
875           ex.printStackTrace();
876         }
877       }
878     });
879
880   }
881
882   public boolean addPdbFile(AlignFrame alf, String sequenceId,
883           String pdbEntryString, String pdbFile)
884   {
885     AlignFrame alFrame = alf;
886     SequenceI toaddpdb = alFrame.getViewport().getAlignment()
887             .findName(sequenceId);
888     boolean needtoadd = false;
889     if (toaddpdb != null)
890     {
891       Vector<PDBEntry> pdbe = toaddpdb.getAllPDBEntries();
892       PDBEntry pdbentry = null;
893       if (pdbe != null && pdbe.size() > 0)
894       {
895         for (int pe = 0, peSize = pdbe.size(); pe < peSize; pe++)
896         {
897           pdbentry = pdbe.elementAt(pe);
898           if (!pdbentry.getId().equals(pdbEntryString)
899                   && !pdbentry.getFile().equals(pdbFile))
900           {
901             pdbentry = null;
902           }
903           else
904           {
905             continue;
906           }
907         }
908       }
909       if (pdbentry == null)
910       {
911         pdbentry = new PDBEntry();
912         pdbentry.setId(pdbEntryString);
913         pdbentry.setFile(pdbFile);
914         needtoadd = true; // add this new entry to sequence.
915       }
916       // resolve data source
917       // TODO: this code should be a refactored to an io package
918       DataSourceType protocol = AppletFormatAdapter.resolveProtocol(pdbFile,
919               FileFormat.PDB);
920       if (protocol == null)
921       {
922         return false;
923       }
924       if (needtoadd)
925       {
926         pdbentry.setProperty("protocol", protocol);
927         toaddpdb.addPDBId(pdbentry);
928         alFrame.alignPanel.getStructureSelectionManager()
929                 .registerPDBEntry(pdbentry);
930       }
931     }
932     return true;
933   }
934
935   public AlignFrame loadAlignment(String text, int width, int height,
936           String title)
937   {
938     AlignmentI al = null;
939
940     try
941     {
942       FileFormatI format = new IdentifyFile().identify(text,
943               DataSourceType.PASTE);
944       al = new AppletFormatAdapter().readFile(text, DataSourceType.PASTE,
945               format);
946       if (al.getHeight() > 0)
947       {
948         return new AlignFrame(al, width, height, title);
949       }
950     } catch (IOException ex)
951     {
952       ex.printStackTrace();
953     }
954     return null;
955   }
956
957   public String getFeatureGroupsOn(AlignFrame alf)
958   {
959     return arrayToSeparatorList(
960             alf.getFeatureGroups());
961   }
962
963   public void highlightIn(final AlignFrame alf, final String sequenceId,
964           final String position, final String alignedPosition)
965   {
966     // TODO: could try to highlight in all alignments if alf==null
967     jalview.analysis.SequenceIdMatcher matcher = new jalview.analysis.SequenceIdMatcher(
968             alf.getViewport().getAlignment()
969                     .getSequencesArray());
970     final SequenceI sq = matcher.findIdMatch(sequenceId);
971     if (sq != null)
972     {
973       int apos = -1;
974       try
975       {
976         apos = Integer.valueOf(position).intValue();
977         apos--;
978       } catch (NumberFormatException ex)
979       {
980         return;
981       }
982       final int pos = apos;
983       // use vamsas listener to broadcast to all listeners in scope
984       if (alignedPosition != null && (alignedPosition.trim().length() == 0
985               || alignedPosition.toLowerCase().indexOf("false") > -1))
986       {
987         java.awt.EventQueue.invokeLater(new Runnable()
988         {
989           @Override
990           public void run()
991           {
992             StructureSelectionManager
993                     .getStructureSelectionManager(Desktop.getInstance())
994                     .mouseOverVamsasSequence(sq, sq.findIndex(pos), null);
995           }
996         });
997       }
998       else
999       {
1000         java.awt.EventQueue.invokeLater(new Runnable()
1001         {
1002           @Override
1003           public void run()
1004           {
1005             StructureSelectionManager
1006                     .getStructureSelectionManager(Desktop.getInstance())
1007                     .mouseOverVamsasSequence(sq, pos, null);
1008           }
1009         });
1010       }
1011     }
1012   }
1013
1014   public void selectIn(final AlignFrame alf, String sequenceIds,
1015           String columns, String sep)
1016   {
1017     if (sep == null || sep.length() == 0)
1018     {
1019       sep = separator;
1020     }
1021     else
1022     {
1023       if (debug)
1024       {
1025         System.err.println("Selecting region using separator string '"
1026                 + separator + "'");
1027       }
1028     }
1029     // deparse fields
1030     String[] ids = JalviewAppLoader.separatorListToArray(sequenceIds, sep);
1031     String[] cols = JalviewAppLoader.separatorListToArray(columns, sep);
1032     final SequenceGroup sel = new SequenceGroup();
1033     final ColumnSelection csel = new ColumnSelection();
1034     AlignmentI al = alf.getViewport().getAlignment();
1035     jalview.analysis.SequenceIdMatcher matcher = new jalview.analysis.SequenceIdMatcher(
1036             alf.getViewport().getAlignment()
1037                     .getSequencesArray());
1038     int start = 0, end = al.getWidth(), alw = al.getWidth();
1039     boolean seqsfound = true;
1040     if (ids != null && ids.length > 0)
1041     {
1042       seqsfound = false;
1043       for (int i = 0; i < ids.length; i++)
1044       {
1045         if (ids[i].trim().length() == 0)
1046         {
1047           continue;
1048         }
1049         SequenceI sq = matcher.findIdMatch(ids[i]);
1050         if (sq != null)
1051         {
1052           seqsfound = true;
1053           sel.addSequence(sq, false);
1054         }
1055       }
1056     }
1057     boolean inseqpos = false;
1058     if (cols != null && cols.length > 0)
1059     {
1060       boolean seset = false;
1061       for (int i = 0; i < cols.length; i++)
1062       {
1063         String cl = cols[i].trim();
1064         if (cl.length() == 0)
1065         {
1066           continue;
1067         }
1068         int p;
1069         if ((p = cl.indexOf("-")) > -1)
1070         {
1071           int from = -1, to = -1;
1072           try
1073           {
1074             from = Integer.valueOf(cl.substring(0, p)).intValue();
1075             from--;
1076           } catch (NumberFormatException ex)
1077           {
1078             System.err.println(
1079                     "ERROR: Couldn't parse first integer in range element column selection string '"
1080                             + cl + "' - format is 'from-to'");
1081             return;
1082           }
1083           try
1084           {
1085             to = Integer.valueOf(cl.substring(p + 1)).intValue();
1086             to--;
1087           } catch (NumberFormatException ex)
1088           {
1089             System.err.println(
1090                     "ERROR: Couldn't parse second integer in range element column selection string '"
1091                             + cl + "' - format is 'from-to'");
1092             return;
1093           }
1094           if (from >= 0 && to >= 0)
1095           {
1096             // valid range
1097             if (from < to)
1098             {
1099               int t = to;
1100               to = from;
1101               to = t;
1102             }
1103             if (!seset)
1104             {
1105               start = from;
1106               end = to;
1107               seset = true;
1108             }
1109             else
1110             {
1111               // comment to prevent range extension
1112               if (start > from)
1113               {
1114                 start = from;
1115               }
1116               if (end < to)
1117               {
1118                 end = to;
1119               }
1120             }
1121             for (int r = from; r <= to; r++)
1122             {
1123               if (r >= 0 && r < alw)
1124               {
1125                 csel.addElement(r);
1126               }
1127             }
1128             if (debug)
1129             {
1130               System.err.println("Range '" + cl + "' deparsed as [" + from
1131                       + "," + to + "]");
1132             }
1133           }
1134           else
1135           {
1136             System.err.println("ERROR: Invalid Range '" + cl
1137                     + "' deparsed as [" + from + "," + to + "]");
1138           }
1139         }
1140         else
1141         {
1142           int r = -1;
1143           try
1144           {
1145             r = Integer.valueOf(cl).intValue();
1146             r--;
1147           } catch (NumberFormatException ex)
1148           {
1149             if (cl.toLowerCase().equals("sequence"))
1150             {
1151               // we are in the dataset sequence's coordinate frame.
1152               inseqpos = true;
1153             }
1154             else
1155             {
1156               System.err.println(
1157                       "ERROR: Couldn't parse integer from point selection element of column selection string '"
1158                               + cl + "'");
1159               return;
1160             }
1161           }
1162           if (r >= 0 && r <= alw)
1163           {
1164             if (!seset)
1165             {
1166               start = r;
1167               end = r;
1168               seset = true;
1169             }
1170             else
1171             {
1172               // comment to prevent range extension
1173               if (start > r)
1174               {
1175                 start = r;
1176               }
1177               if (end < r)
1178               {
1179                 end = r;
1180               }
1181             }
1182             csel.addElement(r);
1183             if (debug)
1184             {
1185               System.err.println("Point selection '" + cl
1186                       + "' deparsed as [" + r + "]");
1187             }
1188           }
1189           else
1190           {
1191             System.err.println("ERROR: Invalid Point selection '" + cl
1192                     + "' deparsed as [" + r + "]");
1193           }
1194         }
1195       }
1196     }
1197     if (seqsfound)
1198     {
1199       // we only propagate the selection when it was the null selection, or the
1200       // given sequences were found in the alignment.
1201       if (inseqpos && sel.getSize() > 0)
1202       {
1203         // assume first sequence provides reference frame ?
1204         SequenceI rs = sel.getSequenceAt(0);
1205         start = rs.findIndex(start);
1206         end = rs.findIndex(end);
1207         List<Integer> cs = new ArrayList<>(csel.getSelected());
1208         csel.clear();
1209         for (Integer selectedCol : cs)
1210         {
1211           csel.addElement(rs.findIndex(selectedCol));
1212         }
1213       }
1214       sel.setStartRes(start);
1215       sel.setEndRes(end);
1216       EventQueue.invokeLater(new Runnable()
1217       {
1218         @Override
1219         public void run()
1220         {
1221           alf.select(sel, csel, alf
1222                   .getCurrentView().getAlignment().getHiddenColumns());
1223         }
1224       });
1225     }
1226   }
1227
1228   public String getAlignmentOrderFrom(AlignFrame alf, String sep)
1229   {
1230     AlignmentI alorder = alf.getViewport().getAlignment();
1231     String[] order = new String[alorder.getHeight()];
1232     for (int i = 0; i < order.length; i++)
1233     {
1234       order[i] = alorder.getSequenceAt(i).getName();
1235     }
1236     return arrayToSeparatorList(order, sep);
1237   }
1238
1239   public String getSelectedSequencesAsAlignmentFrom(AlignFrame alf,
1240           String format, String suffix)
1241   {
1242     try
1243     {
1244       AlignViewport vp = alf.getViewport();
1245       FileFormatI theFormat = FileFormats.getInstance().forName(format);
1246       boolean seqlimits = (suffix == null
1247               || suffix.equalsIgnoreCase("true"));
1248       if (vp.getSelectionGroup() != null)
1249       {
1250         // JBPNote: getSelectionAsNewSequence behaviour has changed - this
1251         // method now returns a full copy of sequence data
1252         // TODO consider using getSequenceSelection instead here
1253         String reply = new AppletFormatAdapter().formatSequences(theFormat,
1254                 new Alignment(vp.getSelectionAsNewSequence()),
1255                 seqlimits);
1256         return reply;
1257       }
1258     } catch (IllegalArgumentException ex)
1259     {
1260       ex.printStackTrace();
1261       return "Error retrieving alignment, possibly invalid format specifier: "
1262               + format;
1263     }
1264     return "";
1265   }
1266
1267   public String orderAlignmentBy(AlignFrame alf, String order,
1268           String undoName, String sep)
1269   {
1270     if (sep == null || sep.length() == 0)
1271     {
1272       sep = separator;
1273     }
1274     String[] ids = JalviewAppLoader.separatorListToArray(order, sep);
1275     SequenceI[] sqs = null;
1276     if (ids != null && ids.length > 0)
1277     {
1278       jalview.analysis.SequenceIdMatcher matcher = new jalview.analysis.SequenceIdMatcher(
1279               alf.getViewport().getAlignment()
1280                       .getSequencesArray());
1281       int s = 0;
1282       sqs = new SequenceI[ids.length];
1283       for (int i = 0; i < ids.length; i++)
1284       {
1285         if (ids[i].trim().length() == 0)
1286         {
1287           continue;
1288         }
1289         SequenceI sq = matcher.findIdMatch(ids[i]);
1290         if (sq != null)
1291         {
1292           sqs[s++] = sq;
1293         }
1294       }
1295       if (s > 0)
1296       {
1297         SequenceI[] sqq = new SequenceI[s];
1298         System.arraycopy(sqs, 0, sqq, 0, s);
1299         sqs = sqq;
1300       }
1301       else
1302       {
1303         sqs = null;
1304       }
1305     }
1306     if (sqs == null)
1307     {
1308       return "";
1309     }
1310     ;
1311     final AlignmentOrder aorder = new AlignmentOrder(sqs);
1312
1313     if (undoName != null && undoName.trim().length() == 0)
1314     {
1315       undoName = null;
1316     }
1317     final String _undoName = undoName;
1318     // TODO: deal with synchronization here: cannot raise any events until after
1319     // this has returned.
1320     return alf.sortBy(aorder, _undoName) ? "true" : "";
1321   }
1322
1323   public String getAlignmentFrom(AlignFrame alf, String format,
1324           String suffix)
1325   {
1326     try
1327     {
1328       boolean seqlimits = (suffix == null
1329               || suffix.equalsIgnoreCase("true"));
1330
1331       FileFormatI theFormat = FileFormats.getInstance().forName(format);
1332       String reply = new AppletFormatAdapter().formatSequences(theFormat,
1333               alf.getViewport().getAlignment(), seqlimits);
1334       return reply;
1335     } catch (IllegalArgumentException ex)
1336     {
1337       ex.printStackTrace();
1338       return "Error retrieving alignment, possibly invalid format specifier: "
1339               + format;
1340     }
1341   }
1342
1343   public void loadAnnotationFrom(AlignFrame alf, String annotation)
1344   {
1345     if (new AnnotationFile().annotateAlignmentView(
1346             alf.getViewport(), annotation,
1347             DataSourceType.PASTE))
1348     {
1349       alf.alignPanel.fontChanged();
1350       alf.alignPanel.setScrollValues(0, 0);
1351     }
1352     else
1353     {
1354       alf.parseFeaturesFile(annotation,
1355               DataSourceType.PASTE);
1356     }
1357   }
1358
1359   public boolean loadFeaturesFrom(AlignFrame alf, String features,
1360           boolean autoenabledisplay)
1361   {
1362     boolean ret = alf.parseFeaturesFile(features,
1363             DataSourceType.PASTE);
1364     if (!ret)
1365     {
1366       return false;
1367     }
1368     if (autoenabledisplay)
1369     {
1370       alf.getViewport().setShowSequenceFeatures(true);
1371       // this next was for a checkbox in JalviewLite
1372       // ((AlignFrame) alf).getViewport().sequenceFeatures.setState(true);
1373     }
1374     return true;
1375   }
1376
1377   /**
1378    * JavaScript interface to print the alignment frame
1379    * 
1380    * @param alf
1381    * @param format
1382    *          "jalview" or "gff" with or without ";includeComplement" or
1383    *          ";includeNonpositional"; default with no ";" is
1384    *          ";includeNonpositional"
1385    * @return
1386    */
1387   public String getFeaturesFrom(AlignFrame alf, String format)
1388   {
1389     AlignFrame f = (alf);
1390
1391     String features;
1392     FeaturesFile formatter = new FeaturesFile();
1393     format = format.toLowerCase();
1394     if (format.indexOf(";") < 0)
1395       format += ";includenonpositional";
1396     boolean nonpos = format.indexOf(";includenonpositional") > 0;
1397     boolean compl = format.indexOf(";includecomplement") >= 0;
1398     if (format.startsWith("jalview"))
1399     {
1400       features = formatter.printJalviewFormat(
1401               f.getViewport().getAlignment().getSequencesArray(),
1402               f.alignPanel.getFeatureRenderer(), nonpos, compl);
1403     }
1404     else
1405     {
1406       features = formatter.printGffFormat(
1407               f.getViewport().getAlignment().getSequencesArray(),
1408               f.alignPanel.getFeatureRenderer(), nonpos, compl);
1409     }
1410
1411     if (features == null)
1412     {
1413       features = "";
1414     }
1415     return features;
1416
1417   }
1418
1419   public String getAnnotationFrom(AlignFrame alf)
1420   {
1421     AlignFrame f = alf;
1422     String annotation = new AnnotationFile()
1423             .printAnnotationsForView(f.getViewport());
1424     return annotation;
1425   }
1426
1427   // public AlignFrame newViewFrom(AlignFrame alf, String name)
1428   // {
1429   // return (AlignFrame) alf.newView(name, true);
1430   // }
1431   //
1432   public String[] separatorListToArray(String list)
1433   {
1434     return separatorListToArray(list, separator);
1435   }
1436
1437   public Object[] getSelectionForListener(AlignFrame currentFrame,
1438           SequenceGroup seqsel, ColumnSelection colsel,
1439           HiddenColumns hidden, SelectionSource source, Object alignFrame)
1440   {
1441     // System.err.println("Testing selection event relay to
1442     // jsfunction:"+_listener);
1443     String setid = "";
1444     AlignFrame src = (AlignFrame) alignFrame;
1445     if (source != null)
1446     {
1447       if (source instanceof AlignViewport
1448               && currentFrame.getViewport() == source)
1449       {
1450         // should be valid if it just generated an event!
1451         src = currentFrame;
1452
1453       }
1454     }
1455     String[] seqs = new String[] {};
1456     String[] cols = new String[] {};
1457     int strt = 0, end = (src == null) ? -1
1458             : src.alignPanel.av.getAlignment().getWidth();
1459     if (seqsel != null && seqsel.getSize() > 0)
1460     {
1461       seqs = new String[seqsel.getSize()];
1462       for (int i = 0; i < seqs.length; i++)
1463       {
1464         seqs[i] = seqsel.getSequenceAt(i).getName();
1465       }
1466       if (strt < seqsel.getStartRes())
1467       {
1468         strt = seqsel.getStartRes();
1469       }
1470       if (end == -1 || end > seqsel.getEndRes())
1471       {
1472         end = seqsel.getEndRes();
1473       }
1474     }
1475     if (colsel != null && !colsel.isEmpty())
1476     {
1477       if (end == -1)
1478       {
1479         end = colsel.getMax() + 1;
1480       }
1481       cols = new String[colsel.getSelected().size()];
1482       for (int i = 0; i < cols.length; i++)
1483       {
1484         cols[i] = "" + (1 + colsel.getSelected().get(i).intValue());
1485       }
1486     }
1487     else
1488     {
1489       if (seqsel != null && seqsel.getSize() > 0)
1490       {
1491         // send a valid range, otherwise we send the empty selection
1492         cols = new String[2];
1493         cols[0] = "" + (1 + strt) + "-" + (1 + end);
1494       }
1495     }
1496     return new Object[] { src, setid, arrayToSeparatorList(seqs),
1497         arrayToSeparatorList(cols) };
1498   }
1499
1500 }