bde636224d962a1838a8e20a7f8e1b155f48ab54
[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.File;
26 import java.io.FileInputStream;
27 import java.io.FileOutputStream;
28 import java.io.InputStream;
29 import java.io.InputStreamReader;
30 import java.io.PrintWriter;
31 import java.io.StringWriter;
32 import java.net.Authenticator;
33 import java.net.PasswordAuthentication;
34 import java.net.URL;
35 import java.text.DateFormat;
36 import java.text.SimpleDateFormat;
37 import java.util.Arrays;
38 import java.util.Collections;
39 import java.util.Date;
40 import java.util.Enumeration;
41 import java.util.Locale;
42 import java.util.Properties;
43 import java.util.StringTokenizer;
44 import java.util.TreeSet;
45 import java.util.regex.Pattern;
46
47 import javax.swing.LookAndFeel;
48 import javax.swing.UIManager;
49
50 import org.apache.log4j.ConsoleAppender;
51 import org.apache.log4j.Level;
52 import org.apache.log4j.Logger;
53 import org.apache.log4j.SimpleLayout;
54 import org.apache.log4j.builders.appender.ConsoleAppenderBuilder;
55 import org.apache.logging.log4j.core.config.Configurator;
56 import org.apache.logging.log4j.core.config.builder.api.AppenderComponentBuilder;
57 import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder;
58 import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory;
59 import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration;
60
61 import jalview.datamodel.PDBEntry;
62 import jalview.gui.Preferences;
63 import jalview.gui.UserDefinedColours;
64 import jalview.schemes.ColourSchemeLoader;
65 import jalview.schemes.ColourSchemes;
66 import jalview.schemes.UserColourScheme;
67 import jalview.structure.StructureImportSettings;
68 import jalview.urls.IdOrgSettings;
69 import jalview.util.ChannelProperties;
70 import jalview.util.ColorUtils;
71 import jalview.util.MessageManager;
72 import jalview.util.Platform;
73 import jalview.ws.sifts.SiftsSettings;
74
75 /**
76  * Stores and retrieves Jalview Application Properties Lists and fields within
77  * list entries are separated by '|' symbols unless otherwise stated (|) clauses
78  * are alternative values for a tag. <br>
79  * <br>
80  * Current properties include:
81  * <ul>
82  * <br>
83  * logs.Axis.Level - one of the stringified Levels for log4j controlling the
84  * logging level for axis (used for web services) <br>
85  * </li>
86  * <li>logs.Castor.Level - one of the stringified Levels for log4j controlling
87  * the logging level for castor (used for serialization) <br>
88  * </li>
89  * <li>logs.Jalview.Level - Cache.log stringified level. <br>
90  * </li>
91  * <li>SCREEN_WIDTH</li>
92  * <li>SCREEN_HEIGHT</li>
93  * <li>SCREEN_Y=285</li>
94  * <li>SCREEN_X=371</li>
95  * <li>SHOW_FULLSCREEN boolean</li>
96  * <li>FONT_NAME java font name for alignment text display</li>
97  * <li>FONT_SIZE size of displayed alignment text</li>
98  * <li>FONT_STYLE style of font displayed (sequence labels are always
99  * italic)</li>
100  * <li>GAP_SYMBOL character to treat as gap symbol (usually -,.,' ')</li>
101  * <li>LAST_DIRECTORY last directory for browsing alignment</li>
102  * <li>USER_DEFINED_COLOURS list of user defined colour scheme files</li>
103  * <li>SHOW_FULL_ID show id with '/start-end' numbers appended</li>
104  * <li>SHOW_IDENTITY show percentage identity annotation</li>
105  * <li>SHOW_QUALITY show alignment quality annotation</li>
106  * <li>SHOW_ANNOTATIONS show alignment annotation rows</li>
107  * <li>SHOW_CONSERVATION show alignment conservation annotation</li>
108  * <li>SORT_ANNOTATIONS currently either SEQUENCE_AND_LABEL or
109  * LABEL_AND_SEQUENCE</li>
110  * <li>SHOW_AUTOCALC_ABOVE true to show autocalculated annotations above
111  * sequence annotations</li>
112  * <li>CENTRE_COLUMN_LABELS centre the labels at each column in a displayed
113  * annotation row</li>
114  * <li>DEFAULT_COLOUR default colour scheme to apply for a new alignment</li>
115  * <li>DEFAULT_FILE_FORMAT file format used to save</li>
116  * <li>STARTUP_FILE file loaded on startup (may be a fully qualified url)</li>
117  * <li>SHOW_STARTUP_FILE flag to control loading of startup file</li>
118  * <li>VERSION the version of the jalview build</li>
119  * <li>BUILD_DATE date of this build</li>
120  * <li>LATEST_VERSION the latest jalview version advertised on the
121  * www.jalview.org</li>
122  * <li>PIR_MODELLER boolean indicating if PIR files are written with MODELLER
123  * descriptions</li>
124  * <li>(FASTA,MSF,PILEUP,CLUSTAL,BLC,PIR,PFAM)_JVSUFFIX boolean for adding jv
125  * suffix to file</li>
126  * <li>RECENT_URL list of recently retrieved URLs</li>
127  * <li>RECENT_FILE list of recently opened files</li>
128  * <li>USE_PROXY flag for whether a http proxy is to be used</li>
129  * <li>PROXY_SERVER the proxy</li>
130  * <li>PROXY_PORT</li>
131  * <li>NOQUESTIONNAIRES true to prevent jalview from checking the questionnaire
132  * service</li>
133  * <li>QUESTIONNAIRE last questionnaire:responder id string from questionnaire
134  * service</li>
135  * <li>USAGESTATS (false - user prompted) Enable google analytics tracker for
136  * collecting usage statistics</li>
137  * <li>SHOW_OVERVIEW boolean for overview window display</li>
138  * <li>ANTI_ALIAS boolean for smooth fonts</li>
139  * <li>RIGHT_ALIGN_IDS boolean</li>
140  * <li>AUTO_CALC_CONSENSUS boolean for automatic recalculation of consensus</li>
141  * <li>PAD_GAPS boolean</li>
142  * <li>ID_ITALICS boolean</li>
143  * <li>SHOW_JV_SUFFIX</li>
144  * <li>WRAP_ALIGNMENT</li>
145  * <li>EPS_RENDERING (Prompt each time|Lineart|Text) default for EPS rendering
146  * style check</li>
147  * <li>SORT_ALIGNMENT (No sort|Id|Pairwise Identity)</li>
148  * <li>SEQUENCE_LINKS list of name|URL pairs for opening a url with
149  * $SEQUENCE_ID$</li>
150  * <li>STORED_LINKS list of name|url pairs which user has entered but are not
151  * currently used
152  * <li>DEFAULT_LINK name of single url to be used when user double clicks a
153  * sequence id (must be in SEQUENCE_LINKS or STORED_LINKS)
154  * <li>GROUP_LINKS list of name|URL[|&lt;separator&gt;] tuples - see
155  * jalview.utils.GroupURLLink for more info</li>
156  * <li>DEFAULT_BROWSER for unix</li>
157  * <li>SHOW_MEMUSAGE boolean show memory usage and warning indicator on desktop
158  * (false)</li>
159  * <li>VERSION_CHECK (true) check for the latest release version from
160  * www.jalview.org (or the alias given by the www.jalview.org property)</li>
161  * <li>SHOW_NPFEATS_TOOLTIP (true) show non-positional features in the Sequence
162  * ID tooltip</li>
163  * <li>SHOW_DBREFS_TOOLTIP (true) show Database Cross References in the Sequence
164  * ID tooltip</li>
165  * <li>SHOW_UNCONSERVED (false) only render unconserved residues - conserved
166  * displayed as '.'</li>
167  * <li>SORT_BY_TREE (false) sort the current alignment view according to the
168  * order of a newly displayed tree</li>
169  * <li>DBFETCH_USEPICR (false) use PICR to recover valid DB references from
170  * sequence ID strings before attempting retrieval from any datasource</li>
171  * <li>SHOW_GROUP_CONSENSUS (false) Show consensus annotation for groups in the
172  * alignment.</li>
173  * <li>SHOW_GROUP_CONSERVATION (false) Show conservation annotation for groups
174  * in the alignment.</li>
175  * <li>SHOW_CONSENSUS_HISTOGRAM (false) Show consensus annotation row's
176  * histogram.</li>
177  * <li>SHOW_CONSENSUS_LOGO (false) Show consensus annotation row's sequence
178  * logo.</li>
179  * <li>NORMALISE_CONSENSUS_LOGO (false) Show consensus annotation row's sequence
180  * logo normalised to row height rather than histogram height.</li>
181  * <li>FOLLOW_SELECTIONS (true) Controls whether a new alignment view should
182  * respond to selections made in other alignments containing the same sequences.
183  * </li>
184  * <li>JWS2HOSTURLS comma-separated list of URLs to try for JABAWS services</li>
185  * <li>SHOW_WSDISCOVERY_ERRORS (true) Controls if the web service URL discovery
186  * warning dialog box is displayed.</li>
187  * <li>ANNOTATIONCOLOUR_MIN (orange) Shade used for minimum value of annotation
188  * when shading by annotation</li>
189  * <li>ANNOTATIONCOLOUR_MAX (red) Shade used for maximum value of annotation
190  * when shading by annotation</li>
191  * <li>www.jalview.org (https://www.jalview.org) a property enabling all HTTP
192  * requests to be redirected to a mirror of https://www.jalview.org</li>
193  * <li>FIGURE_AUTOIDWIDTH (false) Expand the left hand column of an exported
194  * alignment figure to accommodate even the longest sequence ID or annotation
195  * label.</li>
196  * <li>FIGURE_FIXEDIDWIDTH Specifies the width to use for the left-hand column
197  * when exporting an alignment as a figure (setting FIGURE_AUTOIDWIDTH to true
198  * will override this).</li>
199  * <li>STRUCT_FROM_PDB (false) derive secondary structure annotation from PDB
200  * record</li>
201  * <li>USE_RNAVIEW (false) use RNAViewer to derive secondary structure</li>
202  * <li>ADD_SS_ANN (false) add secondary structure annotation to alignment
203  * display</li>
204  * <li>ADD_TEMPFACT_ANN (false) add Temperature Factor annotation to alignment
205  * display</li>
206  * <li>STRUCTURE_DISPLAY choose from JMOL (default) or CHIMERA for 3D structure
207  * display</li>
208  * <li>CHIMERA_PATH specify full path to Chimera program (if non-standard)</li>
209  * <li>ID_ORG_HOSTURL location of jalview service providing identifiers.org urls
210  * </li>
211  * 
212  * </ul>
213  * Deprecated settings:
214  * <ul>
215  * *
216  * <li>DISCOVERY_START - Boolean - controls if discovery services are queried on
217  * startup (JWS1 services only)</li>
218  * <li>DISCOVERY_URLS - comma separated list of Discovery Service endpoints.
219  * (JWS1 services only)</li>
220  * <li>SHOW_JWS1_SERVICES (true) enable or disable the original Jalview 2
221  * services in the desktop GUI</li>
222  * <li>ENABLE_RSBS_EDITOR (false for 2.7 release) enable or disable RSBS editing
223  * panel in web service preferences</li>
224  * </ul>
225  * 
226  * @author $author$
227  * @version $Revision$
228  */
229 public class Cache
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 = System
250           .getProperty("user.home") + File.separatorChar
251           + ".sifts_downloads" + File.separatorChar;
252
253   private final static String DEFAULT_CACHE_THRESHOLD_IN_DAYS = "2";
254
255   private final static String DEFAULT_FAIL_SAFE_PID_THRESHOLD = "30";
256
257   /**
258    * Identifiers.org download settings
259    */
260   private static final String ID_ORG_FILE = System.getProperty("user.home")
261           + File.separatorChar + ".identifiers.org.ids.json";
262
263   /**
264    * Allowed values are PDB or mmCIF
265    */
266   private final static String PDB_DOWNLOAD_FORMAT = PDBEntry.Type.MMCIF
267           .toString();
268
269   private final static String DEFAULT_PDB_FILE_PARSER = StructureImportSettings.StructureParser.JMOL_PARSER
270           .toString();
271
272   /*
273    * a date formatter using a fixed (rather than the user's) locale; 
274    * this ensures that date properties can be written and re-read successfully
275    * even if the user changes their locale setting
276    */
277   private static final DateFormat date_format = SimpleDateFormat
278           .getDateTimeInstance(SimpleDateFormat.MEDIUM,
279                   SimpleDateFormat.MEDIUM, Locale.UK);
280
281   /**
282    * Initialises the Jalview Application Log
283    */
284   public static Logger log;
285
286   // save the proxy properties set at startup
287   public final static String[] startupProxyProperties = {
288       System.getProperty("http.proxyHost"),
289       System.getProperty("http.proxyPort"),
290       System.getProperty("https.proxyHost"),
291       System.getProperty("https.proxyPort"),
292       System.getProperty("http.proxyUser"),
293       System.getProperty("http.proxyPassword"),
294       System.getProperty("https.proxyUser"),
295       System.getProperty("https.proxyPassword"),
296       System.getProperty("http.nonProxyHosts") };
297
298   public final static String PROXYTYPE_NONE = "none";
299
300   // "false" and "true" for backward compatibility
301   public final static String PROXYTYPE_SYSTEM = "false";
302
303   public final static String PROXYTYPE_CUSTOM = "true";
304
305   // in-memory only storage of proxy password, safer to use char array
306   public static char[] proxyAuthPassword = null;
307
308   /** Jalview Properties */
309   public static Properties applicationProperties = new Properties()
310   {
311     // override results in properties output in alphabetical order
312     @Override
313     public synchronized Enumeration<Object> keys()
314     {
315       return Collections.enumeration(new TreeSet<>(super.keySet()));
316     }
317   };
318
319   /* build Properties (not all saved to .jalview_properties) */
320   public static Properties buildProperties = new Properties();
321
322   /** Default file is ~/.jalview_properties */
323   static String propertiesFile;
324
325   private static boolean propsAreReadOnly = Platform.isJS();
326
327   private final static String JS_PROPERTY_PREFIX = "jalview_";
328
329   public static void initLogger()
330   {
331     if (log != null)
332     {
333       return;
334     }
335     try
336     {
337       // configure the root logger to stderr
338       ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory.newConfigurationBuilder();
339       AppenderComponentBuilder consoleApp = builder.newAppender("stderr", "Console");
340       builder.add(consoleApp);
341       Configurator.initialize(builder.build());
342       // log output
343       Logger laxis = Logger.getLogger("org.apache.axis");
344       jalview.bin.Cache.log = Logger.getLogger("jalview.bin.Jalview");
345
346       laxis.setLevel(Level.toLevel(
347               Cache.getDefault("logs.Axis.Level", Level.INFO.toString())));
348       // lcastor = Logger.getLogger("org.exolab.castor.xml.Marshaller");
349       // lcastor.setLevel(Level.toLevel(Cache.getDefault("logs.Castor.Level",
350       // Level.INFO.toString())));
351       // we shouldn't need to do this
352       org.apache.log4j.Logger.getRootLogger()
353               .setLevel(org.apache.log4j.Level.INFO);
354
355       jalview.bin.Cache.log.setLevel(Level.toLevel(Cache
356               .getDefault("logs.Jalview.level", Level.INFO.toString())));
357       // laxis.addAppender(ap);
358       // lcastor.addAppender(ap);
359       // jalview.bin.Cache.log.addAppender(ap);
360       // Tell the user that debug is enabled
361       jalview.bin.Cache.log.debug(ChannelProperties.getProperty("app_name")
362               + " Debugging Output Follows.");
363     } catch (Exception ex)
364     {
365       System.err.println("Problems initializing the log4j system\n");
366       ex.printStackTrace(System.err);
367     }
368   }
369
370   /**
371    * Loads properties from the given properties file. Any existing properties are
372    * first cleared.
373    */
374   public static void loadProperties(String propsFile)
375   {
376     propertiesFile = propsFile;
377     String releasePropertiesFile = null;
378     boolean defaultProperties = false;
379     if (propsFile == null && !propsAreReadOnly)
380     {
381       String channelPrefsFilename = ChannelProperties
382               .getProperty("preferences.filename");
383       String releasePrefsFilename = ".jalview_properties";
384       propertiesFile = System.getProperty("user.home") + File.separatorChar
385               + channelPrefsFilename;
386       releasePropertiesFile = System.getProperty("user.home")
387               + File.separatorChar + releasePrefsFilename;
388       defaultProperties = true;
389     }
390     else
391     {
392       // don't corrupt the file we've been given.
393       propsAreReadOnly = true;
394     }
395
396     if (propertiesFile == null)
397     { // BH 2019
398       Platform.readInfoProperties(JS_PROPERTY_PREFIX,
399               applicationProperties);
400     }
401     else
402     {
403       try
404       {
405         InputStream fis;
406         try
407         {
408           // props file provided as URL
409           fis = new URL(propertiesFile).openStream();
410           System.out.println(
411                   "Loading jalview properties from : " + propertiesFile);
412           System.out.println(
413                   "Disabling Jalview writing to user's local properties file.");
414           propsAreReadOnly = true;
415         } catch (Exception ex)
416         {
417           fis = null;
418         }
419         if (fis == null)
420         {
421           String readPropertiesFile = propertiesFile;
422           // if we're using the usual properties file and the channel properties
423           // file doesn't exist, read .jalview_properties
424           // (but we'll still save to the channel properties file).
425           if (defaultProperties && (!new File(propertiesFile).exists())
426                   && (new File(releasePropertiesFile).exists()))
427           {
428             readPropertiesFile = releasePropertiesFile;
429           }
430           fis = new FileInputStream(readPropertiesFile);
431         }
432         applicationProperties.clear();
433         applicationProperties.load(fis);
434
435         // remove any old build properties
436
437         deleteBuildProperties();
438         fis.close();
439       } catch (Exception ex)
440       {
441         System.out.println("Error reading properties file: " + ex);
442       }
443     }
444
445     /* TO BE REPLACED WITH PROXY_TYPE SETTINGS 
446     if (getDefault("USE_PROXY", false))
447     {
448       String proxyServer = getDefault("PROXY_SERVER", ""),
449               proxyPort = getDefault("PROXY_PORT", "8080");
450     }
451     */
452
453     // PROXY TYPE settings (now three options "none", "false", "true", but using
454     // backward compatible strings)
455     String proxyType = getDefault("USE_PROXY", PROXYTYPE_SYSTEM);
456     // default to upgrading old settings
457     switch (proxyType)
458     {
459     case PROXYTYPE_NONE:
460       clearProxyProperties();
461       break;
462     case PROXYTYPE_SYSTEM: // use system settings
463       resetProxyProperties();
464       break;
465     case PROXYTYPE_CUSTOM: // use specified proxy settings
466       String httpHost = getDefault("PROXY_SERVER", "");
467       String httpPort = getDefault("PROXY_PORT", "8080");
468       String httpsHost = getDefault("PROXY_SERVER_HTTPS", httpHost);
469       String httpsPort = getDefault("PROXY_PORT_HTTPS", httpPort);
470       String httpUser = getDefault("PROXY_AUTH_USER", null);
471       // https.proxyUser and https.proxyPassword are not able to be
472       // independently set in Preferences yet (or http.nonProxyHosts)
473       String httpsUser = getDefault("PROXY_AUTH_USER_HTTPS", httpUser);
474       setProxyProperties(httpHost, httpPort, httpsHost, httpsPort, httpUser,
475               proxyAuthPassword, httpsUser, proxyAuthPassword, "localhost");
476       break;
477     default:
478       String message = "Incorrect PROXY_TYPE - should be 'none' (clear proxy properties), 'false' (system settings), 'true' (custom settings): "
479               + proxyType;
480       Cache.warn(message);
481     }
482
483     // LOAD THE AUTHORS FROM THE authors.props file
484     String authorDetails = resolveResourceURLFor("/authors.props");
485
486     try
487     {
488       if (authorDetails != null)
489       {
490         URL localJarFileURL = new URL(authorDetails);
491         InputStream in = localJarFileURL.openStream();
492         applicationProperties.load(in);
493         in.close();
494       }
495     } catch (Exception ex)
496     {
497       System.out.println("Error reading author details: " + ex);
498       authorDetails = null;
499     }
500     if (authorDetails == null)
501     {
502       applicationProperties.remove("AUTHORS");
503       applicationProperties.remove("AUTHORFNAMES");
504       applicationProperties.remove("YEAR");
505     }
506
507     loadBuildProperties(false);
508
509     SiftsSettings
510             .setMapWithSifts(Cache.getDefault("MAP_WITH_SIFTS", false));
511
512     SiftsSettings.setSiftDownloadDirectory(jalview.bin.Cache
513             .getDefault("sifts_download_dir", DEFAULT_SIFTS_DOWNLOAD_DIR));
514
515     SiftsSettings.setFailSafePIDThreshold(
516             jalview.bin.Cache.getDefault("sifts_fail_safe_pid_threshold",
517                     DEFAULT_FAIL_SAFE_PID_THRESHOLD));
518
519     SiftsSettings.setCacheThresholdInDays(
520             jalview.bin.Cache.getDefault("sifts_cache_threshold_in_days",
521                     DEFAULT_CACHE_THRESHOLD_IN_DAYS));
522
523     IdOrgSettings.setUrl(getDefault("ID_ORG_HOSTURL",
524             "https://www.jalview.org/services/identifiers"));
525     IdOrgSettings.setDownloadLocation(ID_ORG_FILE);
526
527     StructureImportSettings.setDefaultStructureFileFormat(jalview.bin.Cache
528             .getDefault("PDB_DOWNLOAD_FORMAT", PDB_DOWNLOAD_FORMAT));
529     StructureImportSettings
530             .setDefaultPDBFileParser(DEFAULT_PDB_FILE_PARSER);
531     // StructureImportSettings
532     // .setDefaultPDBFileParser(jalview.bin.Cache.getDefault(
533     // "DEFAULT_PDB_FILE_PARSER", DEFAULT_PDB_FILE_PARSER));
534
535     String jnlpVersion = System.getProperty("jalview.version");
536
537     // jnlpVersion will be null if a latest version check for the channel needs
538     // to be done
539     // Dont do this check if running in headless mode
540
541     if (jnlpVersion == null && getDefault("VERSION_CHECK", true)
542             && (System.getProperty("java.awt.headless") == null || System
543                     .getProperty("java.awt.headless").equals("false")))
544     {
545
546       class VersionChecker extends Thread
547       {
548
549         @Override
550         public void run()
551         {
552           String remoteBuildPropertiesUrl = Cache
553                   .getAppbaseBuildProperties();
554
555           String orgtimeout = System
556                   .getProperty("sun.net.client.defaultConnectTimeout");
557           if (orgtimeout == null)
558           {
559             orgtimeout = "30";
560             System.out.println("# INFO: Setting default net timeout to "
561                     + orgtimeout + " seconds.");
562           }
563           String remoteVersion = null;
564           try
565           {
566             System.setProperty("sun.net.client.defaultConnectTimeout",
567                     "5000");
568             java.net.URL url = new java.net.URL(remoteBuildPropertiesUrl);
569
570             BufferedReader in = new BufferedReader(
571                     new InputStreamReader(url.openStream()));
572
573             Properties remoteBuildProperties = new Properties();
574             remoteBuildProperties.load(in);
575             remoteVersion = remoteBuildProperties.getProperty("VERSION");
576           } catch (Exception ex)
577           {
578             System.out
579                     .println("Non-fatal exception when checking version at "
580                             + remoteBuildPropertiesUrl + ":");
581             System.out.println(ex);
582             remoteVersion = getProperty("VERSION");
583           }
584           System.setProperty("sun.net.client.defaultConnectTimeout",
585                   orgtimeout);
586
587           setProperty("LATEST_VERSION", remoteVersion);
588         }
589       }
590
591       VersionChecker vc = new VersionChecker();
592       vc.start();
593     }
594     else
595     {
596       if (jnlpVersion != null)
597       {
598         setProperty("LATEST_VERSION", jnlpVersion);
599       }
600       else
601       {
602         applicationProperties.remove("LATEST_VERSION");
603       }
604     }
605
606     // LOAD USERDEFINED COLOURS
607     Cache.initUserColourSchemes(getProperty("USER_DEFINED_COLOURS"));
608     jalview.io.PIRFile.useModellerOutput = Cache.getDefault("PIR_MODELLER",
609             false);
610   }
611
612   /**
613    * construct a resource URL for the given absolute resource pathname
614    * 
615    * @param resourcePath
616    * @return
617    */
618   private static String resolveResourceURLFor(String resourcePath)
619   {
620     String url = null;
621     if (Platform.isJS() || !Cache.class.getProtectionDomain()
622             .getCodeSource().getLocation().toString().endsWith(".jar"))
623     {
624       try
625       {
626         url = Cache.class.getResource(resourcePath).toString();
627       } catch (Exception ex)
628       {
629         System.err.println("Failed to resolve resource " + resourcePath
630                 + ": " + ex.getMessage());
631       }
632     }
633     else
634     {
635       url = "jar:".concat(Cache.class.getProtectionDomain().getCodeSource()
636               .getLocation().toString().concat("!" + resourcePath));
637     }
638     return url;
639   }
640
641   public static void loadBuildProperties(boolean reportVersion)
642   {
643     String codeInstallation = getProperty("INSTALLATION");
644     boolean printVersion = codeInstallation == null;
645
646     /*
647      * read build properties - from the Jalview jar for a Java distribution,
648      * or from codebase file in test or JalviewJS context
649      */
650     try
651     {
652       String buildDetails = resolveResourceURLFor("/.build_properties");
653       URL localJarFileURL = new URL(buildDetails);
654       InputStream in = localJarFileURL.openStream();
655       buildProperties.load(in);
656       in.close();
657       if (buildProperties.getProperty("BUILD_DATE", null) != null)
658       {
659         applicationProperties.put("BUILD_DATE",
660                 buildProperties.getProperty("BUILD_DATE"));
661       }
662       if (buildProperties.getProperty("INSTALLATION", null) != null)
663       {
664         applicationProperties.put("INSTALLATION",
665                 buildProperties.getProperty("INSTALLATION"));
666       }
667       if (buildProperties.getProperty("VERSION", null) != null)
668       {
669         applicationProperties.put("VERSION",
670                 buildProperties.getProperty("VERSION"));
671       }
672     } catch (Exception ex)
673     {
674       System.out.println("Error reading build details: " + ex);
675       applicationProperties.remove("VERSION");
676     }
677     String codeVersion = getProperty("VERSION");
678     codeInstallation = getProperty("INSTALLATION");
679
680     if (codeVersion == null)
681     {
682       // THIS SHOULD ONLY BE THE CASE WHEN TESTING!!
683       codeVersion = "Test";
684       codeInstallation = "";
685     }
686     else
687     {
688       codeInstallation = " (" + codeInstallation + ")";
689     }
690     setProperty("VERSION", codeVersion);
691     new BuildDetails(codeVersion, null, codeInstallation);
692     if (printVersion && reportVersion)
693     {
694       System.out.println(ChannelProperties.getProperty("app_name")
695               + " Version: " + codeVersion + codeInstallation);
696     }
697   }
698
699   private static void deleteBuildProperties()
700   {
701     applicationProperties.remove("LATEST_VERSION");
702     applicationProperties.remove("VERSION");
703     applicationProperties.remove("AUTHORS");
704     applicationProperties.remove("AUTHORFNAMES");
705     applicationProperties.remove("YEAR");
706     applicationProperties.remove("BUILD_DATE");
707     applicationProperties.remove("INSTALLATION");
708   }
709
710   /**
711    * Gets Jalview application property of given key. Returns null if key not found
712    * 
713    * @param key
714    *              Name of property
715    * 
716    * @return Property value
717    */
718   public static String getProperty(String key)
719   {
720     String prop = applicationProperties.getProperty(key);
721     if (prop == null && Platform.isJS())
722     {
723       prop = applicationProperties.getProperty(Platform.getUniqueAppletID()
724               + "_" + JS_PROPERTY_PREFIX + key);
725     }
726     return prop;
727   }
728
729   /**
730    * These methods are used when checking if the saved preference is different to
731    * the default setting
732    */
733
734   public static boolean getDefault(String property, boolean def)
735   {
736     String string = getProperty(property);
737     if (string != null)
738     {
739       def = Boolean.valueOf(string).booleanValue();
740     }
741
742     return def;
743   }
744
745   public static int getDefault(String property, int def)
746   {
747     String string = getProperty(property);
748     if (string != null)
749     {
750       try
751       {
752         def = Integer.parseInt(string);
753       } catch (NumberFormatException e)
754       {
755         System.out.println("Error parsing int property '" + property
756                 + "' with value '" + string + "'");
757       }
758     }
759
760     return def;
761   }
762
763   /**
764    * Answers the value of the given property, or the supplied default value if the
765    * property is not set
766    */
767   public static String getDefault(String property, String def)
768   {
769     String value = getProperty(property);
770     return value == null ? def : value;
771   }
772
773   /**
774    * Stores property in the file "HOME_DIR/.jalview_properties"
775    * 
776    * @param key
777    *              Name of object
778    * @param obj
779    *              String value of property
780    * 
781    * @return previous value of property (or null)
782    */
783   public static Object setProperty(String key, String obj)
784   {
785     Object oldValue = null;
786     try
787     {
788       oldValue = applicationProperties.setProperty(key, obj);
789       if (propertiesFile != null && !propsAreReadOnly)
790       {
791         FileOutputStream out = new FileOutputStream(propertiesFile);
792         applicationProperties.store(out, "---JalviewX Properties File---");
793         out.close();
794       }
795     } catch (Exception ex)
796     {
797       System.out.println(
798               "Error setting property: " + key + " " + obj + "\n" + ex);
799     }
800     return oldValue;
801   }
802
803   /**
804    * remove the specified property from the jalview properties file
805    * 
806    * @param string
807    */
808   public static void removeProperty(String string)
809   {
810     applicationProperties.remove(string);
811     saveProperties();
812   }
813
814   /**
815    * save the properties to the jalview properties path
816    */
817   public static void saveProperties()
818   {
819     if (!propsAreReadOnly)
820     {
821       try
822       {
823         FileOutputStream out = new FileOutputStream(propertiesFile);
824         applicationProperties.store(out, "---JalviewX Properties File---");
825         out.close();
826       } catch (Exception ex)
827       {
828         System.out.println("Error saving properties: " + ex);
829       }
830     }
831   }
832
833   /**
834    * internal vamsas class discovery state
835    */
836   private static int vamsasJarsArePresent = -1;
837
838   /**
839    * Searches for vamsas client classes on class path.
840    * 
841    * @return true if vamsas client is present on classpath
842    */
843   public static boolean vamsasJarsPresent()
844   {
845     if (vamsasJarsArePresent == -1)
846     {
847       try
848       {
849         if (jalview.jbgui.GDesktop.class.getClassLoader()
850                 .loadClass("uk.ac.vamsas.client.VorbaId") != null)
851         {
852           jalview.bin.Cache.log.debug(
853                   "Found Vamsas Classes (uk.ac..vamsas.client.VorbaId can be loaded)");
854           vamsasJarsArePresent = 1;
855           Logger lvclient = Logger.getLogger("uk.ac.vamsas");
856           lvclient.setLevel(Level.toLevel(Cache
857                   .getDefault("logs.Vamsas.Level", Level.INFO.toString())));
858
859           lvclient.addAppender(log.getAppender("JalviewLogger"));
860           // Tell the user that debug is enabled
861           lvclient.debug(ChannelProperties.getProperty("app_name")
862                   + " Vamsas Client Debugging Output Follows.");
863         }
864       } catch (Exception e)
865       {
866         vamsasJarsArePresent = 0;
867         jalview.bin.Cache.log.debug("Vamsas Classes are not present");
868       }
869     }
870     return (vamsasJarsArePresent > 0);
871   }
872
873   /**
874    * internal vamsas class discovery state
875    */
876   private static int groovyJarsArePresent = -1;
877
878   /**
879    * Searches for vamsas client classes on class path.
880    * 
881    * @return true if vamsas client is present on classpath
882    */
883   public static boolean groovyJarsPresent()
884   {
885     if (groovyJarsArePresent == -1)
886     {
887       try
888       {
889         if (Cache.class.getClassLoader()
890                 .loadClass("groovy.lang.GroovyObject") != null)
891         {
892           jalview.bin.Cache.log.debug(
893                   "Found Groovy (groovy.lang.GroovyObject can be loaded)");
894           groovyJarsArePresent = 1;
895           Logger lgclient = Logger.getLogger("groovy");
896           lgclient.setLevel(Level.toLevel(Cache
897                   .getDefault("logs.Groovy.Level", Level.INFO.toString())));
898
899           lgclient.addAppender(log.getAppender("JalviewLogger"));
900           // Tell the user that debug is enabled
901           lgclient.debug(ChannelProperties.getProperty("app_name")
902                   + " Groovy Client Debugging Output Follows.");
903         }
904       } catch (Error e)
905       {
906         groovyJarsArePresent = 0;
907         jalview.bin.Cache.log.debug("Groovy Classes are not present", e);
908       } catch (Exception e)
909       {
910         groovyJarsArePresent = 0;
911         jalview.bin.Cache.log.debug("Groovy Classes are not present");
912       }
913     }
914     return (groovyJarsArePresent > 0);
915   }
916
917   /**
918    * GA tracker object - actually JGoogleAnalyticsTracker null if tracking not
919    * enabled.
920    */
921   protected static Object tracker = null;
922
923   protected static Class trackerfocus = null;
924
925   protected static Class jgoogleanalyticstracker = null;
926
927   /**
928    * Initialise the google tracker if it is not done already.
929    */
930   public static void initGoogleTracker()
931   {
932     if (tracker == null)
933     {
934       if (jgoogleanalyticstracker == null)
935       {
936         // try to get the tracker class
937         try
938         {
939           jgoogleanalyticstracker = Cache.class.getClassLoader().loadClass(
940                   "com.boxysystems.jgoogleanalytics.JGoogleAnalyticsTracker");
941           trackerfocus = Cache.class.getClassLoader()
942                   .loadClass("com.boxysystems.jgoogleanalytics.FocusPoint");
943         } catch (Exception e)
944         {
945           log.debug(
946                   "com.boxysystems.jgoogleanalytics package is not present - tracking not enabled.");
947           tracker = null;
948           jgoogleanalyticstracker = null;
949           trackerfocus = null;
950           return;
951         }
952       }
953       // now initialise tracker
954       Exception re = null, ex = null;
955       Error err = null;
956       String vrs = "No Version Accessible";
957       try
958       {
959         // Google analytics tracking code for Library Finder
960         tracker = jgoogleanalyticstracker
961                 .getConstructor(new Class[]
962                 { String.class, String.class, String.class })
963                 .newInstance(new Object[]
964                 { ChannelProperties.getProperty("app_name") + " Desktop",
965                     (vrs = jalview.bin.Cache.getProperty("VERSION") + "_"
966                             + jalview.bin.Cache.getDefault("BUILD_DATE",
967                                     "unknown")),
968                     "UA-9060947-1" });
969         jgoogleanalyticstracker
970                 .getMethod("trackAsynchronously", new Class[]
971                 { trackerfocus })
972                 .invoke(tracker, new Object[]
973                 { trackerfocus.getConstructor(new Class[] { String.class })
974                         .newInstance(new Object[]
975                         { "Application Started." }) });
976       } catch (RuntimeException e)
977       {
978         re = e;
979       } catch (Exception e)
980       {
981         ex = e;
982       } catch (Error e)
983       {
984         err = e;
985       }
986       if (re != null || ex != null || err != null)
987       {
988         if (log != null)
989         {
990           if (re != null)
991           {
992             log.debug("Caught runtime exception in googletracker init:",
993                     re);
994           }
995           if (ex != null)
996           {
997             log.warn(
998                     "Failed to initialise GoogleTracker for Jalview Desktop with version "
999                             + vrs,
1000                     ex);
1001           }
1002           if (err != null)
1003           {
1004             log.error(
1005                     "Whilst initing GoogleTracker for Jalview Desktop version "
1006                             + vrs,
1007                     err);
1008           }
1009         }
1010         else
1011         {
1012           if (re != null)
1013           {
1014             System.err.println(
1015                     "Debug: Caught runtime exception in googletracker init:"
1016                             + vrs);
1017             re.printStackTrace();
1018           }
1019           if (ex != null)
1020           {
1021             System.err.println(
1022                     "Warning:  Failed to initialise GoogleTracker for Jalview Desktop with version "
1023                             + vrs);
1024             ex.printStackTrace();
1025           }
1026
1027           if (err != null)
1028           {
1029             System.err.println(
1030                     "ERROR: Whilst initing GoogleTracker for Jalview Desktop version "
1031                             + vrs);
1032             err.printStackTrace();
1033           }
1034         }
1035       }
1036       else
1037       {
1038         log.debug("Successfully initialised tracker.");
1039       }
1040     }
1041   }
1042
1043   /**
1044    * get the user's default colour if available
1045    * 
1046    * @param property
1047    * @param defcolour
1048    * @return
1049    */
1050   public static Color getDefaultColour(String property, Color defcolour)
1051   {
1052     String colprop = getProperty(property);
1053     if (colprop == null)
1054     {
1055       return defcolour;
1056     }
1057     Color col = ColorUtils.parseColourString(colprop);
1058     if (col == null)
1059     {
1060       log.warn("Couldn't parse '" + colprop + "' as a colour for "
1061               + property);
1062     }
1063     return (col == null) ? defcolour : col;
1064   }
1065
1066   /**
1067    * store a colour as a Jalview user default property
1068    * 
1069    * @param property
1070    * @param colour
1071    */
1072   public static void setColourProperty(String property, Color colour)
1073   {
1074     setProperty(property, jalview.util.Format.getHexString(colour));
1075   }
1076
1077   /**
1078    * Stores a formatted date in a jalview property, using a fixed locale.
1079    * 
1080    * @param propertyName
1081    * @param date
1082    * @return the formatted date string
1083    */
1084   public static String setDateProperty(String propertyName, Date date)
1085   {
1086     String formatted = date_format.format(date);
1087     setProperty(propertyName, formatted);
1088     return formatted;
1089   }
1090
1091   /**
1092    * Reads a date stored in a Jalview property, parses it (using a fixed locale
1093    * format) and returns as a Date, or null if parsing fails
1094    * 
1095    * @param propertyName
1096    * @return
1097    * 
1098    */
1099   public static Date getDateProperty(String propertyName)
1100   {
1101     String val = getProperty(propertyName);
1102     if (val != null)
1103     {
1104       try
1105       {
1106         return date_format.parse(val);
1107       } catch (Exception ex)
1108       {
1109         System.err.println("Invalid or corrupt date in property '"
1110                 + propertyName + "' : value was '" + val + "'");
1111       }
1112     }
1113     return null;
1114   }
1115
1116   /**
1117    * get and parse a property as an integer. send any parsing problems to
1118    * System.err
1119    * 
1120    * @param property
1121    * @return null or Integer
1122    */
1123   public static Integer getIntegerProperty(String property)
1124   {
1125     String val = getProperty(property);
1126     if (val != null && (val = val.trim()).length() > 0)
1127     {
1128       try
1129       {
1130         return Integer.valueOf(val);
1131       } catch (NumberFormatException x)
1132       {
1133         System.err.println("Invalid integer in property '" + property
1134                 + "' (value was '" + val + "')");
1135       }
1136     }
1137     return null;
1138   }
1139
1140   /**
1141    * Set the specified value, or remove it if null or empty. Does not save the
1142    * properties file.
1143    * 
1144    * @param propName
1145    * @param value
1146    */
1147   public static void setOrRemove(String propName, String value)
1148   {
1149     if (propName == null)
1150     {
1151       return;
1152     }
1153     if (value == null || value.trim().length() < 1)
1154     {
1155       Cache.applicationProperties.remove(propName);
1156     }
1157     else
1158     {
1159       Cache.applicationProperties.setProperty(propName, value);
1160     }
1161   }
1162
1163   /**
1164    * Loads in user colour schemes from files.
1165    * 
1166    * @param files
1167    *                a '|'-delimited list of file paths
1168    */
1169   public static void initUserColourSchemes(String files)
1170   {
1171     if (files == null || files.length() == 0)
1172     {
1173       return;
1174     }
1175
1176     // In case colours can't be loaded, we'll remove them
1177     // from the default list here.
1178     StringBuffer coloursFound = new StringBuffer();
1179     StringTokenizer st = new StringTokenizer(files, "|");
1180     while (st.hasMoreElements())
1181     {
1182       String file = st.nextToken();
1183       try
1184       {
1185         UserColourScheme ucs = ColourSchemeLoader.loadColourScheme(file);
1186         if (ucs != null)
1187         {
1188           if (coloursFound.length() > 0)
1189           {
1190             coloursFound.append("|");
1191           }
1192           coloursFound.append(file);
1193           ColourSchemes.getInstance().registerColourScheme(ucs);
1194         }
1195       } catch (Exception ex)
1196       {
1197         System.out.println("Error loading User ColourFile\n" + ex);
1198       }
1199     }
1200     if (!files.equals(coloursFound.toString()))
1201     {
1202       if (coloursFound.toString().length() > 1)
1203       {
1204         setProperty(UserDefinedColours.USER_DEFINED_COLOURS,
1205                 coloursFound.toString());
1206       }
1207       else
1208       {
1209         applicationProperties
1210                 .remove(UserDefinedColours.USER_DEFINED_COLOURS);
1211       }
1212     }
1213   }
1214
1215   /**
1216    * Initial logging information helper for various versions output
1217    * 
1218    * @param prefix
1219    * @param value
1220    * @param defaultValue
1221    */
1222   private static void appendIfNotNull(StringBuilder sb, String prefix,
1223           String value, String suffix, String defaultValue)
1224   {
1225     if (value == null && defaultValue == null)
1226     {
1227       return;
1228     }
1229     String line = prefix + (value != null ? value : defaultValue) + suffix;
1230     sb.append(line);
1231   }
1232
1233   /**
1234    * 
1235    * @return Jalview version, build details and JVM platform version for console
1236    */
1237   public static String getVersionDetailsForConsole()
1238   {
1239     StringBuilder sb = new StringBuilder();
1240     sb.append(ChannelProperties.getProperty("app_name"))
1241             .append(" Version: ");
1242     sb.append(jalview.bin.Cache.getDefault("VERSION", "TEST"));
1243     sb.append("\n");
1244     sb.append(ChannelProperties.getProperty("app_name"))
1245             .append(" Installation: ");
1246     sb.append(jalview.bin.Cache.getDefault("INSTALLATION", "unknown"));
1247     sb.append("\n");
1248     sb.append("Build Date: ");
1249     sb.append(jalview.bin.Cache.getDefault("BUILD_DATE", "unknown"));
1250     sb.append("\n");
1251     sb.append("Java version: ");
1252     sb.append(System.getProperty("java.version"));
1253     sb.append("\n");
1254     sb.append(System.getProperty("os.arch"));
1255     sb.append(" ");
1256     sb.append(System.getProperty("os.name"));
1257     sb.append(" ");
1258     sb.append(System.getProperty("os.version"));
1259     sb.append("\n");
1260     appendIfNotNull(sb, "Install4j version: ",
1261             System.getProperty("sys.install4jVersion"), "\n", null);
1262     appendIfNotNull(sb, "Install4j template version: ",
1263             System.getProperty("installer_template_version"), "\n", null);
1264     appendIfNotNull(sb, "Launcher version: ",
1265             System.getProperty("launcher_version"), "\n", null);
1266     LookAndFeel laf = UIManager.getLookAndFeel();
1267     String lafName = laf == null ? "Not obtained" : laf.getName();
1268     String lafClass = laf == null ? "unknown" : laf.getClass().getName();
1269     sb.append("LookAndFeel: ");
1270     sb.append(lafName);
1271     sb.append(" (");
1272     sb.append(lafClass);
1273     sb.append(")\n");
1274     // Not displayed in release version ( determined by possible version number
1275     // regex 9[9.]*9[.-_a9]* )
1276     if (Pattern.matches("^\\d[\\d\\.]*\\d[\\.\\-\\w]*$",
1277             jalview.bin.Cache.getDefault("VERSION", "TEST")))
1278     {
1279       appendIfNotNull(sb, "Getdown appdir: ",
1280               System.getProperty("getdownappdir"), "\n", null);
1281       appendIfNotNull(sb, "Getdown appbase: ",
1282               System.getProperty("getdownappbase"), "\n", null);
1283       appendIfNotNull(sb, "Java home: ", System.getProperty("java.home"),
1284               "\n", "unknown");
1285     }
1286     return sb.toString();
1287   }
1288
1289   /**
1290    * 
1291    * @return build details as reported in splashscreen
1292    */
1293   public static String getBuildDetailsForSplash()
1294   {
1295     // consider returning more human friendly info
1296     // eg 'built from Source' or update channel
1297     return jalview.bin.Cache.getDefault("INSTALLATION", "unknown");
1298   }
1299
1300   public static String getStackTraceString(Throwable t)
1301   {
1302     StringWriter sw = new StringWriter();
1303     PrintWriter pw = new PrintWriter(sw);
1304     t.printStackTrace(pw);
1305     return sw.toString();
1306   }
1307
1308   // proxy properties methods
1309   public static void clearProxyProperties()
1310   {
1311     setProxyProperties(null, null, null, null, null, null, null, null,
1312             null);
1313   }
1314
1315   public static void resetProxyProperties()
1316   {
1317     setProxyProperties(startupProxyProperties[0], startupProxyProperties[1],
1318             startupProxyProperties[2], startupProxyProperties[3],
1319             startupProxyProperties[4],
1320             startupProxyProperties[5] == null ? null
1321                     : startupProxyProperties[5].toCharArray(),
1322             startupProxyProperties[6],
1323             startupProxyProperties[7] == null ? null
1324                     : startupProxyProperties[7].toCharArray(),
1325             startupProxyProperties[8]);
1326     StringBuilder sb = new StringBuilder();
1327     sb.append("Setting proxy properties to: http.proxyHost=")
1328             .append(startupProxyProperties[0]).append(", http.proxyPort=")
1329             .append(startupProxyProperties[1])
1330             .append(startupProxyProperties[4] != null
1331                     && !startupProxyProperties[4].isEmpty()
1332                             ? " [" + startupProxyProperties[4] + "]"
1333                             : "")
1334             .append(", https.proxyHost=").append(startupProxyProperties[2])
1335             .append(", https.proxyPort=").append(startupProxyProperties[3])
1336             .append(startupProxyProperties[6] != null
1337                     && !startupProxyProperties[6].isEmpty()
1338                             ? " [" + startupProxyProperties[6] + "]"
1339                             : "");
1340
1341     Cache.debug(sb.toString());
1342   }
1343
1344   public static void setProxyPropertiesFromPreferences()
1345   {
1346     setProxyPropertiesFromPreferences(Cache.PROXYTYPE_SYSTEM);
1347   }
1348
1349   public static void setProxyPropertiesFromPreferences(
1350           String previousProxyType)
1351   {
1352     String proxyType = Cache.getDefault("USE_PROXY",
1353             Cache.PROXYTYPE_SYSTEM);
1354     if (previousProxyType != null
1355             && !proxyType.equals(Cache.PROXYTYPE_CUSTOM) // always apply
1356                                                          // customProxy
1357             && proxyType.equals(previousProxyType))
1358     {
1359       // no change
1360       return;
1361     }
1362     switch (proxyType)
1363     {
1364     case Cache.PROXYTYPE_NONE:
1365       if (!previousProxyType.equals(proxyType))
1366       {
1367         Cache.log.info("Setting no proxy settings");
1368         Cache.setProxyProperties(null, null, null, null, null, null, null,
1369                 null, null);
1370       }
1371       break;
1372     case Cache.PROXYTYPE_CUSTOM:
1373       // always re-set a custom proxy -- it might have changed, particularly
1374       // password
1375       Cache.log.info("Setting custom proxy settings");
1376       boolean proxyAuthSet = Cache.getDefault("PROXY_AUTH", false);
1377       Cache.setProxyProperties(Cache.getDefault("PROXY_SERVER", null),
1378               Cache.getDefault("PROXY_PORT", null),
1379               Cache.getDefault("PROXY_SERVER_HTTPS", null),
1380               Cache.getDefault("PROXY_PORT_HTTPS", null),
1381               proxyAuthSet ? Cache.getDefault("PROXY_AUTH_USERNAME", "")
1382                       : null,
1383               proxyAuthSet ? Cache.proxyAuthPassword : null,
1384               proxyAuthSet ? Cache.getDefault("PROXY_AUTH_USERNAME", "")
1385                       : null,
1386               proxyAuthSet ? Cache.proxyAuthPassword : null, "localhost");
1387       break;
1388     default: // system proxy settings by default
1389       Cache.log.info("Setting system proxy settings");
1390       Cache.resetProxyProperties();
1391     }
1392   }
1393
1394   public static void setProxyProperties(String httpHost, String httpPort,
1395           String httpsHost, String httpsPort, String httpUser,
1396           char[] httpPassword, String httpsUser, char[] httpsPassword,
1397           String nonProxyHosts)
1398   {
1399     setOrClearSystemProperty("http.proxyHost", httpHost);
1400     setOrClearSystemProperty("http.proxyPort", httpPort);
1401     setOrClearSystemProperty("https.proxyHost", httpsHost);
1402     setOrClearSystemProperty("https.proxyPort", httpsPort);
1403     setOrClearSystemProperty("http.proxyUser", httpUser);
1404     setOrClearSystemProperty("https.proxyUser", httpsUser);
1405     // note: passwords for http.proxyPassword and https.proxyPassword are sent
1406     // via the Authenticator, properties do not need to be set
1407
1408     // are we using a custom proxy (password prompt might be required)?
1409     boolean customProxySet = getDefault("USE_PROXY", PROXYTYPE_SYSTEM)
1410             .equals(PROXYTYPE_CUSTOM);
1411
1412     /*
1413      * A bug in Java means the AuthCache does not get reset, so once it has working credentials,
1414      * it never asks for more, so changing the Authenticator has no effect (as getPasswordAuthentication()
1415      * is not re-called).
1416      * This could lead to password leak to a hostile proxy server, so I'm putting in a hack to clear
1417      * the AuthCache.
1418      * see https://www.generacodice.com/en/articolo/154918/Reset-the-Authenticator-credentials
1419      * ...
1420      * Turns out this is only accessible in Java 8, and not in Java 9 onwards, so commenting out
1421      */
1422     /*
1423     try
1424     {
1425       sun.net.www.protocol.http.AuthCacheValue
1426               .setAuthCache(new sun.net.www.protocol.http.AuthCacheImpl());
1427     } catch (Throwable t)
1428     {
1429       Cache.error(t.getMessage());
1430       Cache.debug(getStackTraceString(t));
1431     }
1432     */
1433
1434     if (httpUser != null || httpsUser != null)
1435     {
1436       try
1437       {
1438         char[] displayHttpPw = new char[httpPassword == null ? 0
1439                 : httpPassword.length];
1440         Arrays.fill(displayHttpPw, '*');
1441         Cache.debug("CACHE Proxy: setting new Authenticator with httpUser='"
1442                 + httpUser + "' httpPassword='" + displayHttpPw + "'");
1443         if (!Platform.isJS())
1444         /* *
1445          * java.net.Authenticator not implemented in SwingJS yet
1446          * 
1447          * @j2sIgnore
1448          * 
1449          */
1450         {
1451           Authenticator.setDefault(new Authenticator()
1452           {
1453             @Override
1454             protected PasswordAuthentication getPasswordAuthentication()
1455             {
1456               if (getRequestorType() == RequestorType.PROXY)
1457               {
1458                 String protocol = getRequestingProtocol();
1459                 boolean needProxyPasswordSet = false;
1460                 if (customProxySet &&
1461                 // we have a username but no password for the scheme being
1462                 // requested
1463                 (protocol.equalsIgnoreCase("http")
1464                         && (httpUser != null && httpUser.length() > 0
1465                                 && (httpPassword == null
1466                                         || httpPassword.length == 0)))
1467                         || (protocol.equalsIgnoreCase("https")
1468                                 && (httpsUser != null
1469                                         && httpsUser.length() > 0
1470                                         && (httpsPassword == null
1471                                                 || httpsPassword.length == 0))))
1472                 {
1473                   // open Preferences -> Connections
1474                   String message = MessageManager
1475                           .getString("label.proxy_password_required");
1476                   Preferences.openPreferences(
1477                           Preferences.TabRef.CONNECTIONS_TAB, message);
1478                   Preferences.getInstance()
1479                           .proxyAuthPasswordCheckHighlight(true, true);
1480                 }
1481                 else
1482                 {
1483                   try
1484                   {
1485                     if (protocol.equalsIgnoreCase("http")
1486                             && getRequestingHost()
1487                                     .equalsIgnoreCase(httpHost)
1488                             && getRequestingPort() == Integer
1489                                     .valueOf(httpPort))
1490                     {
1491                       Cache.debug(
1492                               "AUTHENTICATOR returning PasswordAuthentication(\""
1493                                       + httpUser + "\", '"
1494                                       + new String(displayHttpPw) + "')");
1495                       return new PasswordAuthentication(httpUser,
1496                               httpPassword);
1497                     }
1498                     if (protocol.equalsIgnoreCase("https")
1499                             && getRequestingHost()
1500                                     .equalsIgnoreCase(httpsHost)
1501                             && getRequestingPort() == Integer
1502                                     .valueOf(httpsPort))
1503                     {
1504                       char[] displayHttpsPw = new char[httpPassword.length];
1505                       Arrays.fill(displayHttpsPw, '*');
1506                       Cache.debug(
1507                               "AUTHENTICATOR returning PasswordAuthentication(\""
1508                                       + httpsUser + "\", '" + displayHttpsPw
1509                                       + "'");
1510                       return new PasswordAuthentication(httpsUser,
1511                               httpsPassword);
1512                     }
1513                   } catch (NumberFormatException e)
1514                   {
1515                     Cache.error("Problem with proxy port values [http:"
1516                             + httpPort + ", https:" + httpsPort + "]");
1517                   }
1518                   Cache.debug(
1519                           "AUTHENTICATOR after trying to get PasswordAuthentication");
1520                 }
1521               }
1522               // non proxy request
1523               Cache.debug("AUTHENTICATOR returning null");
1524               return null;
1525             }
1526           });
1527         } // end of j2sIgnore for java.net.Authenticator
1528
1529         // required to re-enable basic authentication (should be okay for a
1530         // local proxy)
1531         Cache.debug(
1532                 "AUTHENTICATOR setting property 'jdk.http.auth.tunneling.disabledSchemes' to \"\"");
1533         System.setProperty("jdk.http.auth.tunneling.disabledSchemes", "");
1534       } catch (SecurityException e)
1535       {
1536         Cache.error("Could not set default Authenticator");
1537         Cache.debug(getStackTraceString(e));
1538       }
1539     }
1540     else
1541     {
1542       // reset the Authenticator to protect http.proxyUser and
1543       // http.proxyPassword Just In Case
1544       /* as noted above, due to bug in java this doesn't work if the sun.net.www.protocol.http.AuthCache
1545        * has working credentials. No workaround for Java 11.
1546        */
1547       if (!Platform.isJS())
1548       /* *
1549        * java.net.Authenticator not implemented in SwingJS yet
1550        * 
1551        * @j2sIgnore
1552        * 
1553        */
1554       {
1555         Cache.debug("AUTHENTICATOR setting default Authenticator to null");
1556         Authenticator.setDefault(null);
1557       }
1558     }
1559
1560     // nonProxyHosts not currently configurable in Preferences
1561     Cache.debug("AUTHENTICATOR setting property 'http.nonProxyHosts' to \""
1562             + nonProxyHosts + "\"");
1563     setOrClearSystemProperty("http.nonProxyHosts", nonProxyHosts);
1564   }
1565
1566   public static void setOrClearSystemProperty(String key, char[] value)
1567   {
1568     setOrClearSystemProperty(key,
1569             (value == null) ? null : new String(value));
1570   }
1571
1572   public static void setOrClearSystemProperty(String key, String value)
1573   {
1574     if (key == null)
1575     {
1576       return;
1577     }
1578     if (value == null)
1579     {
1580       System.clearProperty(key);
1581     }
1582     else
1583     {
1584       System.setProperty(key, value);
1585     }
1586   }
1587
1588   public final static int TRACE = 10;
1589
1590   public final static int DEBUG = 20;
1591
1592   public final static int INFO = 30;
1593
1594   public final static int WARN = 40;
1595
1596   public final static int ERROR = 50;
1597
1598   public static boolean println(int level, String message)
1599   {
1600     if (Cache.log == null)
1601     {
1602       if (level >= WARN)
1603         System.err.println(message);
1604       else if (level >= INFO)
1605         System.out.println(message);
1606       // not printing debug or trace messages
1607       return false;
1608     }
1609     if (level >= ERROR)
1610     {
1611       Cache.log.error(message);
1612     }
1613     else if (level >= WARN)
1614     {
1615       Cache.log.warn(message);
1616     }
1617     else if (level >= INFO)
1618     {
1619       Cache.log.info(message);
1620     }
1621     else if (level >= DEBUG)
1622     {
1623       Cache.log.debug(message);
1624     }
1625     else
1626     {
1627       Cache.log.trace(message);
1628     }
1629     return true;
1630   }
1631
1632   public static void trace(String message)
1633   {
1634     println(TRACE, message);
1635   }
1636
1637   public static void debug(String message)
1638   {
1639     println(DEBUG, message);
1640   }
1641
1642   public static void info(String message)
1643   {
1644     println(INFO, message);
1645   }
1646
1647   public static void warn(String message)
1648   {
1649     println(WARN, message);
1650   }
1651
1652   public static void error(String message)
1653   {
1654     println(ERROR, message);
1655   }
1656
1657   /**
1658    * Getdown appbase methods
1659    */
1660
1661   private static final String releaseAppbase;
1662
1663   private static String getdownAppbase;
1664
1665   private static String getdownDistDir;
1666
1667   static
1668   {
1669     if (!Platform.isJS())
1670     {
1671       Float specversion = Float
1672               .parseFloat(System.getProperty("java.specification.version"));
1673       releaseAppbase = (specversion < 9)
1674               ? "https://www.jalview.org/getdown/release/1.8"
1675               : "https://www.jalview.org/getdown/release/11";
1676     }
1677     else
1678     {
1679       // this value currenly made up, can be changed to URL that will be
1680       // "https://www.jalview.org/jalview-js/swingjs/j2s/build_properties"
1681       releaseAppbase = "https://www.jalview.org/jalview-js";
1682       getdownAppbase = releaseAppbase;
1683       getdownDistDir = "/swingjs/j2s";
1684     }
1685   }
1686
1687   // look for properties (passed in by getdown) otherwise default to release
1688   private static void setGetdownAppbase()
1689   {
1690     if (getdownAppbase != null)
1691     {
1692       return;
1693     }
1694     String appbase = System.getProperty("getdownappbase");
1695     String distDir = System.getProperty("getdowndistdir");
1696     if (appbase == null)
1697     {
1698       appbase = buildProperties.getProperty("GETDOWNAPPBASE");
1699       distDir = buildProperties.getProperty("GETDOWNAPPDISTDIR");
1700     }
1701     if (appbase == null)
1702     {
1703       appbase = releaseAppbase;
1704       distDir = "release";
1705     }
1706     if (appbase.endsWith("/"))
1707     {
1708       appbase = appbase.substring(0, appbase.length() - 1);
1709     }
1710     if (distDir == null)
1711     {
1712       distDir = appbase.equals(releaseAppbase) ? "release" : "alt";
1713     }
1714     getdownAppbase = appbase;
1715     getdownDistDir = distDir;
1716   }
1717
1718   public static String getGetdownAppbase()
1719   {
1720     setGetdownAppbase();
1721     return getdownAppbase;
1722   }
1723
1724   public static String getAppbaseBuildProperties()
1725   {
1726     String appbase = getGetdownAppbase();
1727     return appbase + "/" + getdownDistDir + "/build_properties";
1728   }
1729 }