Merge branch 'JAL-3927_ws_preferences_dialog_fix' into alpha/JAL-3066_Jalview_212_sli...
[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 java.awt.Color;
24 import java.io.BufferedReader;
25 import java.io.FileInputStream;
26 import java.io.FileOutputStream;
27 import java.io.InputStream;
28 import java.io.InputStreamReader;
29 import java.net.URL;
30 import java.text.DateFormat;
31 import java.text.SimpleDateFormat;
32 import java.util.Collections;
33 import java.util.Date;
34 import java.util.Enumeration;
35 import java.util.Locale;
36 import java.util.Properties;
37 import java.util.StringTokenizer;
38 import java.util.TreeSet;
39
40 import org.apache.log4j.ConsoleAppender;
41 import org.apache.log4j.Level;
42 import org.apache.log4j.Logger;
43 import org.apache.log4j.SimpleLayout;
44
45 import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
46 import jalview.datamodel.PDBEntry;
47 import jalview.gui.UserDefinedColours;
48 import jalview.schemes.ColourSchemeLoader;
49 import jalview.schemes.ColourSchemes;
50 import jalview.schemes.UserColourScheme;
51 import jalview.structure.StructureImportSettings;
52 import jalview.urls.IdOrgSettings;
53 import jalview.util.ColorUtils;
54 import jalview.util.Platform;
55 import jalview.ws.sifts.SiftsSettings;
56
57 /**
58  * Stores and retrieves Jalview Application Properties Lists and fields within
59  * list entries are separated by '|' symbols unless otherwise stated (|) clauses
60  * are alternative values for a tag. <br>
61  * <br>
62  * Current properties include:
63  * <ul>
64  * <br>
65  * logs.Axis.Level - one of the stringified Levels for log4j controlling the
66  * logging level for axis (used for web services) <br>
67  * </li>
68  * <li>logs.Castor.Level - one of the stringified Levels for log4j controlling
69  * the logging level for castor (used for serialization) <br>
70  * </li>
71  * <li>logs.Jalview.Level - Cache.log stringified level. <br>
72  * </li>
73  * <li>SCREEN_WIDTH</li>
74  * <li>SCREEN_HEIGHT</li>
75  * <li>SCREEN_Y=285</li>
76  * <li>SCREEN_X=371</li>
77  * <li>SHOW_FULLSCREEN boolean</li>
78  * <li>FONT_NAME java font name for alignment text display</li>
79  * <li>FONT_SIZE size of displayed alignment text</li>
80  * <li>FONT_STYLE style of font displayed (sequence labels are always
81  * italic)</li>
82  * <li>GAP_SYMBOL character to treat as gap symbol (usually -,.,' ')</li>
83  * <li>LAST_DIRECTORY last directory for browsing alignment</li>
84  * <li>USER_DEFINED_COLOURS list of user defined colour scheme files</li>
85  * <li>SHOW_FULL_ID show id with '/start-end' numbers appended</li>
86  * <li>SHOW_IDENTITY show percentage identity annotation</li>
87  * <li>SHOW_QUALITY show alignment quality annotation</li>
88  * <li>SHOW_ANNOTATIONS show alignment annotation rows</li>
89  * <li>SHOW_CONSERVATION show alignment conservation annotation</li>
90  * <li>SORT_ANNOTATIONS currently either SEQUENCE_AND_LABEL or
91  * LABEL_AND_SEQUENCE</li>
92  * <li>SHOW_AUTOCALC_ABOVE true to show autocalculated annotations above
93  * sequence annotations</li>
94  * <li>CENTRE_COLUMN_LABELS centre the labels at each column in a displayed
95  * annotation row</li>
96  * <li>DEFAULT_COLOUR default colour scheme to apply for a new alignment</li>
97  * <li>DEFAULT_FILE_FORMAT file format used to save</li>
98  * <li>STARTUP_FILE file loaded on startup (may be a fully qualified url)</li>
99  * <li>SHOW_STARTUP_FILE flag to control loading of startup file</li>
100  * <li>VERSION the version of the jalview build</li>
101  * <li>BUILD_DATE date of this build</li>
102  * <li>LATEST_VERSION the latest jalview version advertised on the
103  * www.jalview.org</li>
104  * <li>PIR_MODELLER boolean indicating if PIR files are written with MODELLER
105  * descriptions</li>
106  * <li>(FASTA,MSF,PILEUP,CLUSTAL,BLC,PIR,PFAM)_JVSUFFIX boolean for adding jv
107  * suffix to file</li>
108  * <li>RECENT_URL list of recently retrieved URLs</li>
109  * <li>RECENT_FILE list of recently opened files</li>
110  * <li>USE_PROXY flag for whether a http proxy is to be used</li>
111  * <li>PROXY_SERVER the proxy</li>
112  * <li>PROXY_PORT</li>
113  * <li>NOQUESTIONNAIRES true to prevent jalview from checking the questionnaire
114  * service</li>
115  * <li>QUESTIONNAIRE last questionnaire:responder id string from questionnaire
116  * service</li>
117  * <li>USAGESTATS (false - user prompted) Enable google analytics tracker for
118  * collecting usage statistics</li>
119  * <li>SHOW_OVERVIEW boolean for overview window display</li>
120  * <li>ANTI_ALIAS boolean for smooth fonts</li>
121  * <li>RIGHT_ALIGN_IDS boolean</li>
122  * <li>AUTO_CALC_CONSENSUS boolean for automatic recalculation of consensus</li>
123  * <li>PAD_GAPS boolean</li>
124  * <li>ID_ITALICS boolean</li>
125  * <li>SHOW_JV_SUFFIX</li>
126  * <li>WRAP_ALIGNMENT</li>
127  * <li>EPS_RENDERING (Prompt each time|Lineart|Text) default for EPS rendering
128  * style check</li>
129  * <li>SORT_ALIGNMENT (No sort|Id|Pairwise Identity)</li>
130  * <li>SEQUENCE_LINKS list of name|URL pairs for opening a url with
131  * $SEQUENCE_ID$</li>
132  * <li>STORED_LINKS list of name|url pairs which user has entered but are not
133  * currently used
134  * <li>DEFAULT_LINK name of single url to be used when user double clicks a
135  * sequence id (must be in SEQUENCE_LINKS or STORED_LINKS)
136  * <li>GROUP_LINKS list of name|URL[|&lt;separator&gt;] tuples - see
137  * jalview.utils.GroupURLLink for more info</li>
138  * <li>DEFAULT_BROWSER for unix</li>
139  * <li>SHOW_MEMUSAGE boolean show memory usage and warning indicator on desktop
140  * (false)</li>
141  * <li>VERSION_CHECK (true) check for the latest release version from
142  * www.jalview.org (or the alias given by the www.jalview.org property)</li>
143  * <li>SHOW_NPFEATS_TOOLTIP (true) show non-positional features in the Sequence
144  * ID tooltip</li>
145  * <li>SHOW_DBREFS_TOOLTIP (true) show Database Cross References in the Sequence
146  * ID tooltip</li>
147  * <li>SHOW_UNCONSERVED (false) only render unconserved residues - conserved
148  * displayed as '.'</li>
149  * <li>SORT_BY_TREE (false) sort the current alignment view according to the
150  * order of a newly displayed tree</li>
151  * <li>DBFETCH_USEPICR (false) use PICR to recover valid DB references from
152  * sequence ID strings before attempting retrieval from any datasource</li>
153  * <li>SHOW_GROUP_CONSENSUS (false) Show consensus annotation for groups in the
154  * alignment.</li>
155  * <li>SHOW_GROUP_CONSERVATION (false) Show conservation annotation for groups
156  * in the alignment.</li>
157  * <li>SHOW_CONSENSUS_HISTOGRAM (false) Show consensus annotation row's
158  * histogram.</li>
159  * <li>SHOW_CONSENSUS_LOGO (false) Show consensus annotation row's sequence
160  * logo.</li>
161  * <li>NORMALISE_CONSENSUS_LOGO (false) Show consensus annotation row's sequence
162  * logo normalised to row height rather than histogram height.</li>
163  * <li>FOLLOW_SELECTIONS (true) Controls whether a new alignment view should
164  * respond to selections made in other alignments containing the same sequences.
165  * </li>
166  * <li>JWS2HOSTURLS comma-separated list of URLs to try for JABAWS services</li>
167  * <li>SHOW_WSDISCOVERY_ERRORS (true) Controls if the web service URL discovery
168  * warning dialog box is displayed.</li>
169  * <li>ANNOTATIONCOLOUR_MIN (orange) Shade used for minimum value of annotation
170  * when shading by annotation</li>
171  * <li>ANNOTATIONCOLOUR_MAX (red) Shade used for maximum value of annotation
172  * when shading by annotation</li>
173  * <li>www.jalview.org (http://www.jalview.org) a property enabling all HTTP
174  * requests to be redirected to a mirror of http://www.jalview.org</li>
175  * <li>FIGURE_AUTOIDWIDTH (false) Expand the left hand column of an exported
176  * alignment figure to accommodate even the longest sequence ID or annotation
177  * label.</li>
178  * <li>FIGURE_FIXEDIDWIDTH Specifies the width to use for the left-hand column
179  * when exporting an alignment as a figure (setting FIGURE_AUTOIDWIDTH to true
180  * will override this).</li>
181  * <li>STRUCT_FROM_PDB (false) derive secondary structure annotation from PDB
182  * record</li>
183  * <li>USE_RNAVIEW (false) use RNAViewer to derive secondary structure</li>
184  * <li>ADD_SS_ANN (false) add secondary structure annotation to alignment
185  * display</li>
186  * <li>ADD_TEMPFACT_ANN (false) add Temperature Factor annotation to alignment
187  * display</li>
188  * <li>STRUCTURE_DISPLAY choose from JMOL (default) or CHIMERA for 3D structure
189  * display</li>
190  * <li>CHIMERA_PATH specify full path to Chimera program (if non-standard)</li>
191  * <li>ID_ORG_HOSTURL location of jalview service providing identifiers.org urls
192  * </li>
193  * 
194  * </ul>
195  * Deprecated settings:
196  * <ul>
197  * *
198  * <li>DISCOVERY_START - Boolean - controls if discovery services are queried on
199  * startup (JWS1 services only)</li>
200  * <li>DISCOVERY_URLS - comma separated list of Discovery Service endpoints.
201  * (JWS1 services only)</li>
202  * <li>SHOW_JWS1_SERVICES (true) enable or disable the original Jalview 2
203  * services in the desktop GUI</li>
204  * <li>ENABLE_RSBS_EDITOR (false for 2.7 release) enable or disable RSBS editing
205  * panel in web service preferences</li>
206  * </ul>
207  * 
208  * @author $author$
209  * @version $Revision$
210  */
211 public class Cache implements ApplicationSingletonI
212 {
213
214   private Cache()
215   {
216     // private singleton
217   }
218
219   /**
220    * In Java, this will be a static field instance, which will be
221    * application-specific; in JavaScript it will be an applet-specific instance
222    * tied to the applet's ThreadGroup.
223    * 
224    * @return
225    */
226   public static Cache getInstance()
227   {
228     return (Cache) ApplicationSingletonProvider.getInstance(Cache.class);
229   }
230
231   /**
232    * property giving log4j level for CASTOR loggers
233    */
234   public static final String CASTORLOGLEVEL = "logs.Castor.level";
235
236   /**
237    * property giving log4j level for AXIS loggers
238    */
239   public static final String AXISLOGLEVEL = "logs.Axis.level";
240
241   /**
242    * property giving log4j level for Jalview Log
243    */
244   public static final String JALVIEWLOGLEVEL = "logs.Jalview.level";
245
246   /**
247    * Sifts settings
248    */
249   public static final String DEFAULT_SIFTS_DOWNLOAD_DIR = Platform.getUserPath(".sifts_downloads/");
250   
251   private final static String DEFAULT_CACHE_THRESHOLD_IN_DAYS = "2";
252
253   private final static String DEFAULT_FAIL_SAFE_PID_THRESHOLD = "30";
254
255   /**
256    * Identifiers.org download settings
257    */
258   private static final String ID_ORG_FILE = Platform.getUserPath(".identifiers.org.ids.json");
259
260   /**
261    * Allowed values are PDB or mmCIF
262    */
263   private final static String PDB_DOWNLOAD_FORMAT = PDBEntry.Type.MMCIF
264           .toString();
265
266   private final static String DEFAULT_PDB_FILE_PARSER = StructureImportSettings.StructureParser.JMOL_PARSER
267           .toString();
268
269   /*
270    * a date formatter using a fixed (rather than the user's) locale; 
271    * this ensures that date properties can be written and re-read successfully
272    * even if the user changes their locale setting
273    */
274   private static final DateFormat date_format = SimpleDateFormat
275           .getDateTimeInstance(SimpleDateFormat.MEDIUM,
276                   SimpleDateFormat.MEDIUM, Locale.UK);
277
278   /**
279    * Initialises the Jalview Application Log
280    */
281   public static Logger log;
282
283   /** Jalview Properties */
284   private Properties applicationProperties = new Properties()
285   {
286     // override results in properties output in alphabetical order
287     @Override
288     public synchronized Enumeration<Object> keys()
289     {
290       return Collections.enumeration(new TreeSet<>(super.keySet()));
291     }
292   };
293
294   /** Default file is ~/.jalview_properties */
295   static String propertiesFile;
296
297   private static boolean propsAreReadOnly = Platform.isJS();
298
299   private final static String JS_PROPERTY_PREFIX = "jalview_";
300
301   public static void initLogger()
302   {
303     if (log != null)
304     {
305       return;
306     }
307     try
308     {
309       // TODO: redirect stdout and stderr here in order to grab the output of
310       // the log
311
312       ConsoleAppender ap = new ConsoleAppender(new SimpleLayout(),
313               "System.err");
314       ap.setName("JalviewLogger");
315       org.apache.log4j.Logger.getRootLogger().addAppender(ap); // catch all for
316       // log output
317       Logger laxis = Logger.getLogger("org.apache.axis");
318       Logger lcastor = Logger.getLogger("org.exolab.castor");
319       jalview.bin.Cache.log = Logger.getLogger("jalview.bin.Jalview");
320
321       laxis.setLevel(Level.toLevel(
322               Cache.getDefault("logs.Axis.Level", Level.INFO.toString())));
323       lcastor.setLevel(Level.toLevel(Cache.getDefault("logs.Castor.Level",
324               Level.INFO.toString())));
325       lcastor = Logger.getLogger("org.exolab.castor.xml");
326       lcastor.setLevel(Level.toLevel(Cache.getDefault("logs.Castor.Level",
327               Level.INFO.toString())));
328       // lcastor = Logger.getLogger("org.exolab.castor.xml.Marshaller");
329       // lcastor.setLevel(Level.toLevel(Cache.getDefault("logs.Castor.Level",
330       // Level.INFO.toString())));
331       // we shouldn't need to do this
332       org.apache.log4j.Logger.getRootLogger().setLevel(org.apache.log4j.Level.INFO); 
333
334       jalview.bin.Cache.log.setLevel(Level.toLevel(Cache
335               .getDefault("logs.Jalview.level", Level.INFO.toString())));
336       // laxis.addAppender(ap);
337       // lcastor.addAppender(ap);
338       // jalview.bin.Cache.log.addAppender(ap);
339       // Tell the user that debug is enabled
340       jalview.bin.Cache.log.debug("Jalview Debugging Output Follows.");
341     } catch (Exception ex)
342     {
343       System.err.println("Problems initializing the log4j system\n");
344       ex.printStackTrace(System.err);
345     }
346   }
347
348   /**
349    * Loads properties from the given properties file. Any existing properties
350    * are first cleared.
351    */
352   public static void loadProperties(String propsFile)
353   {
354
355     getInstance().loadPropertiesImpl(propsFile);
356
357   }
358
359   private void loadPropertiesImpl(String propsFile)
360   {
361
362     propertiesFile = propsFile;
363     if (propsFile == null && !propsAreReadOnly)
364     {
365       propertiesFile = Platform.getUserPath(".jalview_properties");
366     }
367     else
368     {
369       // don't corrupt the file we've been given.
370       propsAreReadOnly = true;
371     }
372
373     if (propertiesFile == null)
374     { // BH 2019
375       Platform.readInfoProperties(JS_PROPERTY_PREFIX,
376               applicationProperties);
377     }
378     else
379     {
380       try
381       {
382         InputStream fis;
383         try
384         {
385           fis = new java.net.URL(propertiesFile).openStream();
386           System.out.println(
387                   "Loading jalview properties from : " + propertiesFile);
388           System.out.println(
389                   "Disabling Jalview writing to user's local properties file.");
390           propsAreReadOnly = true;
391
392         } catch (Exception ex)
393         {
394           fis = null;
395         }
396         if (fis == null)
397         {
398           fis = new FileInputStream(propertiesFile);
399         }
400         applicationProperties.clear();
401         applicationProperties.load(fis);
402
403         // remove any old build properties
404
405         deleteBuildProperties();
406         fis.close();
407       } catch (Exception ex)
408       {
409         System.out.println("Error reading properties file: " + ex);
410       }
411     }
412     if (getDefault("USE_PROXY", false))
413     {
414       String proxyServer = getDefault("PROXY_SERVER", ""),
415               proxyPort = getDefault("PROXY_PORT", "8080");
416
417       System.out.println("Using proxyServer: " + proxyServer
418               + " proxyPort: " + proxyPort);
419
420       System.setProperty("http.proxyHost", proxyServer);
421       System.setProperty("http.proxyPort", proxyPort);
422     }
423
424     // LOAD THE AUTHORS FROM THE authors.props file
425     String authorDetails = resolveResourceURLFor("/authors.props");
426
427     try
428     {
429       if (authorDetails != null)
430       {
431         URL localJarFileURL = new URL(authorDetails);
432         InputStream in = localJarFileURL.openStream();
433         applicationProperties.load(in);
434         in.close();
435       }
436     } catch (Exception ex)
437     {
438       System.out.println("Error reading author details: " + ex);
439       authorDetails = null;
440     }
441     if (authorDetails == null)
442     {
443       applicationProperties.remove("AUTHORS");
444       applicationProperties.remove("AUTHORFNAMES");
445       applicationProperties.remove("YEAR");
446     }
447
448     loadBuildProperties(false);
449
450     SiftsSettings
451             .setMapWithSifts(Cache.getDefault("MAP_WITH_SIFTS", false));
452
453     SiftsSettings.setSiftDownloadDirectory(jalview.bin.Cache
454             .getDefault("sifts_download_dir", DEFAULT_SIFTS_DOWNLOAD_DIR));
455
456     SiftsSettings.setFailSafePIDThreshold(
457             jalview.bin.Cache.getDefault("sifts_fail_safe_pid_threshold",
458                     DEFAULT_FAIL_SAFE_PID_THRESHOLD));
459
460     SiftsSettings.setCacheThresholdInDays(
461             jalview.bin.Cache.getDefault("sifts_cache_threshold_in_days",
462                     DEFAULT_CACHE_THRESHOLD_IN_DAYS));
463
464     IdOrgSettings.setUrl(getDefault("ID_ORG_HOSTURL",
465             "http://www.jalview.org/services/identifiers"));
466     IdOrgSettings.setDownloadLocation(ID_ORG_FILE);
467
468     StructureImportSettings.setDefaultStructureFileFormat(jalview.bin.Cache
469             .getDefault("PDB_DOWNLOAD_FORMAT", PDB_DOWNLOAD_FORMAT));
470     StructureImportSettings
471             .setDefaultPDBFileParser(DEFAULT_PDB_FILE_PARSER);
472     // StructureImportSettings
473     // .setDefaultPDBFileParser(jalview.bin.Cache.getDefault(
474     // "DEFAULT_PDB_FILE_PARSER", DEFAULT_PDB_FILE_PARSER));
475
476     String jnlpVersion = System.getProperty("jalview.version");
477
478     // jnlpVersion will be null if a latest version check for the channel needs
479     // to
480     // be done
481     // Dont do this check if running in headless mode
482
483     if (jnlpVersion == null && getDefault("VERSION_CHECK", true)
484             && (System.getProperty("java.awt.headless") == null || System
485                     .getProperty("java.awt.headless").equals("false")))
486     {
487       new Thread()
488       {
489         @Override
490         public void run()
491         {
492           String orgtimeout = System
493                   .getProperty("sun.net.client.defaultConnectTimeout");
494           if (orgtimeout == null)
495           {
496             orgtimeout = "30";
497             System.out.println("# INFO: Setting default net timeout to "
498                     + orgtimeout + " seconds.");
499           }
500           String remoteVersion = null;
501           try
502           {
503             System.setProperty("sun.net.client.defaultConnectTimeout",
504                     "5000");
505             java.net.URL url = new java.net.URL(Cache
506                     .getDefault("www.jalview.org", "http://www.jalview.org")
507                     + "/webstart/jalview.jnlp");
508             BufferedReader in = new BufferedReader(
509                     new InputStreamReader(url.openStream()));
510             String line = null;
511             while ((line = in.readLine()) != null)
512             {
513               if (line.indexOf("jalview.version") == -1)
514               {
515                 continue;
516               }
517
518               line = line.substring(line.indexOf("value=") + 7);
519               line = line.substring(0, line.lastIndexOf("\""));
520               remoteVersion = line;
521               break;
522             }
523           } catch (Exception ex)
524           {
525             System.out.println(
526                     "Non-fatal exception when checking version at www.jalview.org :");
527             System.out.println(ex);
528             remoteVersion = getProperty("VERSION");
529           }
530           System.setProperty("sun.net.client.defaultConnectTimeout",
531                   orgtimeout);
532
533           setProperty("LATEST_VERSION", remoteVersion);
534         }
535       }.start();
536     }
537     else
538     {
539       if (jnlpVersion != null)
540       {
541         setProperty("LATEST_VERSION", jnlpVersion);
542       }
543       else
544       {
545         applicationProperties.remove("LATEST_VERSION");
546       }
547     }
548
549     // LOAD USERDEFINED COLOURS
550     Cache.initUserColourSchemes(getProperty("USER_DEFINED_COLOURS"));
551     jalview.io.PIRFile.useModellerOutput = Cache.getDefault("PIR_MODELLER",
552             false);
553   }
554
555   /**
556    * construct a resource URL for the given absolute resource pathname
557    * 
558    * @param resourcePath
559    * @return
560    */
561   private static String resolveResourceURLFor(String resourcePath)
562   {
563     String url = null;
564     if (Platform.isJS() || !Cache.class.getProtectionDomain()
565             .getCodeSource().getLocation().toString().endsWith(".jar"))
566     {
567       try
568       {
569         url = Cache.class.getResource(resourcePath).toString();
570       } catch (Exception ex)
571       {
572         System.err.println("Failed to resolve resource " + resourcePath
573                 + ": " + ex.getMessage());
574       }
575     }
576     else
577     {
578       url = "jar:".concat(Cache.class.getProtectionDomain().getCodeSource()
579               .getLocation().toString().concat("!" + resourcePath));
580     }
581     return url;
582   }
583
584   public void loadBuildProperties(boolean reportVersion)
585   {
586     String codeInstallation = getProperty("INSTALLATION");
587     boolean printVersion = codeInstallation == null;
588
589     /*
590      * read build properties - from the Jalview jar for a Java distribution,
591      * or from codebase file in test or JalviewJS context
592      */
593     try
594     {
595       String buildDetails = resolveResourceURLFor("/.build_properties");
596       URL localJarFileURL = new URL(buildDetails);
597       InputStream in = localJarFileURL.openStream();
598       applicationProperties.load(in);
599       in.close();
600     } catch (Exception ex)
601     {
602       System.out.println("Error reading build details: " + ex);
603       applicationProperties.remove("VERSION");
604     }
605     String codeVersion = getProperty("VERSION");
606     codeInstallation = getProperty("INSTALLATION");
607
608     if (codeVersion == null)
609     {
610       // THIS SHOULD ONLY BE THE CASE WHEN TESTING!!
611       codeVersion = "Test";
612       codeInstallation = "";
613     }
614     else
615     {
616       codeInstallation = " (" + codeInstallation + ")";
617     }
618     setProperty("VERSION", codeVersion);
619     new BuildDetails(codeVersion, null, codeInstallation);
620     if (printVersion && reportVersion)
621     {
622       System.out.println(
623               "Jalview Version: " + codeVersion + codeInstallation);
624     }
625   }
626
627   private void deleteBuildProperties()
628   {
629     applicationProperties.remove("LATEST_VERSION");
630     applicationProperties.remove("VERSION");
631     applicationProperties.remove("AUTHORS");
632     applicationProperties.remove("AUTHORFNAMES");
633     applicationProperties.remove("YEAR");
634     applicationProperties.remove("BUILD_DATE");
635     applicationProperties.remove("INSTALLATION");
636   }
637
638   /**
639    * Gets Jalview application property of given key. Returns null if key not
640    * found
641    * 
642    * @param key
643    *          Name of property
644    * 
645    * @return Property value
646    */
647   public static String getProperty(String key)
648   {
649     String prop = getInstance().applicationProperties.getProperty(key);
650     // if (prop == null && Platform.isJS())
651     // {
652     // prop = applicationProperties.getProperty(Platform.getUniqueAppletID()
653     // + "_" + JS_PROPERTY_PREFIX + key);
654     // }
655     return prop;
656   }
657
658   /**
659    * These methods are used when checking if the saved preference is different
660    * to the default setting
661    */
662
663   public static boolean getDefault(String property, boolean def)
664   {
665     String string = getProperty(property);
666     if (string != null)
667     {
668       def = Boolean.valueOf(string).booleanValue();
669     }
670
671     return def;
672   }
673
674   public static int getDefault(String property, int def)
675   {
676     String string = getProperty(property);
677     if (string != null)
678     {
679       try
680       {
681         def = Integer.parseInt(string);
682       } catch (NumberFormatException e)
683       {
684         System.out.println("Error parsing int property '" + property
685                 + "' with value '" + string + "'");
686       }
687     }
688
689     return def;
690   }
691
692   /**
693    * Answers the value of the given property, or the supplied default value if
694    * the property is not set
695    */
696   public static String getDefault(String property, String def)
697   {
698     String value = getProperty(property);
699     return value == null ? def : value;
700   }
701
702   /**
703    * Stores property in the file "HOME_DIR/.jalview_properties"
704    * 
705    * @param key
706    *          Name of object
707    * @param obj
708    *          String value of property
709    * 
710    * @return previous value of property (or null)
711    */
712   public static Object setProperty(String key, String obj)
713   {
714     return getInstance().setPropertyImpl(key, obj, true);
715   }
716
717   /**
718    * Removes the specified property from the jalview properties file
719    * 
720    * @param key
721    */
722   public static void removeProperty(String key)
723   {
724     getInstance().removePropertyImpl(key, true);
725   }
726
727   /**
728    * Removes the named property for the running application, without saving the
729    * properties file
730    * 
731    * BH noting that ColourMenuHelper calls this. If the intent is to save, then
732    * simply chanet that call to removeProperty(key).
733    * 
734    * @param key
735    */
736   public static void removePropertyNoSave(String key)
737   {
738
739     getInstance().
740
741             removePropertyImpl(key, false);
742   }
743
744   /**
745    * Removes the named property, and optionally saves the current properties to
746    * file
747    * 
748    * @param key
749    * @param andSave
750    */
751   private void removePropertyImpl(String key, boolean andSave)
752   {
753     applicationProperties.remove(key);
754     if (andSave)
755       saveProperties();
756   }
757
758   /**
759    * save the properties to the jalview properties path
760    */
761   public static void saveProperties()
762   {
763     getInstance().savePropertiesImpl();
764   }
765
766   /**
767    * save the properties to the jalview properties path
768    */
769   private void savePropertiesImpl()
770
771   {
772     if (!propsAreReadOnly)
773     {
774       try
775       {
776         FileOutputStream out = new FileOutputStream(propertiesFile);
777         applicationProperties.store(out, "---JalviewX Properties File---");
778         out.close();
779       } catch (Exception ex)
780       {
781         System.out.println("Error saving properties: " + ex);
782       }
783     }
784   }
785
786   /**
787    * internal vamsas class discovery state
788    */
789   private static int vamsasJarsArePresent = -1;
790
791   /**
792    * Searches for vamsas client classes on class path.
793    * 
794    * @return true if vamsas client is present on classpath
795    */
796   public static boolean vamsasJarsPresent()
797   {
798     if (vamsasJarsArePresent == -1)
799     {
800       try
801       {
802         if (jalview.jbgui.GDesktop.class.getClassLoader()
803                 .loadClass("uk.ac.vamsas.client.VorbaId") != null)
804         {
805           jalview.bin.Cache.log.debug(
806                   "Found Vamsas Classes (uk.ac..vamsas.client.VorbaId can be loaded)");
807           vamsasJarsArePresent = 1;
808           Logger lvclient = Logger.getLogger("uk.ac.vamsas");
809           lvclient.setLevel(Level.toLevel(Cache
810                   .getDefault("logs.Vamsas.Level", Level.INFO.toString())));
811
812           lvclient.addAppender(log.getAppender("JalviewLogger"));
813           // Tell the user that debug is enabled
814           lvclient.debug("Jalview Vamsas Client Debugging Output Follows.");
815         }
816       } catch (Exception e)
817       {
818         vamsasJarsArePresent = 0;
819         jalview.bin.Cache.log.debug("Vamsas Classes are not present");
820       }
821     }
822     return (vamsasJarsArePresent > 0);
823   }
824
825   /**
826    * internal vamsas class discovery state
827    */
828   private static int groovyJarsArePresent = -1;
829
830   /**
831    * Searches for vamsas client classes on class path.
832    * 
833    * @return true if vamsas client is present on classpath
834    */
835   public static boolean groovyJarsPresent()
836   {
837     if (groovyJarsArePresent == -1)
838     {
839       try
840       {
841         if (Cache.class.getClassLoader()
842                 .loadClass("groovy.lang.GroovyObject") != null)
843         {
844           jalview.bin.Cache.log.debug(
845                   "Found Groovy (groovy.lang.GroovyObject can be loaded)");
846           groovyJarsArePresent = 1;
847           Logger lgclient = Logger.getLogger("groovy");
848           lgclient.setLevel(Level.toLevel(Cache
849                   .getDefault("logs.Groovy.Level", Level.INFO.toString())));
850
851           lgclient.addAppender(log.getAppender("JalviewLogger"));
852           // Tell the user that debug is enabled
853           lgclient.debug("Jalview Groovy Client Debugging Output Follows.");
854         }
855       } catch (Error e)
856       {
857         groovyJarsArePresent = 0;
858         jalview.bin.Cache.log.debug("Groovy Classes are not present", e);
859       } catch (Exception e)
860       {
861         groovyJarsArePresent = 0;
862         jalview.bin.Cache.log.debug("Groovy Classes are not present");
863       }
864     }
865     return (groovyJarsArePresent > 0);
866   }
867
868   /**
869    * GA tracker object - actually JGoogleAnalyticsTracker null if tracking not
870    * enabled.
871    */
872   protected static Object tracker = null;
873
874   protected static Class trackerfocus = null;
875
876   protected static Class jgoogleanalyticstracker = null;
877
878   /**
879    * Initialise the google tracker if it is not done already.
880    */
881   public static void initGoogleTracker()
882   {
883     if (tracker == null)
884     {
885       if (jgoogleanalyticstracker == null)
886       {
887         // try to get the tracker class
888         try
889         {
890           jgoogleanalyticstracker = Cache.class.getClassLoader().loadClass(
891                   "com.boxysystems.jgoogleanalytics.JGoogleAnalyticsTracker");
892           trackerfocus = Cache.class.getClassLoader()
893                   .loadClass("com.boxysystems.jgoogleanalytics.FocusPoint");
894         } catch (Exception e)
895         {
896           log.debug(
897                   "com.boxysystems.jgoogleanalytics package is not present - tracking not enabled.");
898           tracker = null;
899           jgoogleanalyticstracker = null;
900           trackerfocus = null;
901           return;
902         }
903       }
904       // now initialise tracker
905       Exception re = null, ex = null;
906       Error err = null;
907       String vrs = "No Version Accessible";
908       try
909       {
910         // Google analytics tracking code for Library Finder
911         tracker = jgoogleanalyticstracker
912                 .getConstructor(new Class[]
913                 { String.class, String.class, String.class })
914                 .newInstance(new Object[]
915                 { "Jalview Desktop",
916                     (vrs = jalview.bin.Cache.getProperty("VERSION") + "_"
917                             + jalview.bin.Cache.getDefault("BUILD_DATE",
918                                     "unknown")),
919                     "UA-9060947-1" });
920         jgoogleanalyticstracker
921                 .getMethod("trackAsynchronously", new Class[]
922                 { trackerfocus })
923                 .invoke(tracker, new Object[]
924                 { trackerfocus.getConstructor(new Class[] { String.class })
925                         .newInstance(new Object[]
926                         { "Application Started." }) });
927       } catch (RuntimeException e)
928       {
929         re = e;
930       } catch (Exception e)
931       {
932         ex = e;
933       } catch (Error e)
934       {
935         err = e;
936       }
937       if (re != null || ex != null || err != null)
938       {
939         if (log != null)
940         {
941           if (re != null)
942           {
943             log.debug("Caught runtime exception in googletracker init:",
944                     re);
945           }
946           if (ex != null)
947           {
948             log.warn(
949                     "Failed to initialise GoogleTracker for Jalview Desktop with version "
950                             + vrs,
951                     ex);
952           }
953           if (err != null)
954           {
955             log.error(
956                     "Whilst initing GoogleTracker for Jalview Desktop version "
957                             + vrs,
958                     err);
959           }
960         }
961         else
962         {
963           if (re != null)
964           {
965             System.err.println(
966                     "Debug: Caught runtime exception in googletracker init:"
967                             + vrs);
968             re.printStackTrace();
969           }
970           if (ex != null)
971           {
972             System.err.println(
973                     "Warning:  Failed to initialise GoogleTracker for Jalview Desktop with version "
974                             + vrs);
975             ex.printStackTrace();
976           }
977
978           if (err != null)
979           {
980             System.err.println(
981                     "ERROR: Whilst initing GoogleTracker for Jalview Desktop version "
982                             + vrs);
983             err.printStackTrace();
984           }
985         }
986       }
987       else
988       {
989         log.debug("Successfully initialised tracker.");
990       }
991     }
992   }
993
994   /**
995    * get the user's default colour if available
996    * 
997    * @param property
998    * @param defcolour
999    * @return
1000    */
1001   public static Color getDefaultColour(String property, Color defcolour)
1002   {
1003     String colprop = getProperty(property);
1004     if (colprop == null)
1005     {
1006       return defcolour;
1007     }
1008     Color col = ColorUtils.parseColourString(colprop);
1009     if (col == null)
1010     {
1011       log.warn("Couldn't parse '" + colprop + "' as a colour for "
1012               + property);
1013     }
1014     return (col == null) ? defcolour : col;
1015   }
1016
1017   /**
1018    * store a colour as a Jalview user default property
1019    * 
1020    * @param property
1021    * @param colour
1022    */
1023   public static void setColourPropertyNoSave(String property, Color colour)
1024   {
1025     setPropertyNoSave(property, jalview.util.Format.getHexString(colour));
1026   }
1027
1028   /**
1029    * Stores a formatted date in a jalview property, using a fixed locale.
1030    * 
1031    * @param propertyName
1032    * @param date
1033    * @return the formatted date string
1034    */
1035   public static String setDateProperty(String propertyName, Date date)
1036   {
1037     String formatted = date_format.format(date);
1038     setProperty(propertyName, formatted);
1039     return formatted;
1040   }
1041
1042   /**
1043    * Reads a date stored in a Jalview property, parses it (using a fixed locale
1044    * format) and returns as a Date, or null if parsing fails
1045    * 
1046    * @param propertyName
1047    * @return
1048    * 
1049    */
1050   public static Date getDateProperty(String propertyName)
1051   {
1052     String val = getProperty(propertyName);
1053     
1054     if (val != null)
1055     {
1056       try
1057       {
1058         if ((val = val.trim()).indexOf(",") < 0 && val.indexOf("-") >= 0 && val.indexOf(" ") == val.lastIndexOf(" ")) {
1059           val = val.replace(" ",", ").replace('-',' ');
1060         }
1061         Date date =  date_format.parse(val);
1062         return date;
1063       } catch (Exception ex)
1064       {
1065         System.err.println("Invalid or corrupt date in property '"
1066                 + propertyName + "' : value was '" + val + "'");
1067       }
1068     }
1069     return null;
1070   }
1071
1072   /**
1073    * get and parse a property as an integer. send any parsing problems to
1074    * System.err
1075    * 
1076    * @param property
1077    * @return null or Integer
1078    */
1079   public static Integer getIntegerProperty(String property)
1080   {
1081     String val = getProperty(property);
1082     if (val != null && (val = val.trim()).length() > 0)
1083     {
1084       try
1085       {
1086         return Integer.valueOf(val);
1087       } catch (NumberFormatException x)
1088       {
1089         System.err.println("Invalid integer in property '" + property
1090                 + "' (value was '" + val + "')");
1091       }
1092     }
1093     return null;
1094   }
1095
1096   /**
1097    * Set the specified value, or remove it if null or empty. Does not save the
1098    * properties file.
1099    * 
1100    * @param propName
1101    * @param value
1102    */
1103   public static void setOrRemove(String propName, String value)
1104   {
1105     if (propName == null)
1106     {
1107       return;
1108     }
1109     if (value == null || value.trim().length() < 1)
1110     {
1111       getInstance().applicationProperties.remove(propName);
1112     }
1113     else
1114     {
1115       getInstance().applicationProperties.setProperty(propName, value);
1116     }
1117   }
1118
1119   /**
1120    * Loads in user colour schemes from files.
1121    * 
1122    * @param files
1123    *          a '|'-delimited list of file paths
1124    */
1125   public static void initUserColourSchemes(String files)
1126   {
1127     if (files == null || files.length() == 0)
1128     {
1129       return;
1130     }
1131
1132     // In case colours can't be loaded, we'll remove them
1133     // from the default list here.
1134     StringBuffer coloursFound = new StringBuffer();
1135     StringTokenizer st = new StringTokenizer(files, "|");
1136     while (st.hasMoreElements())
1137     {
1138       String file = st.nextToken();
1139       try
1140       {
1141         UserColourScheme ucs = ColourSchemeLoader.loadColourScheme(file);
1142         if (ucs != null)
1143         {
1144           if (coloursFound.length() > 0)
1145           {
1146             coloursFound.append("|");
1147           }
1148           coloursFound.append(file);
1149           ColourSchemes.getInstance().registerColourScheme(ucs);
1150         }
1151       } catch (Exception ex)
1152       {
1153         System.out.println("Error loading User ColourFile\n" + ex);
1154       }
1155     }
1156     if (!files.equals(coloursFound.toString()))
1157     {
1158       if (coloursFound.toString().length() > 1)
1159       {
1160         setProperty(UserDefinedColours.USER_DEFINED_COLOURS,
1161                 coloursFound.toString());
1162       }
1163       else
1164       {
1165         getInstance().applicationProperties
1166                 .remove(UserDefinedColours.USER_DEFINED_COLOURS);
1167       }
1168     }
1169   }
1170
1171   /**
1172    * Initial logging information helper for various versions output
1173    * 
1174    * @param prefix
1175    * @param value
1176    * @param defaultValue
1177    */
1178   private static void appendIfNotNull(StringBuilder sb, String prefix,
1179           String value, String suffix, String defaultValue)
1180   {
1181     if (value == null && defaultValue == null)
1182     {
1183       return;
1184     }
1185     String line = prefix + (value != null ? value : defaultValue) + suffix;
1186     sb.append(line);
1187   }
1188
1189   /**
1190    * 
1191    * @return Jalview version, build details and JVM platform version for console
1192    */
1193   public static String getVersionDetailsForConsole()
1194   {
1195     StringBuilder sb = new StringBuilder();
1196     sb.append("Jalview Version: "
1197             + jalview.bin.Cache.getDefault("VERSION", "TEST"));
1198     sb.append("\n");
1199     sb.append("Jalview Installation: "
1200             + jalview.bin.Cache.getDefault("INSTALLATION", "unknown"));
1201     sb.append("\n");
1202     sb.append("Build Date: "
1203             + jalview.bin.Cache.getDefault("BUILD_DATE", "unknown"));
1204     sb.append("\n");
1205     sb.append("Java version: " + System.getProperty("java.version"));
1206     sb.append("\n");
1207     sb.append(System.getProperty("os.arch") + " "
1208             + System.getProperty("os.name") + " "
1209             + System.getProperty("os.version"));
1210     sb.append("\n");
1211     appendIfNotNull(sb, "Install4j version: ",
1212             System.getProperty("sys.install4jVersion"), "\n", null);
1213     appendIfNotNull(sb, "Install4j template version: ",
1214             System.getProperty("installer_template_version"), "\n", null);
1215     appendIfNotNull(sb, "Launcher version: ",
1216             System.getProperty("launcher_version"), "\n", null);
1217     if (jalview.bin.Cache.getDefault("VERSION", "TEST")
1218             .equals("DEVELOPMENT"))
1219     {
1220       appendIfNotNull(sb, "Getdown appdir: ",
1221               System.getProperty("getdownappdir"), "\n", null);
1222       appendIfNotNull(sb, "Java home: ", System.getProperty("java.home"),
1223               "\n", "unknown");
1224     }
1225     return sb.toString();
1226   }
1227
1228   /**
1229    * 
1230    * @return build details as reported in splashscreen
1231    */
1232   public static String getBuildDetailsForSplash()
1233   {
1234     // consider returning more human friendly info
1235     // eg 'built from Source' or update channel
1236     return jalview.bin.Cache.getDefault("INSTALLATION", "unknown");
1237   }
1238
1239   /**
1240    * 
1241    * For AppletParams and Preferences ok_actionPerformed and
1242    * startupFileTextfield_mouseClicked
1243    * 
1244    * Sets a property value for the running application, without saving it to the
1245    * properties file
1246    * 
1247    * @param key
1248    * @param obj
1249    */
1250   public static void setPropertyNoSave(String key, String obj)
1251   {
1252     getInstance().setPropertyImpl(key, obj, false);
1253   }
1254
1255   /**
1256    * Sets a property value, and optionally also saves the current properties to
1257    * file
1258    * 
1259    * @param key
1260    * @param obj
1261    * @param andSave
1262    * @return
1263    */
1264   private Object setPropertyImpl(
1265           String key, String obj, boolean andSave)
1266   {
1267     Object oldValue = null;
1268     try
1269     {
1270       oldValue = applicationProperties.setProperty(key, obj);
1271       if (andSave && !propsAreReadOnly && propertiesFile != null)
1272       {
1273         FileOutputStream out = new FileOutputStream(propertiesFile);
1274         applicationProperties.store(out, "---JalviewX Properties File---");
1275         out.close();
1276       }
1277     } catch (Exception ex)
1278     {
1279       System.out.println(
1280               "Error setting property: " + key + " " + obj + "\n" + ex);
1281     }
1282     return oldValue;
1283   }
1284
1285 }