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