04f68f394550779867fff36ee0de6316b9cead37
[jalview.git] / src / jalview / bin / Jalview.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.5)
3  * Copyright (C) 2010 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle
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 of the License, or (at your option) any later version.
10  * 
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.bin;
19
20 import java.io.BufferedReader;
21 import java.io.File;
22 import java.io.FileOutputStream;
23 import java.io.IOException;
24 import java.io.OutputStreamWriter;
25 import java.io.PrintWriter;
26 import java.lang.reflect.Constructor;
27 import java.net.URL;
28 import java.net.URLDecoder;
29 import java.security.AllPermission;
30 import java.security.CodeSource;
31 import java.security.PermissionCollection;
32 import java.security.Permissions;
33 import java.security.Policy;
34 import java.util.*;
35
36 import javax.swing.*;
37
38 import jalview.gui.*;
39 import jalview.util.Platform;
40
41 /**
42  * Main class for Jalview Application <br>
43  * <br>
44  * start with java -Djava.ext.dirs=$PATH_TO_LIB$ jalview.bin.Jalview
45  * 
46  * @author $author$
47  * @version $Revision$
48  */
49 public class Jalview
50 {
51   static
52   {
53     // grab all the rights we can the JVM
54     Policy.setPolicy(new Policy()
55     {
56       public PermissionCollection getPermissions(CodeSource codesource)
57       {
58         Permissions perms = new Permissions();
59         perms.add(new AllPermission());
60         return (perms);
61       }
62
63       public void refresh()
64       {
65       }
66     });
67   }
68
69   /**
70    * main class for Jalview application
71    * 
72    * @param args
73    *          open <em>filename</em>
74    */
75   public static void main(String[] args)
76   {
77     System.out.println("Java version: "
78             + System.getProperty("java.version"));
79     System.out.println(System.getProperty("os.arch") + " "
80             + System.getProperty("os.name") + " "
81             + System.getProperty("os.version"));
82     if (new Platform().isAMac())
83     {
84         System.setProperty("com.apple.mrj.application.apple.menu.about.name", "Jalview");
85         System.setProperty("apple.laf.useScreenMenuBar", "true");
86     }
87
88     ArgsParser aparser = new ArgsParser(args);
89     boolean headless = false;
90
91     if (aparser.contains("help") || aparser.contains("h"))
92     {
93       System.out
94               .println("Usage: jalview -open [FILE] [OUTPUT_FORMAT] [OUTPUT_FILE]\n\n"
95                       + "-nodisplay\tRun Jalview without User Interface.\n"
96                       + "-props FILE\tUse the given Jalview properties file instead of users default.\n"
97                       + "-annotations FILE\tAdd precalculated annotations to the alignment.\n"
98                       + "-tree FILE\tLoad the given newick format tree file onto the alignment\n"
99                       + "-features FILE\tUse the given file to mark features on the alignment.\n"
100                       + "-fasta FILE\tCreate alignment file FILE in Fasta format.\n"
101                       + "-clustal FILE\tCreate alignment file FILE in Clustal format.\n"
102                       + "-pfam FILE\tCreate alignment file FILE in PFAM format.\n"
103                       + "-msf FILE\tCreate alignment file FILE in MSF format.\n"
104                       + "-pileup FILE\tCreate alignment file FILE in Pileup format\n"
105                       + "-pir FILE\tCreate alignment file FILE in PIR format.\n"
106                       + "-blc FILE\tCreate alignment file FILE in BLC format.\n"
107                       + "-jalview FILE\tCreate alignment file FILE in Jalview format.\n"
108                       + "-png FILE\tCreate PNG image FILE from alignment.\n"
109                       + "-imgMap FILE\tCreate HTML file FILE with image map of PNG image.\n"
110                       + "-eps FILE\tCreate EPS file FILE from alignment.\n"
111                       + "-questionnaire URL\tQueries the given URL for information about any Jalview user questionnaires.\n"
112                       + "-noquestionnaire\tTurn off questionnaire check.\n"
113                       + "-nousagestats\tTurn off google analytics tracking for this session.\n"
114                       + "-sortbytree OR -nosortbytree\tEnable or disable sorting of the given alignment by the given tree\n"
115                       // +
116                       // "-setprop PROPERTY=VALUE\tSet the given Jalview property, after all other properties files have been read\n\t (quote the 'PROPERTY=VALUE' pair to ensure spaces are passed in correctly)"
117                       + "-dasserver nickname=URL\tAdd and enable a das server with given nickname\n\t\t\t(alphanumeric or underscores only) for retrieval of features for all alignments.\n"
118                       + "\t\t\tSources that also support the sequence command may be specified by prepending the URL with sequence:\n"
119                       + "\t\t\t e.g. sequence:http://localdas.somewhere.org/das/source)\n"
120                       + "-fetchfrom nickname\tQuery nickname for features for the alignments and display them.\n"
121                       // +
122                       // "-vdoc vamsas-document\tImport vamsas document into new session or join existing session with same URN\n"
123                       // + "-vses vamsas-session\tJoin session with given URN\n"
124                       + "-groovy FILE\tExecute groovy script in FILE, after all other arguments have been processed (if FILE is the text 'STDIN' then the file will be read from STDIN)\n"
125                       + "\n~Read documentation in Application or visit http://www.jalview.org for description of Features and Annotations file~\n\n");
126       System.exit(0);
127     }
128     Cache.loadProperties(aparser.getValue("props")); // must do this before
129     // anything else!
130     String defs = aparser.getValue("setprop");
131     while (defs != null)
132     {
133       int p = defs.indexOf('=');
134       if (p == -1)
135       {
136         System.err.println("Ignoring invalid setprop argument : " + defs);
137       }
138       else
139       {
140         System.out.println("Executing setprop argument: " + defs);
141         // DISABLED FOR SECURITY REASONS
142         // TODO: add a property to allow properties to be overriden by cli args
143         // Cache.setProperty(defs.substring(0,p), defs.substring(p+1));
144       }
145       defs = aparser.getValue("setprop");
146     }
147     if (aparser.contains("nodisplay"))
148     {
149       System.setProperty("java.awt.headless", "true");
150     }
151     if (System.getProperty("java.awt.headless") != null
152             && System.getProperty("java.awt.headless").equals("true"))
153     {
154       headless = true;
155     }
156
157     try
158     {
159       Cache.initLogger();
160     } catch (java.lang.NoClassDefFoundError error)
161     {
162       error.printStackTrace();
163       System.out
164               .println("\nEssential logging libraries not found."
165                       + "\nUse: java -Djava.ext.dirs=$PATH_TO_LIB$ jalview.bin.Jalview");
166       System.exit(0);
167     }
168
169     Desktop desktop = null;
170     
171     try
172     {
173       UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
174     } catch (Exception ex)
175     {
176     }
177
178     if (!headless)
179     {
180       desktop = new Desktop();
181       desktop.setVisible(true);
182       desktop.startServiceDiscovery();
183       if (!aparser.contains("nousagestats"))
184       {
185         startUsageStats(desktop);
186       }
187       if (!aparser.contains("noquestionnaire"))
188       {
189         String url = aparser.getValue("questionnaire");
190         if (url != null)
191         {
192           // Start the desktop questionnaire prompter with the specified
193           // questionnaire
194           Cache.log.debug("Starting questionnaire url at " + url);
195           desktop.checkForQuestionnaire(url);
196         }
197         else
198         {
199           if (Cache.getProperty("NOQUESTIONNAIRES") == null)
200           {
201             // Start the desktop questionnaire prompter with the specified
202             // questionnaire
203             // String defurl =
204             // "http://anaplog.compbio.dundee.ac.uk/cgi-bin/questionnaire.pl";
205             // //
206             String defurl = "http://www.jalview.org/cgi-bin/questionnaire.pl";
207             Cache.log.debug("Starting questionnaire with default url: "
208                     + defurl);
209             desktop.checkForQuestionnaire(defurl);
210
211           }
212         }
213       }
214     }
215
216     String file = null, protocol = null, format = null, data = null;
217     jalview.io.FileLoader fileLoader = new jalview.io.FileLoader();
218     Vector getFeatures = null; // vector of das source nicknames to fetch
219     // features from
220     // loading is done.
221     String groovyscript = null; // script to execute after all loading is
222     // completed one way or another
223     // extract groovy argument and execute if necessary
224     groovyscript = aparser.getValue("groovy", true);
225     file = aparser.getValue("open", true);
226
227     if (file == null && desktop == null)
228     {
229       System.out.println("No files to open!");
230       System.exit(1);
231     }
232     String vamsasImport = aparser.getValue("vdoc"), vamsasSession = aparser
233             .getValue("vsess");
234     if (vamsasImport != null || vamsasSession != null)
235     {
236       if (desktop == null || headless)
237       {
238         System.out
239                 .println("Headless vamsas sessions not yet supported. Sorry.");
240         System.exit(1);
241       }
242       // if we have a file, start a new session and import it.
243       boolean inSession = false;
244       if (vamsasImport != null)
245       {
246         try
247         {
248           String viprotocol = Jalview.checkProtocol(vamsasImport);
249           if (viprotocol == jalview.io.FormatAdapter.FILE)
250           {
251             inSession = desktop.vamsasImport(new File(vamsasImport));
252           }
253           else if (viprotocol == jalview.io.FormatAdapter.URL)
254           {
255             inSession = desktop.vamsasImport(new URL(vamsasImport));
256           }
257
258         } catch (Exception e)
259         {
260           System.err.println("Exeption when importing " + vamsasImport
261                   + " as a vamsas document.");
262           e.printStackTrace();
263         }
264         if (!inSession)
265         {
266           System.err.println("Failed to import " + vamsasImport
267                   + " as a vamsas document.");
268         }
269         else
270         {
271           System.out.println("Imported Successfully into new session "
272                   + desktop.getVamsasApplication().getCurrentSession());
273         }
274       }
275       if (vamsasSession != null)
276       {
277         if (vamsasImport != null)
278         {
279           // close the newly imported session and import the Jalview specific
280           // remnants into the new session later on.
281           desktop.vamsasStop_actionPerformed(null);
282         }
283         // now join the new session
284         try
285         {
286           if (desktop.joinVamsasSession(vamsasSession))
287           {
288             System.out.println("Successfully joined vamsas session "
289                     + vamsasSession);
290           }
291           else
292           {
293             System.err.println("WARNING: Failed to join vamsas session "
294                     + vamsasSession);
295           }
296         } catch (Exception e)
297         {
298           System.err.println("ERROR: Failed to join vamsas session "
299                   + vamsasSession);
300           e.printStackTrace();
301         }
302         if (vamsasImport != null)
303         {
304           // the Jalview specific remnants can now be imported into the new
305           // session at the user's leisure.
306           Cache.log
307                   .info("Skipping Push for import of data into existing vamsas session."); // TODO:
308           // enable
309           // this
310           // when
311           // debugged
312           // desktop.getVamsasApplication().push_update();
313         }
314       }
315     }
316     // Finally, deal with the remaining input data.
317     if (file != null)
318     {
319       System.out.println("Opening file: " + file);
320
321       if (!file.startsWith("http://"))
322       {
323         if (!(new java.io.File(file)).exists())
324         {
325           System.out.println("Can't find " + file);
326           if (headless)
327           {
328             System.exit(1);
329           }
330         }
331       }
332
333       protocol = checkProtocol(file);
334
335       format = new jalview.io.IdentifyFile().Identify(file, protocol);
336
337       AlignFrame af = fileLoader.LoadFileWaitTillLoaded(file, protocol,
338               format);
339       if (af == null)
340       {
341         System.out.println("error");
342         return;
343       }
344
345       data = aparser.getValue("colour", true);
346       if (data != null)
347       {
348         data.replaceAll("%20", " ");
349
350         jalview.schemes.ColourSchemeI cs = jalview.schemes.ColourSchemeProperty
351                 .getColour(af.getViewport().getAlignment(), data);
352
353         if (cs == null)
354         {
355           jalview.schemes.UserColourScheme ucs = new jalview.schemes.UserColourScheme(
356                   "white");
357           ucs.parseAppletParameter(data);
358           cs = ucs;
359         }
360
361         System.out.println("colour is " + data);
362         af.changeColour(cs);
363       }
364
365       // Must maintain ability to use the groups flag
366       data = aparser.getValue("groups", true);
367       if (data != null)
368       {
369         af.parseFeaturesFile(data, checkProtocol(data));
370         System.out.println("Added " + data);
371       }
372       data = aparser.getValue("features", true);
373       if (data != null)
374       {
375         af.parseFeaturesFile(data, checkProtocol(data));
376         System.out.println("Added " + data);
377       }
378
379       data = aparser.getValue("annotations", true);
380       if (data != null)
381       {
382         af.loadJalviewDataFile(data);
383         System.out.println("Added " + data);
384       }
385       // set or clear the sortbytree flag.
386       if (aparser.contains("sortbytree"))
387       {
388         af.getViewport().setSortByTree(true);
389       }
390       if (aparser.contains("nosortbytree"))
391       {
392         af.getViewport().setSortByTree(false);
393       }
394       data = aparser.getValue("tree", true);
395       if (data != null)
396       {
397         jalview.io.NewickFile fin = null;
398         try
399         {
400           fin = new jalview.io.NewickFile(data, checkProtocol(data));
401           if (fin != null)
402           {
403             af.getViewport().setCurrentTree(
404                     af.ShowNewickTree(fin, data).getTree());
405             System.out.println("Added tree " + data);
406           }
407         } catch (IOException ex)
408         {
409           System.err.println("Couldn't add tree " + data);
410           ex.printStackTrace(System.err);
411         }
412       }
413       // todo - load PDB structure to alignment
414       // (associate with identical sequence in alignment, or a specified
415       // sequence)
416
417       getFeatures = checkDasArguments(aparser);
418       if (af != null && getFeatures != null)
419       {
420         FeatureFetcher ff = startFeatureFetching(getFeatures);
421         if (ff != null)
422           while (!ff.allFinished() || af.operationInProgress())
423           {
424             // wait around until fetching is finished.
425             try
426             {
427               Thread.sleep(100);
428             } catch (Exception e)
429             {
430
431             }
432           }
433         getFeatures = null; // have retrieved features - forget them now.
434       }
435       if (groovyscript != null)
436       {
437         // Execute the groovy script after we've done all the rendering stuff
438         // and before any images or figures are generated.
439         if (jalview.bin.Cache.groovyJarsPresent())
440         {
441           System.out.println("Executing script " + groovyscript);
442           executeGroovyScript(groovyscript, desktop);
443         }
444         else
445         {
446           System.err
447                   .println("Sorry. Groovy Support is not available, so ignoring the provided groovy script "
448                           + groovyscript);
449         }
450         groovyscript = null;
451       }
452       String imageName = "unnamed.png";
453       while (aparser.getSize() > 1)
454       {
455         format = aparser.nextValue();
456         file = aparser.nextValue();
457
458         if (format.equalsIgnoreCase("png"))
459         {
460           af.createPNG(new java.io.File(file));
461           imageName = (new java.io.File(file)).getName();
462           System.out.println("Creating PNG image: " + file);
463           continue;
464         }
465         else if (format.equalsIgnoreCase("imgMap"))
466         {
467           af.createImageMap(new java.io.File(file), imageName);
468           System.out.println("Creating image map: " + file);
469           continue;
470         }
471         else if (format.equalsIgnoreCase("eps"))
472         {
473           System.out.println("Creating EPS file: " + file);
474           af.createEPS(new java.io.File(file));
475           continue;
476         }
477
478         if (af.saveAlignment(file, format))
479         {
480           System.out.println("Written alignment in " + format
481                   + " format to " + file);
482         }
483         else
484         {
485           System.out.println("Error writing file " + file + " in " + format
486                   + " format!!");
487         }
488
489       }
490
491       while (aparser.getSize() > 0)
492       {
493         System.out.println("Unknown arg: " + aparser.nextValue());
494       }
495     }
496     AlignFrame startUpAlframe = null;
497     // We'll only open the default file if the desktop is visible.
498     // And the user
499     // ////////////////////
500     if (!headless && file == null && vamsasImport == null
501             && jalview.bin.Cache.getDefault("SHOW_STARTUP_FILE", true))
502     {
503       file = jalview.bin.Cache.getDefault("STARTUP_FILE",
504               "http://www.jalview.org/examples/exampleFile_2_3.jar");
505
506       protocol = "File";
507
508       if (file.indexOf("http:") > -1)
509       {
510         protocol = "URL";
511       }
512
513       if (file.endsWith(".jar"))
514       {
515         format = "Jalview";
516       }
517       else
518       {
519         format = new jalview.io.IdentifyFile().Identify(file, protocol);
520       }
521
522       startUpAlframe = fileLoader.LoadFileWaitTillLoaded(file, protocol,
523               format);
524       getFeatures = checkDasArguments(aparser);
525       // extract groovy arguments before anything else.
526     }
527     // If the user has specified features to be retrieved,
528     // or a groovy script to be executed, do them if they
529     // haven't been done already
530     // fetch features for the default alignment
531     if (getFeatures != null)
532     {
533       if (startUpAlframe != null)
534       {
535         startFeatureFetching(getFeatures);
536       }
537     }
538     // execute a groovy script.
539     if (groovyscript != null)
540     {
541       if (jalview.bin.Cache.groovyJarsPresent())
542       {
543         System.out.println("Executing script " + groovyscript);
544         executeGroovyScript(groovyscript, desktop);
545       }
546       else
547       {
548         System.err
549                 .println("Sorry. Groovy Support is not available, so ignoring the provided groovy script "
550                         + groovyscript);
551       }
552     }
553
554     // Once all other stuff is done, execute any groovy scripts (in order)
555   }
556
557   private static void startUsageStats(final Desktop desktop)
558   {
559     /**
560      * start a User Config prompt asking if we can log usage statistics.
561      */
562     jalview.gui.PromptUserConfig prompter = new jalview.gui.PromptUserConfig(
563             desktop.desktop,
564             "USAGESTATS",
565             "Jalview Usage Statistics",
566             "Do you want to help make Jalview better by enabling "
567                     + "the collection of usage statistics with Google Analytics ?"
568                     + "\n\n(you can enable or disable usage tracking in the preferences)",
569             new Runnable()
570             {
571               public void run()
572               {
573                 Cache.log
574                         .info("Initialising googletracker for usage stats.");
575                 Cache.initGoogleTracker();
576                 Cache.log.debug("Tracking enabled.");
577               }
578             }, new Runnable()
579             {
580               public void run()
581               {
582                 Cache.log.info("Not enabling Google Tracking.");
583               }
584             }, null, true);
585     SwingUtilities.invokeLater(prompter);
586   }
587
588   /**
589    * Locate the given string as a file and pass it to the groovy interpreter.
590    * 
591    * @param groovyscript
592    *          the script to execute
593    * @param jalviewContext
594    *          the Jalview Desktop object passed in to the groovy binding as the
595    *          'Jalview' object.
596    */
597   private static void executeGroovyScript(String groovyscript,
598           Object jalviewContext)
599   {
600     if (jalviewContext == null)
601     {
602       System.err
603               .println("Sorry. Groovy support is currently only available when running with the Jalview GUI enabled.");
604     }
605     File sfile = null;
606     if (groovyscript.trim().equals("STDIN"))
607     {
608       // read from stdin into a tempfile and execute it
609       try
610       {
611         sfile = File.createTempFile("jalview", "groovy");
612         PrintWriter outfile = new PrintWriter(new OutputStreamWriter(
613                 new FileOutputStream(sfile)));
614         BufferedReader br = new BufferedReader(
615                 new java.io.InputStreamReader(System.in));
616         String line = null;
617         while ((line = br.readLine()) != null)
618         {
619           outfile.write(line + "\n");
620         }
621         br.close();
622         outfile.flush();
623         outfile.close();
624
625       } catch (Exception ex)
626       {
627         System.err.println("Failed to read from STDIN into tempfile "
628                 + ((sfile == null) ? "(tempfile wasn't created)" : sfile
629                         .toString()));
630         ex.printStackTrace();
631         return;
632       }
633     }
634     else
635     {
636       sfile = new File(groovyscript);
637     }
638     if (!sfile.exists())
639     {
640       System.err.println("File '" + groovyscript + "' does not exist.");
641       return;
642     }
643     if (!sfile.canRead())
644     {
645       System.err.println("File '" + groovyscript + "' cannot be read.");
646       return;
647     }
648     if (sfile.length() < 1)
649     {
650       System.err.println("File '" + groovyscript + "' is empty.");
651       return;
652     }
653     boolean success = false;
654     try
655     {
656       /*
657        * The following code performs the GroovyScriptEngine invocation using
658        * reflection, and is equivalent to this fragment from the embedding
659        * groovy documentation on the groovy site: <code> import
660        * groovy.lang.Binding; import groovy.util.GroovyScriptEngine;
661        * 
662        * String[] roots = new String[] { "/my/groovy/script/path" };
663        * GroovyScriptEngine gse = new GroovyScriptEngine(roots); Binding binding
664        * = new Binding(); binding.setVariable("input", "world");
665        * gse.run("hello.groovy", binding); </code>
666        */
667       ClassLoader cl = jalviewContext.getClass().getClassLoader();
668       Class gbindingc = cl.loadClass("groovy.lang.Binding");
669       Constructor gbcons = gbindingc.getConstructor(null);
670       Object gbinding = gbcons.newInstance(null);
671       java.lang.reflect.Method setvar = gbindingc.getMethod("setVariable",
672               new Class[]
673               { String.class, Object.class });
674       setvar.invoke(gbinding, new Object[]
675       { "Jalview", jalviewContext });
676       Class gsec = cl.loadClass("groovy.util.GroovyScriptEngine");
677       Constructor gseccons = gsec.getConstructor(new Class[]
678       { URL[].class }); // String[].class });
679       Object gse = gseccons.newInstance(new Object[]
680       { new URL[]
681       { sfile.toURL() } }); // .toString() } });
682       java.lang.reflect.Method run = gsec.getMethod("run", new Class[]
683       { String.class, gbindingc });
684       run.invoke(gse, new Object[]
685       { sfile.getName(), gbinding });
686       success = true;
687     } catch (Exception e)
688     {
689       System.err.println("Exception Whilst trying to execute file " + sfile
690               + " as a groovy script.");
691       e.printStackTrace(System.err);
692
693     }
694     if (success && groovyscript.equals("STDIN"))
695     {
696       // delete temp file that we made - but only if it was successfully
697       // executed
698       sfile.delete();
699     }
700   }
701
702   /**
703    * Check commandline for any das server definitions or any fetchfrom switches
704    * 
705    * @return vector of DAS source nicknames to retrieve from
706    */
707   private static Vector checkDasArguments(ArgsParser aparser)
708   {
709     Vector source = null;
710     String data;
711     String locsources = Cache.getProperty(Cache.DAS_LOCAL_SOURCE);
712     while ((data = aparser.getValue("dasserver", true)) != null)
713     {
714       String nickname = null;
715       String url = null;
716       boolean seq = false, feat = true;
717       int pos = data.indexOf('=');
718       // determine capabilities
719       if (pos > 0)
720       {
721         nickname = data.substring(0, pos);
722       }
723       url = data.substring(pos + 1);
724       if (url != null
725               && (url.startsWith("http:") || url
726                       .startsWith("sequence:http:")))
727       {
728         if (nickname == null)
729         {
730           nickname = url;
731         }
732         if (locsources == null)
733         {
734           locsources = "";
735         }
736         else
737         {
738           locsources += "\t";
739         }
740         locsources = locsources + nickname + "|" + url;
741         System.err
742                 .println("NOTE! dasserver parameter not yet really supported (got args of "
743                         + nickname + "|" + url);
744         if (source == null)
745         {
746           source = new Vector();
747         }
748         source.addElement(nickname);
749       }
750     } // loop until no more server entries are found.
751     if (locsources != null && locsources.indexOf('|') > -1)
752     {
753       Cache.log.debug("Setting local source list in properties file to:\n"
754               + locsources);
755       Cache.setProperty(Cache.DAS_LOCAL_SOURCE, locsources);
756     }
757     while ((data = aparser.getValue("fetchfrom", true)) != null)
758     {
759       System.out.println("adding source '" + data + "'");
760       if (source == null)
761       {
762         source = new Vector();
763       }
764       source.addElement(data);
765     }
766     return source;
767   }
768
769   /**
770    * start a feature fetcher for every alignment frame
771    * 
772    * @param dasSources
773    */
774   private static FeatureFetcher startFeatureFetching(final Vector dasSources)
775   {
776     FeatureFetcher ff = new FeatureFetcher();
777     AlignFrame afs[] = Desktop.getAlignframes();
778     if (afs == null || afs.length == 0)
779     {
780       return null;
781     }
782     for (int i = 0; i < afs.length; i++)
783     {
784       ff.addFetcher(afs[i], dasSources);
785     }
786     return ff;
787   }
788
789   private static String checkProtocol(String file)
790   {
791     String protocol = jalview.io.FormatAdapter.FILE;
792
793     if (file.indexOf("http:") > -1 || file.indexOf("file:") > -1)
794     {
795       protocol = jalview.io.FormatAdapter.URL;
796     }
797     return protocol;
798   }
799 }
800
801 /**
802  * Notes: this argParser does not distinguish between parameter switches,
803  * parameter values and argument text. If an argument happens to be identical to
804  * a parameter, it will be taken as such (even though it didn't have a '-'
805  * prefixing it).
806  * 
807  * @author Andrew Waterhouse and JBP documented.
808  * 
809  */
810 class ArgsParser
811 {
812   Vector vargs = null;
813
814   public ArgsParser(String[] args)
815   {
816     vargs = new Vector();
817     for (int i = 0; i < args.length; i++)
818     {
819       String arg = args[i].trim();
820       if (arg.charAt(0) == '-')
821       {
822         arg = arg.substring(1);
823       }
824       vargs.addElement(arg);
825     }
826   }
827
828   /**
829    * check for and remove first occurence of arg+parameter in arglist.
830    * 
831    * @param arg
832    * @return return the argument following the given arg if arg was in list.
833    */
834   public String getValue(String arg)
835   {
836     return getValue(arg, false);
837   }
838
839   public String getValue(String arg, boolean utf8decode)
840   {
841     int index = vargs.indexOf(arg);
842     String dc = null, ret = null;
843     if (index != -1)
844     {
845       ret = vargs.elementAt(index + 1).toString();
846       vargs.removeElementAt(index);
847       vargs.removeElementAt(index);
848       if (utf8decode && ret != null)
849       {
850         try
851         {
852           dc = URLDecoder.decode(ret, "UTF-8");
853           ret = dc;
854         } catch (Exception e)
855         {
856           // TODO: log failure to decode
857         }
858       }
859     }
860     return ret;
861   }
862
863   /**
864    * check for and remove first occurence of arg in arglist.
865    * 
866    * @param arg
867    * @return true if arg was present in argslist.
868    */
869   public boolean contains(String arg)
870   {
871     if (vargs.contains(arg))
872     {
873       vargs.removeElement(arg);
874       return true;
875     }
876     else
877     {
878       return false;
879     }
880   }
881
882   public String nextValue()
883   {
884     return vargs.remove(0).toString();
885   }
886
887   public int getSize()
888   {
889     return vargs.size();
890   }
891
892 }
893
894 /**
895  * keep track of feature fetching tasks.
896  * 
897  * @author JimP
898  * 
899  */
900 class FeatureFetcher
901 {
902   /*
903    * TODO: generalise to track all jalview events to orchestrate batch
904    * processing events.
905    */
906
907   private int queued = 0;
908
909   private int running = 0;
910
911   public FeatureFetcher()
912   {
913
914   }
915
916   public void addFetcher(final AlignFrame af, final Vector dasSources)
917   {
918     final long id = System.currentTimeMillis();
919     queued++;
920     final FeatureFetcher us = this;
921     new Thread(new Runnable()
922     {
923
924       public void run()
925       {
926         synchronized (us)
927         {
928           queued--;
929           running++;
930         }
931
932         af.setProgressBar("DAS features being retrieved...", id);
933         af.featureSettings_actionPerformed(null);
934         af.featureSettings.fetchDasFeatures(dasSources, true);
935         af.setProgressBar(null, id);
936         synchronized (us)
937         {
938           running--;
939         }
940       }
941     }).start();
942   }
943
944   public synchronized boolean allFinished()
945   {
946     return queued == 0 && running == 0;
947   }
948 };