/* The contents of this file are subject to the THDL Open Community License Version 1.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License on the THDL web site (http://www.thdl.org/). Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific terms governing rights and limitations under the License. The Initial Developer of this software is the Tibetan and Himalayan Digital Library (THDL). Portions created by the THDL are Copyright 2001 THDL. All Rights Reserved. Contributor(s): ______________________________________. */ package org.thdl.util; import java.io.InputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.File; import java.io.FileNotFoundException; import java.util.Properties; import org.thdl.util.ThdlLazyException; import org.thdl.util.OperatingSystemUtils; /** * Provides a clean interface to the multi-tiered system of user * preferences (also known as options). * *

The {@link java.util.Properties java.util.Properties} class * makes it easy for us to provide a hierarchical preferences (that * is, options) structure. One option a user might wish to set is the * font size for Tibetan, for example. The highest precedence goes to * a pref that the user set on the Java command line using something * like java -Dpref=value -jar jskad_or_whatever.jar. If * the user went to the trouble of doing this, we definitely want to * respect his or her wish. The next highest precedence goes to an * individual user's preferences file (a file readable and writable by * {@link java.util.Properties java.util.Properties}, but also * hand-editable), if one exists. Next is the system-wide preferences * file, if one exists. Finally, we fall back on the preferences file * shipped with the application inside the JAR.

* *

ThdlOptions is not instantiable. It contains only static * methods for answering questions about the user's preferences.

* *

There are three kinds of preferences: boolean-valued preferences * ("true" or "false"), integer-valued preferences, and string-valued * preferences. Boolean-valued preferences should, by convention, be * false by default. If you want to enable feature foo by default, * but give a preference for disabling it, then call the preference * "DisableFeatureFoo". If you want to disable feature foo by * default, call it "EnableFeatureFoo". This makes the users' lives * easier.

* *

ThdlOptions is a final class so that compilers can make this * code run efficiently.

* * @author David Chandler */ public final class ThdlOptions { /** * So that you're not tempted to instantiate this class, the * constructor is private: */ private ThdlOptions() { // don't instantiate this class. } /** * Returns the value of a boolean-valued option, or false if that * option is set nowhere in the hierarchy of properties files. * * @param optionName the option whose value you wish to retrieve * (note the naming conventions detailed in the class comment) */ public static boolean getBooleanOption(String optionName) { init(); // Look to the System first. String answer = getSystemValue(optionName); if (answer == null) { answer = userProperties.getProperty(optionName, "false"); } return answer.equalsIgnoreCase("true"); } // FIXMEDOC private static String getSystemValue(String optionName) { // Look to the System first. String answer = null; try { answer = System.getProperty(optionName); } catch (SecurityException e) { if (!suppressErrs()) throw e; } return answer; } /** * Returns the value of a string-valued option, or null if that * option is set nowhere in the hierarchy of properties files. * * @param optionName the option whose value you wish to retrieve * (note the naming conventions detailed in the class comment) */ public static String getStringOption(String optionName) { init(); // Look to the System first. String answer = getSystemValue(optionName); if (answer == null) { answer = userProperties.getProperty(optionName, null); } return answer; } /** * Returns the value of a string-valued option, or defaultValue if that * option is set nowhere in the hierarchy of properties files. * * @param optionName the option whose value you wish to retrieve * (note the naming conventions detailed in the class comment) * @param defaultValue the default value */ public static String getStringOption(String optionName, String defaultValue) { String x = getStringOption(optionName); if (null == x) return defaultValue; else return x; } /** * Returns the value of an integer-valued option, or defaultValue * if that option is set nowhere in the hierarchy of properties * files or is set to something that cannot be decoded to an * integer. * * @param optionName the option whose value you wish to retrieve * (note the naming conventions detailed in the class comment) * @param defaultValue the default value */ public static int getIntegerOption(String optionName, int defaultValue) { Integer x = getIntegerOption(optionName); if (null == x) return defaultValue; else return x.intValue(); } /** * Returns the value of an integer-valued option, or null if that * option is set nowhere in the hierarchy of properties files or * is set to something that cannot be decoded to an integer. * * @param optionName the option whose value you wish to retrieve * (note the naming conventions detailed in the class comment) */ public static Integer getIntegerOption(String optionName) { init(); // Look to the System first. String answer = getSystemValue(optionName); if (answer == null) { answer = userProperties.getProperty(optionName, null); } if (null == answer) { return null; } else { try { return Integer.decode(answer); } catch (NumberFormatException e) { if (getBooleanOption("thdl.debug")) throw new ThdlLazyException(e); else return null; } } } private static boolean suppressErrs() { return false; /* FIXME--make THIS configurable. It's not a simple thing, * though, since you can't use the usual prefences mechanism * because it helps to implement that mechanism. */ } private static void init() { try { initialize(ThdlOptions.class, "/options.txt", suppressErrs()); } catch (FileNotFoundException e) { if (!suppressErrs()) throw new ThdlLazyException(e); } } /** the properties object that is chained to the system-wide properties which is chained to the built-in properties which is chained to the Java System properties */ private static Properties userProperties = null; /** to avoid initializing twice */ private static boolean isInitialized = false; /** Call this when you're testing some code that uses the * preferences mechanism provided by this class, and you don't * want to use options.txt etc. with that code. You'll be * relying on the defaults encoded in the calls to * getBooleanOption etc. If you call this twice, it will wipe * out preferences stored programmatically on each call. */ public static void forTestingOnlyInitializeWithoutDefaultOptionsFile() { userProperties = new Properties(); // empty isInitialized = true; } /** Sets userProperties so that it represents the entire, chained preferences hierarchy. @param resourceHolder the class associated with the builtin defaults properties file resource @param resourceName the name of the builtin defaults properties file resource @param suppressErrors true if the show must go on, false if you want unchecked exceptions thrown when bad things happen @throws FileNotFoundException if !suppressErrors and if the user gave the location of the system-wide or user preferences files, but gave it incorrectly. */ private static void initialize(Class resourceHolder, String resourceName, boolean suppressErrors) throws FileNotFoundException { if (isInitialized) return; isInitialized = true; try { // Get the application's built-in, default properties. Properties defaultProperties = getPropertiesFromResource(resourceHolder, resourceName, suppressErrors, new Properties() /* empty */); // Get the system's properties, if the system administrator // has created any: Properties systemWideProperties = tryToGetPropsFromFile("thdl.system.wide.options.file", // FIXME this default is // system-dependent: "C:\\thdl_opt.txt", defaultProperties, suppressErrors); // Get the user's properties, if they've set any: userProperties = tryToGetPropsFromFile("thdl.user.options.directory", // DLC NOW FIXME: put in options.txt getUserPreferencesPath(), systemWideProperties, suppressErrors); } catch (SecurityException e) { if (suppressErrors) { if (userProperties == null) { userProperties = new Properties(); // empty } // else leave it as is. } else { throw new ThdlLazyException(e); } } } /** Returns a new, nonnull Properties object if (1) a preferences file is found at the location specified by the value of the System property pName, if it is set, or at defaultLoc, if pName is not set, and (2) if that file is successfully read in. Otherwise, this returns defaultProps. @param pName the name of the System property that overrides this application's default idea of where to look for the file @param defaultLoc the default preferences file name @param suppressErrors true iff you want to proceed without throwing exceptions whenever possible @throws FileNotFoundException if !suppressErrors and if the user gave the location of the system-wide or user preferences files, but gave it incorrectly. @throws SecurityException if playing with files or system properties is not OK */ private static Properties tryToGetPropsFromFile(String pName, String defaultLoc, Properties defaultProps, boolean suppressErrors) throws FileNotFoundException, SecurityException { String systemPropFileName = System.getProperty(pName, defaultLoc); /* The empty string means "use the default location". See * options.txt. */ if ("".equals(systemPropFileName)) systemPropFileName = defaultLoc; FileInputStream fis = null; try { fis = new FileInputStream(systemPropFileName); } catch (FileNotFoundException e) { if (null != System.getProperty(pName)) { // the user explicitly set this, but not // correctly. if (!suppressErrors) throw e; } else { // definitely suppress this. On a Mac, I think // this'll happen every time at present. (FIXME) } } Properties props = defaultProps; if (fis != null) { props = getPropertiesFromStream(fis, suppressErrors, defaultProps); } return props; } /** The resource named resourceName is find and read in using * resourceHolder for guidance. Default properties are provided * by defaults if it is non-null. If suppressErrors is true, no * exceptions will be thrown in normal operation. Otherwise, an * unchecked exception may be thrown upon error. */ public static Properties getPropertiesFromResource(Class resourceHolder, String resourceName, boolean suppressErrors, Properties defaults) { InputStream in = resourceHolder.getResourceAsStream(resourceName); return getPropertiesFromStream(in, suppressErrors, defaults); } // FIXMEDOC private static Properties getPropertiesFromStream(InputStream in, boolean suppressErrors, Properties defaults) throws NullPointerException { Properties options; if (defaults == null) options = new Properties(); // empty properties list else options = new Properties(defaults); try { // Load props from the resource: options.load(in); return options; } catch (Exception e) { // e is an IOException or a SecurityException or, if the // resource was not found, a NullPointerException. if (suppressErrors) { return options; } else { throw new ThdlLazyException(e); } } } /** Deletes the user's preferences file, a file whose path is the * value of {@link #getUserPreferencesPath()}. You must restart * the application before this will take effect. * * @throws IOException if an IO or security exception occurs * while deleting from disk. */ public static void clearUserPreferences() throws IOException { // Avoid saving the preferences to disk at program exit. Do // this first in case an exception is thrown during deletion. userProperties = null; File pf = new File(getUserPreferencesPath()); try { if (pf.exists() && !pf.delete()) { throw new IOException("Could not delete the preferences file " + pf.getAbsolutePath()); } } catch (SecurityException e) { throw new IOException("Could not delete the preferences file because a SecurityManager is in place and your security policy does not allow deleting " + pf.getAbsolutePath()); } } /** Saves the user's preferences to a file whose path is the value * of {@link #getUserPreferencesPath()}. You must call * setUserPreference(..) for this to be effective. * * @return true on success, false if no preferences exist to be * saved, such as after calling clearUserPreferences() * * @throws IOException if an IO exception occurs while writing to * the disk. */ public static boolean saveUserPreferences() throws IOException { if (null != userProperties) { userProperties.store(new FileOutputStream(getUserPreferencesPath()), " This file was automatically created by a THDL tool.\n" + "# You may edit this file, but it will be recreated,\n" + "# so your comments will be lost.\n" + "# \n" + "# To understand this file's contents, please see\n" + "# options.txt in the JAR file.\n" + "# \n" + "# Note that this is the user-specific preferences file.\n" + "# This tool also supports a system-specific preferences\n" + "# file, which the user-specific preferences override.\n" + "# \n" + "# Note also that you can set a JVM preference at run-time.\n" + "# Doing so will override both system- and user-specific\n" + "# preferences. On many systems, you do this like so:\n" + "# 'java -Dthdl.default.tibetan.font.size=36 -jar Jskad.jar'\n" + "# \n" + "# There is, unfortunately, no further documentation on the\n" + "# preferences mechanism at this time. Yell for it!\n" + "# \n" + "# Created at:"); // DLC FIXME: document the preferences mechanism. return true; } return false; } /** This returns the location of the user's preferences file. * This value may be overridden, by, you guessed it, a JVM, * built-in, or system-wide preference * thdl.user.options.directory */ public static String getUserPreferencesPath() { String defaultUserDir; switch (OperatingSystemUtils.getOSType()) { case OperatingSystemUtils.MAC: // where? DLC FIXME defaultUserDir = "/tmp"; break; case OperatingSystemUtils.WIN32: defaultUserDir = "C:\\"; break; default: //put linux etc. here defaultUserDir = "/tmp"; break; } String defaultLoc = System.getProperty("user.home", defaultUserDir); String systemsOverridingValue = System.getProperty("thdl.user.options.directory", defaultLoc); return (new File(systemsOverridingValue, "my_thdl_preferences.txt")).getPath(); } /** In order to save preferences, this class must know that the * user (explicitly or implicitly) has changed a preference, * either through selecting something in a ComboBox, going * through a Preferences GUI, or the like. Calling this method * indicates that the user has changed an integer-valued * preference pref to value. * @param pref the preference the user is setting * @param value the user's new preference */ public static void setUserPreference(String pref, int value) { if (userProperties == null) { userProperties = new Properties(); // empty } // else leave it as is. userProperties.setProperty(pref, String.valueOf(value)); } /** In order to save preferences, this class must know that the * user (explicitly or implicitly) has changed a preference, * either through selecting something in a ComboBox, going * through a Preferences GUI, or the like. Calling this method * indicates that the user has changed a boolean-valued * preference pref to value. * @param pref the preference the user is setting * @param value the user's new preference */ public static void setUserPreference(String pref, boolean value) { if (userProperties == null) { userProperties = new Properties(); // empty } // else leave it as is. userProperties.setProperty(pref, String.valueOf(value)); } /** In order to save preferences, this class must know that the * user (explicitly or implicitly) has changed a preference, * either through selecting something in a ComboBox, going * through a Preferences GUI, or the like. Calling this method * indicates that the user has changed a String-valued preference * pref to value. * @param pref the preference the user is setting * @param value the user's new preference */ public static void setUserPreference(String pref, String value) { if (userProperties == null) { userProperties = new Properties(); // empty } // else leave it as is. userProperties.setProperty(pref, value); } }