ee46581a47b00872d8cb0becabd0a066caff0ba5
[jalview.git] / src / jalview / bin / Cache.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.bin;
22
23 import jalview.ws.dbsources.das.api.DasSourceRegistryI;
24 import jalview.ws.dbsources.das.datamodel.DasSourceRegistry;
25
26 import java.awt.Color;
27 import java.io.BufferedReader;
28 import java.io.File;
29 import java.io.FileInputStream;
30 import java.io.FileOutputStream;
31 import java.io.InputStream;
32 import java.io.InputStreamReader;
33 import java.text.DateFormat;
34 import java.text.SimpleDateFormat;
35 import java.util.Collections;
36 import java.util.Date;
37 import java.util.Enumeration;
38 import java.util.Properties;
39 import java.util.TreeSet;
40
41 import org.apache.log4j.ConsoleAppender;
42 import org.apache.log4j.Level;
43 import org.apache.log4j.Logger;
44 import org.apache.log4j.SimpleLayout;
45
46 /**
47  * Stores and retrieves Jalview Application Properties Lists and fields within
48  * list entries are separated by '|' symbols unless otherwise stated (|) clauses
49  * are alternative values for a tag. <br>
50  * <br>
51  * Current properties include:
52  * <ul>
53  * <br>
54  * logs.Axis.Level - one of the stringified Levels for log4j controlling the
55  * logging level for axis (used for web services) <br>
56  * </li>
57  * <li>logs.Castor.Level - one of the stringified Levels for log4j controlling
58  * the logging level for castor (used for serialization) <br>
59  * </li>
60  * <li>logs.Jalview.Level - Cache.log stringified level. <br>
61  * </li>
62  * <li>SCREEN_WIDTH</li>
63  * <li>SCREEN_HEIGHT</li>
64  * <li>SCREEN_Y=285</li>
65  * <li>SCREEN_X=371</li>
66  * <li>SHOW_FULLSCREEN boolean</li>
67  * <li>FONT_NAME java font name for alignment text display</li>
68  * <li>FONT_SIZE size of displayed alignment text</li>
69  * <li>FONT_STYLE style of font displayed (sequence labels are always italic)</li>
70  * <li>GAP_SYMBOL character to treat as gap symbol (usually -,.,' ')</li>
71  * <li>LAST_DIRECTORY last directory for browsing alignment</li>
72  * <li>USER_DEFINED_COLOURS list of user defined colour scheme files</li>
73  * <li>SHOW_FULL_ID show id with '/start-end' numbers appended</li>
74  * <li>SHOW_IDENTITY show percentage identity annotation</li>
75  * <li>SHOW_QUALITY show alignment quality annotation</li>
76  * <li>SHOW_ANNOTATIONS show alignment annotation rows</li>
77  * <li>SHOW_CONSERVATION show alignment conservation annotation</li>
78  * <li>SORT_ANNOTATIONS currently either SEQUENCE_AND_LABEL or
79  * LABEL_AND_SEQUENCE</li>
80  * <li>SHOW_AUTOCALC_ABOVE true to show autocalculated annotations above
81  * sequence annotations</li>
82  * <li>CENTRE_COLUMN_LABELS centre the labels at each column in a displayed
83  * annotation row</li>
84  * <li>DEFAULT_COLOUR default colour scheme to apply for a new alignment</li>
85  * <li>DEFAULT_FILE_FORMAT file format used to save</li>
86  * <li>STARTUP_FILE file loaded on startup (may be a fully qualified url)</li>
87  * <li>SHOW_STARTUP_FILE flag to control loading of startup file</li>
88  * <li>VERSION the version of the jalview build</li>
89  * <li>BUILD_DATE date of this build</li>
90  * <li>LATEST_VERSION the latest jalview version advertised on the
91  * www.jalview.org</li>
92  * <li>PIR_MODELLER boolean indicating if PIR files are written with MODELLER
93  * descriptions</li>
94  * <li>(FASTA,MSF,PILEUP,CLUSTAL,BLC,PIR,PFAM)_JVSUFFIX boolean for adding jv
95  * suffix to file</li>
96  * <li>RECENT_URL list of recently retrieved URLs</li>
97  * <li>RECENT_FILE list of recently opened files</li>
98  * <li>USE_PROXY flag for whether a http proxy is to be used</li>
99  * <li>PROXY_SERVER the proxy</li>
100  * <li>PROXY_PORT</li>
101  * <li>NOQUESTIONNAIRES true to prevent jalview from checking the questionnaire
102  * service</li>
103  * <li>QUESTIONNAIRE last questionnaire:responder id string from questionnaire
104  * service</li>
105  * <li>USAGESTATS (false - user prompted) Enable google analytics tracker for
106  * collecting usage statistics</li>
107  * <li>DAS_LOCAL_SOURCE list of local das sources</li>
108  * <li>SHOW_OVERVIEW boolean for overview window display</li>
109  * <li>ANTI_ALIAS boolean for smooth fonts</li>
110  * <li>RIGHT_ALIGN_IDS boolean</li>
111  * <li>AUTO_CALC_CONSENSUS boolean for automatic recalculation of consensus</li>
112  * <li>PAD_GAPS boolean</li>
113  * <li>ID_ITALICS boolean</li>
114  * <li>SHOW_JV_SUFFIX</li>
115  * <li>WRAP_ALIGNMENT</li>
116  * <li>EPS_RENDERING (Prompt each time|Lineart|Text) default for EPS rendering
117  * style check</li>
118  * <li>SORT_ALIGNMENT (No sort|Id|Pairwise Identity)</li>
119  * <li>SEQUENCE_LINKS list of name|URL pairs for opening a url with
120  * $SEQUENCE_ID$</li>
121  * <li>GROUP_LINKS list of name|URL[|&lt;separator&gt;] tuples - see
122  * jalview.utils.GroupURLLink for more info</li>
123  * <li>DAS_REGISTRY_URL the registry to query</li>
124  * <li>DEFAULT_BROWSER for unix</li>
125  * <li>DAS_ACTIVE_SOURCE list of active sources</li>
126  * <li>SHOW_MEMUSAGE boolean show memory usage and warning indicator on desktop
127  * (false)</li>
128  * <li>VERSION_CHECK (true) check for the latest release version from
129  * www.jalview.org (or the alias given by the www.jalview.org property)</li>
130  * <li>SHOW_NPFEATS_TOOLTIP (true) show non-positional features in the Sequence
131  * ID tooltip</li>
132  * <li>SHOW_DBREFS_TOOLTIP (true) show Database Cross References in the Sequence
133  * ID tooltip</li>
134  * <li>SHOW_UNCONSERVED (false) only render unconserved residues - conserved
135  * displayed as '.'</li>
136  * <li>SORT_BY_TREE (false) sort the current alignment view according to the
137  * order of a newly displayed tree</li>
138  * <li>DBFETCH_USEPICR (false) use PICR to recover valid DB references from
139  * sequence ID strings before attempting retrieval from any datasource</li>
140  * <li>SHOW_GROUP_CONSENSUS (false) Show consensus annotation for groups in the
141  * alignment.</li>
142  * <li>SHOW_GROUP_CONSERVATION (false) Show conservation annotation for groups
143  * in the alignment.</li>
144  * <li>SHOW_CONSENSUS_HISTOGRAM (false) Show consensus annotation row's
145  * histogram.</li>
146  * <li>SHOW_CONSENSUS_LOGO (false) Show consensus annotation row's sequence
147  * logo.</li>
148  * <li>NORMALISE_CONSENSUS_LOGO (false) Show consensus annotation row's sequence
149  * logo normalised to row height rather than histogram height.</li>
150  * <li>FOLLOW_SELECTIONS (true) Controls whether a new alignment view should
151  * respond to selections made in other alignments containing the same sequences.
152  * </li>
153  * <li>JWS2HOSTURLS comma-separated list of URLs to try for JABAWS services</li>
154  * <li>SHOW_WSDISCOVERY_ERRORS (true) Controls if the web service URL discovery
155  * warning dialog box is displayed.</li>
156  * <li>ANNOTATIONCOLOUR_MIN (orange) Shade used for minimum value of annotation
157  * when shading by annotation</li>
158  * <li>ANNOTATIONCOLOUR_MAX (red) Shade used for maximum value of annotation
159  * when shading by annotation</li>
160  * <li>www.jalview.org (http://www.jalview.org) a property enabling all HTTP
161  * requests to be redirected to a mirror of http://www.jalview.org</li>
162  * <li>FIGURE_AUTOIDWIDTH (false) Expand the left hand column of an exported
163  * alignment figure to accommodate even the longest sequence ID or annotation
164  * label.</li>
165  * <li>FIGURE_FIXEDIDWIDTH Specifies the width to use for the left-hand column
166  * when exporting an alignment as a figure (setting FIGURE_AUTOIDWIDTH to true
167  * will override this).</li>
168  * <li>STRUCT_FROM_PDB (false) derive secondary structure annotation from PDB
169  * record</li>
170  * <li>USE_RNAVIEW (false) use RNAViewer to derive secondary structure</li>
171  * <li>ADD_SS_ANN (false) add secondary structure annotation to alignment
172  * display</li>
173  * <li>ADD_TEMPFACT_ANN (false) add Temperature Factor annotation to alignment
174  * display</li>
175  * <li>STRUCTURE_DISPLAY choose from JMOL (default) or CHIMERA for 3D structure
176  * display</li>
177  * <li>CHIMERA_PATH specify full path to Chimera program (if non-standard)</li>
178  * 
179  * </ul>
180  * Deprecated settings:
181  * <ul>
182  * *
183  * <li>DISCOVERY_START - Boolean - controls if discovery services are queried on
184  * startup (JWS1 services only)</li>
185  * <li>DISCOVERY_URLS - comma separated list of Discovery Service endpoints.
186  * (JWS1 services only)</li>
187  * <li>SHOW_JWS1_SERVICES (true) enable or disable the original Jalview 2
188  * services in the desktop GUI</li>
189  * <li>ENABLE_RSBS_EDITOR (false for 2.7 release) enable or disable RSBS editing
190  * panel in web service preferences</li>
191  * </ul>
192  * 
193  * @author $author$
194  * @version $Revision$
195  */
196 public class Cache
197 {
198   /**
199    * property giving log4j level for CASTOR loggers
200    */
201   public static final String CASTORLOGLEVEL = "logs.Castor.level";
202
203   /**
204    * property giving log4j level for AXIS loggers
205    */
206   public static final String AXISLOGLEVEL = "logs.Axis.level";
207
208   /**
209    * property giving log4j level for Jalview Log
210    */
211   public static final String JALVIEWLOGLEVEL = "logs.Jalview.level";
212
213   public static final String DAS_LOCAL_SOURCE = "DAS_LOCAL_SOURCE";
214
215   public static final String DAS_REGISTRY_URL = "DAS_REGISTRY_URL";
216
217   public static final String DAS_ACTIVE_SOURCE = "DAS_ACTIVE_SOURCE";
218
219   /**
220    * Initialises the Jalview Application Log
221    */
222   public static Logger log;
223
224   /** Jalview Properties */
225   public static Properties applicationProperties = new Properties()
226   {
227     // override results in properties output in alphabetical order
228     @Override
229     public synchronized Enumeration<Object> keys()
230     {
231       return Collections.enumeration(new TreeSet<Object>(super.keySet()));
232     }
233   };
234
235   /** Default file is ~/.jalview_properties */
236   static String propertiesFile;
237
238   private static boolean propsAreReadOnly = false;
239
240   public static void initLogger()
241   {
242     if (log != null)
243     {
244       return;
245     }
246     try
247     {
248       // TODO: redirect stdout and stderr here in order to grab the output of
249       // the log
250
251       ConsoleAppender ap = new ConsoleAppender(new SimpleLayout(),
252               "System.err");
253       ap.setName("JalviewLogger");
254       org.apache.log4j.Logger.getRootLogger().addAppender(ap); // catch all for
255       // log output
256       Logger laxis = Logger.getLogger("org.apache.axis");
257       Logger lcastor = Logger.getLogger("org.exolab.castor");
258       jalview.bin.Cache.log = Logger.getLogger("jalview.bin.Jalview");
259
260       laxis.setLevel(Level.toLevel(Cache.getDefault("logs.Axis.Level",
261               Level.INFO.toString())));
262       lcastor.setLevel(Level.toLevel(Cache.getDefault("logs.Castor.Level",
263               Level.INFO.toString())));
264       lcastor = Logger.getLogger("org.exolab.castor.xml");
265       lcastor.setLevel(Level.toLevel(Cache.getDefault("logs.Castor.Level",
266               Level.INFO.toString())));
267       // lcastor = Logger.getLogger("org.exolab.castor.xml.Marshaller");
268       // lcastor.setLevel(Level.toLevel(Cache.getDefault("logs.Castor.Level",
269       // Level.INFO.toString())));
270       jalview.bin.Cache.log.setLevel(Level.toLevel(Cache.getDefault(
271               "logs.Jalview.level", Level.INFO.toString())));
272       // laxis.addAppender(ap);
273       // lcastor.addAppender(ap);
274       // jalview.bin.Cache.log.addAppender(ap);
275       // Tell the user that debug is enabled
276       jalview.bin.Cache.log.debug("Jalview Debugging Output Follows.");
277     } catch (Exception ex)
278     {
279       System.err.println("Problems initializing the log4j system\n");
280       ex.printStackTrace(System.err);
281     }
282   }
283
284   /** Called when Jalview is started */
285   public static void loadProperties(String propsFile)
286   {
287     propertiesFile = propsFile;
288     if (propsFile == null)
289     {
290       propertiesFile = System.getProperty("user.home") + File.separatorChar
291               + ".jalview_properties";
292     }
293     else
294     {
295       // don't corrupt the file we've been given.
296       propsAreReadOnly = true;
297     }
298
299     try
300     {
301       InputStream fis;
302       try
303       {
304         fis = new java.net.URL(propertiesFile).openStream();
305         System.out.println("Loading jalview properties from : "
306                 + propertiesFile);
307         System.out
308                 .println("Disabling Jalview writing to user's local properties file.");
309         propsAreReadOnly = true;
310
311       } catch (Exception ex)
312       {
313         fis = null;
314       }
315       if (fis == null)
316       {
317         fis = new FileInputStream(propertiesFile);
318       }
319       applicationProperties.load(fis);
320
321       // remove any old build properties
322
323       deleteBuildProperties();
324       fis.close();
325     } catch (Exception ex)
326     {
327       System.out.println("Error reading properties file: " + ex);
328     }
329
330     if (getDefault("USE_PROXY", false))
331     {
332       String proxyServer = getDefault("PROXY_SERVER", ""), proxyPort = getDefault(
333               "PROXY_PORT", "8080");
334
335       System.out.println("Using proxyServer: " + proxyServer
336               + " proxyPort: " + proxyPort);
337
338       System.setProperty("http.proxyHost", proxyServer);
339       System.setProperty("http.proxyPort", proxyPort);
340     }
341
342     // LOAD THE AUTHORS FROM THE authors.props file
343     try
344     {
345       String authorDetails = "jar:".concat(Cache.class
346               .getProtectionDomain().getCodeSource().getLocation()
347               .toString().concat("!/authors.props"));
348
349       java.net.URL localJarFileURL = new java.net.URL(authorDetails);
350
351       InputStream in = localJarFileURL.openStream();
352       applicationProperties.load(in);
353       in.close();
354     } catch (Exception ex)
355     {
356       System.out.println("Error reading author details: " + ex);
357       applicationProperties.remove("AUTHORS");
358       applicationProperties.remove("AUTHORFNAMES");
359       applicationProperties.remove("YEAR");
360     }
361
362     // FIND THE VERSION NUMBER AND BUILD DATE FROM jalview.jar
363     // MUST FOLLOW READING OF LOCAL PROPERTIES FILE AS THE
364     // VERSION MAY HAVE CHANGED SINCE LAST USING JALVIEW
365     try
366     {
367       String buildDetails = "jar:".concat(Cache.class.getProtectionDomain()
368               .getCodeSource().getLocation().toString()
369               .concat("!/.build_properties"));
370
371       java.net.URL localJarFileURL = new java.net.URL(buildDetails);
372
373       InputStream in = localJarFileURL.openStream();
374       applicationProperties.load(in);
375       in.close();
376     } catch (Exception ex)
377     {
378       System.out.println("Error reading build details: " + ex);
379       applicationProperties.remove("VERSION");
380     }
381
382     String jnlpVersion = System.getProperty("jalview.version");
383     String codeVersion = getProperty("VERSION");
384     String codeInstallation = getProperty("INSTALLATION");
385     if (codeVersion == null)
386     {
387       // THIS SHOULD ONLY BE THE CASE WHEN TESTING!!
388       codeVersion = "Test";
389       jnlpVersion = "Test";
390       codeInstallation = "";
391     }
392     else
393     {
394       codeInstallation = " (" + codeInstallation + ")";
395     }
396     new BuildDetails(codeVersion, null, codeInstallation);
397     System.out
398             .println("Jalview Version: " + codeVersion + codeInstallation);
399
400     // jnlpVersion will be null if we're using InstallAnywhere
401     // Dont do this check if running in headless mode
402     if (jnlpVersion == null
403             && getDefault("VERSION_CHECK", true)
404             && (System.getProperty("java.awt.headless") == null || System
405                     .getProperty("java.awt.headless").equals("false")))
406     {
407
408       class VersionChecker extends Thread
409       {
410         public void run()
411         {
412           String orgtimeout = System
413                   .getProperty("sun.net.client.defaultConnectTimeout");
414           if (orgtimeout == null)
415           {
416             orgtimeout = "30";
417             System.out.println("# INFO: Setting default net timeout to "
418                     + orgtimeout + " seconds.");
419           }
420           String jnlpVersion = null;
421           try
422           {
423             System.setProperty("sun.net.client.defaultConnectTimeout",
424                     "5000");
425             java.net.URL url = new java.net.URL(Cache.getDefault(
426                     "www.jalview.org", "http://www.jalview.org")
427                     + "/webstart/jalview.jnlp");
428             BufferedReader in = new BufferedReader(new InputStreamReader(
429                     url.openStream()));
430             String line = null;
431             while ((line = in.readLine()) != null)
432             {
433               if (line.indexOf("jalview.version") == -1)
434               {
435                 continue;
436               }
437
438               line = line.substring(line.indexOf("value=") + 7);
439               line = line.substring(0, line.lastIndexOf("\""));
440               jnlpVersion = line;
441               break;
442             }
443           } catch (Exception ex)
444           {
445             System.out
446                     .println("Non-fatal exceptions when checking version at www.jalview.org :");
447             System.out.println(ex);
448             jnlpVersion = getProperty("VERSION");
449           }
450           System.setProperty("sun.net.client.defaultConnectTimeout",
451                   orgtimeout);
452
453           setProperty("LATEST_VERSION", jnlpVersion);
454         }
455       }
456
457       VersionChecker vc = new VersionChecker();
458       vc.start();
459     }
460     else
461     {
462       if (jnlpVersion != null)
463       {
464         setProperty("LATEST_VERSION", jnlpVersion);
465       }
466       else
467       {
468         applicationProperties.remove("LATEST_VERSION");
469       }
470     }
471
472     setProperty("VERSION", codeVersion);
473
474     // LOAD USERDEFINED COLOURS
475     jalview.gui.UserDefinedColours
476             .initUserColourSchemes(getProperty("USER_DEFINED_COLOURS"));
477     jalview.io.PIRFile.useModellerOutput = Cache.getDefault("PIR_MODELLER",
478             false);
479   }
480
481   private static void deleteBuildProperties()
482   {
483     applicationProperties.remove("LATEST_VERSION");
484     applicationProperties.remove("VERSION");
485     applicationProperties.remove("AUTHORS");
486     applicationProperties.remove("AUTHORFNAMES");
487     applicationProperties.remove("YEAR");
488     applicationProperties.remove("BUILD_DATE");
489     applicationProperties.remove("INSTALLATION");
490   }
491
492   /**
493    * Gets Jalview application property of given key. Returns null if key not
494    * found
495    * 
496    * @param key
497    *          Name of property
498    * 
499    * @return Property value
500    */
501   public static String getProperty(String key)
502   {
503     return applicationProperties.getProperty(key);
504   }
505
506   /**
507    * These methods are used when checking if the saved preference is different
508    * to the default setting
509    */
510
511   public static boolean getDefault(String property, boolean def)
512   {
513     String string = getProperty(property);
514     if (string != null)
515     {
516       def = Boolean.valueOf(string).booleanValue();
517     }
518
519     return def;
520   }
521
522   /**
523    * These methods are used when checking if the saved preference is different
524    * to the default setting
525    */
526   public static String getDefault(String property, String def)
527   {
528     String string = getProperty(property);
529     if (string != null)
530     {
531       return string;
532     }
533
534     return def;
535   }
536
537   /**
538    * Stores property in the file "HOME_DIR/.jalview_properties"
539    * 
540    * @param key
541    *          Name of object
542    * @param obj
543    *          String value of property
544    * 
545    * @return String value of property
546    */
547   public static String setProperty(String key, String obj)
548   {
549
550     try
551     {
552       applicationProperties.setProperty(key, obj);
553       if (!propsAreReadOnly)
554       {
555         FileOutputStream out = new FileOutputStream(propertiesFile);
556         applicationProperties.store(out, "---JalviewX Properties File---");
557         out.close();
558       }
559     } catch (Exception ex)
560     {
561       System.out.println("Error setting property: " + key + " " + obj
562               + "\n" + ex);
563     }
564     return obj;
565   }
566
567   /**
568    * remove the specified property from the jalview properties file
569    * 
570    * @param string
571    */
572   public static void removeProperty(String string)
573   {
574     applicationProperties.remove(string);
575     saveProperties();
576   }
577
578   /**
579    * save the properties to the jalview properties path
580    */
581   public static void saveProperties()
582   {
583     if (!propsAreReadOnly)
584     {
585       try
586       {
587         FileOutputStream out = new FileOutputStream(propertiesFile);
588         applicationProperties.store(out, "---JalviewX Properties File---");
589         out.close();
590       } catch (Exception ex)
591       {
592         System.out.println("Error saving properties: " + ex);
593       }
594     }
595   }
596
597   /**
598    * internal vamsas class discovery state
599    */
600   private static int vamsasJarsArePresent = -1;
601
602   /**
603    * Searches for vamsas client classes on class path.
604    * 
605    * @return true if vamsas client is present on classpath
606    */
607   public static boolean vamsasJarsPresent()
608   {
609     if (vamsasJarsArePresent == -1)
610     {
611       try
612       {
613         if (jalview.jbgui.GDesktop.class.getClassLoader().loadClass(
614                 "uk.ac.vamsas.client.VorbaId") != null)
615         {
616           jalview.bin.Cache.log
617                   .debug("Found Vamsas Classes (uk.ac..vamsas.client.VorbaId can be loaded)");
618           vamsasJarsArePresent = 1;
619           Logger lvclient = Logger.getLogger("uk.ac.vamsas");
620           lvclient.setLevel(Level.toLevel(Cache.getDefault(
621                   "logs.Vamsas.Level", Level.INFO.toString())));
622
623           lvclient.addAppender(log.getAppender("JalviewLogger"));
624           // Tell the user that debug is enabled
625           lvclient.debug("Jalview Vamsas Client Debugging Output Follows.");
626         }
627       } catch (Exception e)
628       {
629         vamsasJarsArePresent = 0;
630         jalview.bin.Cache.log.debug("Vamsas Classes are not present");
631       }
632     }
633     return (vamsasJarsArePresent > 0);
634   }
635
636   /**
637    * internal vamsas class discovery state
638    */
639   private static int groovyJarsArePresent = -1;
640
641   /**
642    * Searches for vamsas client classes on class path.
643    * 
644    * @return true if vamsas client is present on classpath
645    */
646   public static boolean groovyJarsPresent()
647   {
648     if (groovyJarsArePresent == -1)
649     {
650       try
651       {
652         if (Cache.class.getClassLoader().loadClass(
653                 "groovy.lang.GroovyObject") != null)
654         {
655           jalview.bin.Cache.log
656                   .debug("Found Groovy (groovy.lang.GroovyObject can be loaded)");
657           groovyJarsArePresent = 1;
658           Logger lgclient = Logger.getLogger("groovy");
659           lgclient.setLevel(Level.toLevel(Cache.getDefault(
660                   "logs.Groovy.Level", Level.INFO.toString())));
661
662           lgclient.addAppender(log.getAppender("JalviewLogger"));
663           // Tell the user that debug is enabled
664           lgclient.debug("Jalview Groovy Client Debugging Output Follows.");
665         }
666       } catch (Error e)
667       {
668         groovyJarsArePresent = 0;
669         jalview.bin.Cache.log.debug("Groovy Classes are not present", e);
670       } catch (Exception e)
671       {
672         groovyJarsArePresent = 0;
673         jalview.bin.Cache.log.debug("Groovy Classes are not present");
674       }
675     }
676     return (groovyJarsArePresent > 0);
677   }
678
679   /**
680    * GA tracker object - actually JGoogleAnalyticsTracker null if tracking not
681    * enabled.
682    */
683   protected static Object tracker = null;
684
685   protected static Class trackerfocus = null;
686
687   protected static Class jgoogleanalyticstracker = null;
688
689   /**
690    * Initialise the google tracker if it is not done already.
691    */
692   public static void initGoogleTracker()
693   {
694     if (tracker == null)
695     {
696       if (jgoogleanalyticstracker == null)
697       {
698         // try to get the tracker class
699         try
700         {
701           jgoogleanalyticstracker = Cache.class
702                   .getClassLoader()
703                   .loadClass(
704                           "com.boxysystems.jgoogleanalytics.JGoogleAnalyticsTracker");
705           trackerfocus = Cache.class.getClassLoader().loadClass(
706                   "com.boxysystems.jgoogleanalytics.FocusPoint");
707         } catch (Exception e)
708         {
709           log.debug("com.boxysystems.jgoogleanalytics package is not present - tracking not enabled.");
710           tracker = null;
711           jgoogleanalyticstracker = null;
712           trackerfocus = null;
713           return;
714         }
715       }
716       // now initialise tracker
717       Exception re = null, ex = null;
718       Error err = null;
719       String vrs = "No Version Accessible";
720       try
721       {
722         // Google analytics tracking code for Library Finder
723         tracker = jgoogleanalyticstracker.getConstructor(
724                 new Class[] { String.class, String.class, String.class })
725                 .newInstance(
726                         new Object[] {
727                             "Jalview Desktop",
728                             (vrs = jalview.bin.Cache.getProperty("VERSION")
729                                     + "_"
730                                     + jalview.bin.Cache.getDefault(
731                                             "BUILD_DATE", "unknown")),
732                             "UA-9060947-1" });
733         jgoogleanalyticstracker.getMethod("trackAsynchronously",
734                 new Class[] { trackerfocus }).invoke(
735                 tracker,
736                 new Object[] { trackerfocus.getConstructor(
737                         new Class[] { String.class }).newInstance(
738                         new Object[] { "Application Started." }) });
739       } catch (RuntimeException e)
740       {
741         re = e;
742       } catch (Exception e)
743       {
744         ex = e;
745       } catch (Error e)
746       {
747         err = e;
748       }
749       if (re != null || ex != null || err != null)
750       {
751         if (log != null)
752         {
753           if (re != null)
754           {
755             log.debug("Caught runtime exception in googletracker init:", re);
756           }
757           if (ex != null)
758           {
759             log.warn(
760                     "Failed to initialise GoogleTracker for Jalview Desktop with version "
761                             + vrs, ex);
762           }
763           if (err != null)
764           {
765             log.error(
766                     "Whilst initing GoogleTracker for Jalview Desktop version "
767                             + vrs, err);
768           }
769         }
770         else
771         {
772           if (re != null)
773           {
774             System.err
775                     .println("Debug: Caught runtime exception in googletracker init:"
776                             + vrs);
777             re.printStackTrace();
778           }
779           if (ex != null)
780           {
781             System.err
782                     .println("Warning:  Failed to initialise GoogleTracker for Jalview Desktop with version "
783                             + vrs);
784             ex.printStackTrace();
785           }
786
787           if (err != null)
788           {
789             System.err
790                     .println("ERROR: Whilst initing GoogleTracker for Jalview Desktop version "
791                             + vrs);
792             err.printStackTrace();
793           }
794         }
795       }
796       else
797       {
798         log.debug("Successfully initialised tracker.");
799       }
800     }
801   }
802
803   /**
804    * get the user's default colour if available
805    * 
806    * @param property
807    * @param defcolour
808    * @return
809    */
810   public static Color getDefaultColour(String property, Color defcolour)
811   {
812     String colprop = getProperty(property);
813     if (colprop == null)
814     {
815       return defcolour;
816     }
817     Color col = jalview.schemes.ColourSchemeProperty
818             .getAWTColorFromName(colprop);
819     if (col == null)
820     {
821       try
822       {
823         col = new jalview.schemes.UserColourScheme(colprop).findColour('A');
824       } catch (Exception ex)
825       {
826         log.warn("Couldn't parse '" + colprop + "' as a colour for "
827                 + property);
828         col = null;
829       }
830     }
831     return (col == null) ? defcolour : col;
832   }
833
834   /**
835    * store a colour as a Jalview user default property
836    * 
837    * @param property
838    * @param colour
839    */
840   public static void setColourProperty(String property, Color colour)
841   {
842     setProperty(property, jalview.util.Format.getHexString(colour));
843   }
844
845   public static final DateFormat date_format = SimpleDateFormat
846           .getDateTimeInstance();
847
848   /**
849    * store a date in a jalview property
850    * 
851    * @param string
852    * @param time
853    */
854   public static void setDateProperty(String property, Date time)
855   {
856     setProperty(property, date_format.format(time));
857   }
858
859   /**
860    * read a date stored in a jalview property
861    * 
862    * @param property
863    * @return valid date as stored by setDateProperty, or null
864    * 
865    */
866   public static Date getDateProperty(String property)
867   {
868     String val = getProperty(property);
869     if (val != null)
870     {
871       try
872       {
873         return date_format.parse(val);
874       } catch (Exception ex)
875       {
876         System.err.println("Invalid or corrupt date in property '"
877                 + property + "' : value was '" + val + "'");
878       }
879     }
880     return null;
881   }
882
883   /**
884    * get and parse a property as an integer. send any parsing problems to
885    * System.err
886    * 
887    * @param property
888    * @return null or Integer
889    */
890   public static Integer getIntegerProperty(String property)
891   {
892     String val = getProperty(property);
893     if (val != null && (val = val.trim()).length() > 0)
894     {
895       try
896       {
897         return Integer.valueOf(val);
898       } catch (NumberFormatException x)
899       {
900         System.err.println("Invalid integer in property '" + property
901                 + "' (value was '" + val + "')");
902       }
903     }
904     return null;
905   }
906
907   private static DasSourceRegistryI sourceRegistry = null;
908
909   /**
910    * initialise and ..
911    * 
912    * @return instance of the das source registry
913    */
914   public static DasSourceRegistryI getDasSourceRegistry()
915   {
916     if (sourceRegistry == null)
917     {
918       sourceRegistry = new DasSourceRegistry();
919     }
920     return sourceRegistry;
921   }
922
923   /**
924    * Set the specified value, or remove it if null or empty. Does not save the
925    * properties file.
926    * 
927    * @param propName
928    * @param value
929    */
930   public static void setOrRemove(String propName, String value)
931   {
932     if (propName == null)
933     {
934       return;
935     }
936     if (value == null || value.trim().length() < 1)
937     {
938       Cache.applicationProperties.remove(propName);
939     }
940     else
941     {
942       Cache.applicationProperties.setProperty(propName, value);
943     }
944   }
945 }