diff --git a/build.xml b/build.xml index 8ab7180..6771d29 100644 --- a/build.xml +++ b/build.xml @@ -57,13 +57,12 @@ info on where to find these. - + - - - @@ -160,7 +159,7 @@ info on where to find these. - + @@ -191,7 +190,7 @@ info on where to find these. - + @@ -213,7 +212,7 @@ info on where to find these. - + @@ -230,7 +229,7 @@ info on where to find these. - + @@ -252,7 +251,7 @@ info on where to find these. - + @@ -265,7 +264,7 @@ info on where to find these. - + @@ -292,7 +291,7 @@ info on where to find these. - + @@ -538,8 +537,7 @@ info on where to find these. - - + @@ -549,8 +547,7 @@ info on where to find these. - - + @@ -560,8 +557,7 @@ info on where to find these. - - + @@ -572,8 +568,7 @@ info on where to find these. location="${dist}/lib/DictionarySearchStandalone-${my.jar.suffix}.jar"/> - - + @@ -710,12 +705,19 @@ info on where to find these. - + description="INTERNAL TASK: Copies the license documents and the default properties file (hence the et cetera) to the bin directory. Usually not called directly."> + + + + + + + diff --git a/source/options.txt b/source/options.txt new file mode 100644 index 0000000..1165a5a --- /dev/null +++ b/source/options.txt @@ -0,0 +1,81 @@ +# This is the built-in preferences file for Jskad, Savant, +# QuillDriver, and the Translation Tool. +# +# Please do not edit this file. Instead, create a system-wide or +# user-specific properties file, defining just the options that you +# want to differ from the defaults given here. See the documentation +# for how to do that. Or, if you are a developer or if we haven't +# written any documentation yet *smile*, look at +# 'http://thdltools.sourceforge.net/api/org/thdl/util/ThdlOptions.html'. +# +# +# This file must remain readable by the +# java.util.Properties.load(InputStream) mechanism, so don't just edit +# it willy-nilly -- instead, read the comments for each item. + + + +############################################################################ +######################### User Preferences ########################### +############################################################################ + +# Sets the Tibetan keyboard that Jskad uses on startup. +# Here are the acceptable values: +# +# 0: Extended Wylie Keyboard +# 1: TCC Keyboard #1 +# 2: TCC Keyboard #2 +# 3: Sambhota Keymap One +thdl.default.tibetan.keyboard = 0 + +# Set this to the default font face for Roman input. No error is +# given if this font is not found. +thdl.default.roman.font.face = Serif + +# Set this to the default font size for Roman input: +thdl.default.roman.font.size = 14 + +# Set this to the default font size for Tibetan input: +thdl.default.tibetan.font.size = 36 + +# The extension that Savant's input files, the titles, must have: +thdl.savant.file.extension = .savant + +# Set this to true only if you do not want Jskad to have a status bar. +thdl.Jskad.disable.status.bar = false + +# The message displayed in Jskad's status bar when you start it up +# with thdl.Jskad.disable.status.bar set to false: +thdl.Jskad.initial.status.message = Welcome to Jskad! + + +############################################################################ +####################### Developer Preferences ######################## +############################################################################ + +# Set this to true to disable the creation of log files. +thdl.disable.log.file = false + +# Set this to true if you want the debugging output, the .log files, +# to be placed in your system's temporary directory. You cannot set +# this to true AND set thdl.log.directory. +thdl.use.temp.file.directory.for.log = false + +# Set this to the directory in which you want the debugging output, +# the .log files, placed. You cannot set this to true AND set +# thdl.use.temp.file.directory.for.log to true. +thdl.log.directory = + +# Set this to true only if you are a developer and want to see what +# Jskad is "thinking" as you enter tibetan text. If +# thdl.Jskad.disable.status.bar is true, then this will have no +# effect. +thdl.Jskad.enable.tibetan.mode.status = false + +# Set this to true only if you are a developer and want exceptional +# behavior to rear its ugly head as soon as possible: +thdl.debug = false + +# Set this to true if you want Savant to treat all files as valid +# input. Otherwise, only .savant files are acceptable. +thdl.treat.all.files.as.dot.savant.files.regardless.of.extension = false diff --git a/source/org/thdl/savant/SavantFileView.java b/source/org/thdl/savant/SavantFileView.java index 3e41a9c..324e767 100644 --- a/source/org/thdl/savant/SavantFileView.java +++ b/source/org/thdl/savant/SavantFileView.java @@ -24,6 +24,7 @@ import javax.swing.*; import javax.swing.filechooser.*; import org.thdl.util.ThdlDebug; +import org.thdl.util.ThdlOptions; /** * The SavantFileView "sees through" a *.savant file and @@ -40,7 +41,10 @@ import org.thdl.util.ThdlDebug; public class SavantFileView extends FileView { /** When opening a file, this is the only extension Savant cares about. This is case-insensitive. */ - public final static String dotSavant = ".savant"; + public final static String getDotSavant() { + return ThdlOptions.getStringOption("thdl.savant.file.extension", + ".savant"); + } /** This loads *.savant files as properties files and returns an associated TITLE attribute. For any other type of @@ -56,8 +60,8 @@ public class SavantFileView extends FileView { unresponsive. In addition, you'll cause the floppy drive to spin up every time you refresh or cd in the file chooser. */ - if (!Boolean.getBoolean("THDL_TREAT_ALL_FILES_AS_DOT_SAVANT_FILES_REGARDLESS_OF_EXTENSION")) { /* FIXME */ - if (!f.getName().toLowerCase().endsWith(dotSavant)) + if (!ThdlOptions.getBooleanOption("thdl.treat.all.files.as.dot.savant.files.regardless.of.extension")) { /* FIXME */ + if (!f.getName().toLowerCase().endsWith(getDotSavant())) return null; } Properties p = new Properties(); diff --git a/source/org/thdl/savant/SavantShell.java b/source/org/thdl/savant/SavantShell.java index 6742078..9da80b4 100644 --- a/source/org/thdl/savant/SavantShell.java +++ b/source/org/thdl/savant/SavantShell.java @@ -97,7 +97,7 @@ public class SavantShell extends JFrame if (f.isDirectory()) { return true; } - return f.getName().toLowerCase().endsWith(SavantFileView.dotSavant); + return f.getName().toLowerCase().endsWith(SavantFileView.getDotSavant()); } //the description of this filter diff --git a/source/org/thdl/tib/input/DuffPane.java b/source/org/thdl/tib/input/DuffPane.java index caf9cce..1174371 100644 --- a/source/org/thdl/tib/input/DuffPane.java +++ b/source/org/thdl/tib/input/DuffPane.java @@ -33,6 +33,7 @@ import java.io.*; import javax.swing.text.rtf.*; import org.thdl.util.ThdlDebug; +import org.thdl.util.ThdlOptions; import org.thdl.util.StatusBar; /** @@ -284,6 +285,20 @@ public RTFEditorKit rtfEd = null; } */ + private static int defaultTibFontSize() { + // FIXME: at program exit, or when the user selects "Save + // preferences", or somehow, save the value the users chooses: + return ThdlOptions.getIntegerOption("thdl.default.tibetan.font.size", + 36); + } + + private static int defaultRomanFontSize() { + // FIXME: at program exit, or when the user selects "Save + // preferences", or somehow, save the value the users chooses: + return ThdlOptions.getIntegerOption("thdl.default.roman.font.size", + 14); + } + /** * This method sets up the editor, assigns fonts and point sizes, * sets the document, the caret, and adds key and focus listeners. @@ -298,15 +313,16 @@ public RTFEditorKit rtfEd = null; styleContext = new StyleContext(); doc = new TibetanDocument(styleContext); - doc.setTibetanFontSize(36); + doc.setTibetanFontSize(defaultTibFontSize()); setDocument(doc); Style defaultStyle = styleContext.getStyle(StyleContext.DEFAULT_STYLE); StyleConstants.setFontFamily(defaultStyle, "TibetanMachineWeb"); - StyleConstants.setFontSize(defaultStyle, 36); + StyleConstants.setFontSize(defaultStyle, defaultTibFontSize()); // FIXME make pref - romanFontFamily = "Serif"; - romanFontSize = 14; + romanFontFamily = ThdlOptions.getStringOption("thdl.default.roman.font.face", + "Serif"); // FIXME write out this preference. + romanFontSize = defaultRomanFontSize(); // FIXME make pref setRomanAttributeSet(romanFontFamily, romanFontSize); // newDocument(); @@ -403,12 +419,12 @@ public RTFEditorKit rtfEd = null; styleContext = new StyleContext(); doc = new TibetanDocument(styleContext); - doc.setTibetanFontSize(36); + doc.setTibetanFontSize(defaultTibFontSize()); setDocument(doc); Style defaultStyle = styleContext.getStyle(StyleContext.DEFAULT_STYLE); StyleConstants.setFontFamily(defaultStyle, "TibetanMachineWeb"); - StyleConstants.setFontSize(defaultStyle, 36); + StyleConstants.setFontSize(defaultStyle, defaultTibFontSize()); } /** @@ -1006,9 +1022,9 @@ public void paste(int offset) { e.consume(); initKeyboard(); if (isTibetan) - doc.appendDuff(caret.getDot(),"\n",TibetanMachineWeb.getAttributeSet(1)); + doc.appendDuff(caret.getDot(),"\n",TibetanMachineWeb.getAttributeSet(1)); // FIXME does this work on all platforms? else - append("\n", romanAttributeSet); + append("\n", romanAttributeSet); // FIXME does this work on all platforms? break; } } @@ -1455,10 +1471,10 @@ public void paste(int offset) { */ public void toTibetanMachineWeb(String wylie, int offset) { try { - StringTokenizer sTok = new StringTokenizer(wylie, "\n\t", true); + StringTokenizer sTok = new StringTokenizer(wylie, "\n\t", true); // FIXME does this work on all platforms? while (sTok.hasMoreTokens()) { String next = sTok.nextToken(); - if (next.equals("\n") || next.equals("\t")) { + if (next.equals("\n") || next.equals("\t")) { // FIXME does this work on all platforms? try { doc.insertString(offset, next, null); offset++; diff --git a/source/org/thdl/tib/input/Jskad.java b/source/org/thdl/tib/input/Jskad.java index cf88bf1..1e27e4a 100644 --- a/source/org/thdl/tib/input/Jskad.java +++ b/source/org/thdl/tib/input/Jskad.java @@ -33,6 +33,7 @@ import javax.swing.text.rtf.*; import org.thdl.tib.text.*; import org.thdl.util.ThdlDebug; +import org.thdl.util.ThdlOptions; import org.thdl.util.StatusBar; import org.thdl.util.ThdlActionListener; import org.thdl.util.RTFPane; @@ -80,7 +81,6 @@ public class Jskad extends JPanel implements DocumentListener { private final String keybd4HelpFile = "Sambhota_keymap_one.rtf"; - private String fontName = ""; private JComboBox fontFamilies, fontSizes; private JFileChooser fileChooser; private javax.swing.filechooser.FileFilter rtfFilter; @@ -90,7 +90,6 @@ public class Jskad extends JPanel implements DocumentListener { private static int numberOfTibsRTFOpen = 0; private static int x_size; private static int y_size; - private TibetanKeyboard sambhota1Keyboard = null, duff1Keyboard = null, duff2Keyboard = null; /** * The text editing window which this Jskad object embeds. @@ -115,10 +114,12 @@ public class Jskad extends JPanel implements DocumentListener { * is a JApplet then the File menu is omitted from the menu bar. */ public Jskad(final Object parent) { - if (Boolean.getBoolean("thdl.Jskad.disable.status.bar")) { + if (ThdlOptions.getBooleanOption("thdl.Jskad.disable.status.bar")) { statusBar = null; } else { - statusBar = new StatusBar("Welcome to Jskad!"); + statusBar + = new StatusBar(ThdlOptions.getStringOption("thdl.Jskad.initial.status.message", + "Welcome to Jskad!")); } parentObject = parent; numberOfTibsRTFOpen++; @@ -378,49 +379,35 @@ public class Jskad extends JPanel implements DocumentListener { toolBar.add(new JLabel("Keyboard:")); toolBar.addSeparator(); + /* Initialize dp before calling installKeyboard. */ + if (ThdlOptions.getBooleanOption(Jskad.enableKeypressStatusProp)) { + dp = new DuffPane(statusBar); + } else { + dp = new DuffPane(); + } + + String[] keyboard_options = {keybd1Description, keybd2Description, keybd3Description, keybd4Description}; final JComboBox keyboards = new JComboBox(keyboard_options); + int initialKeyboard + = ThdlOptions.getIntegerOption("thdl.default.tibetan.keyboard", 0); + try { + keyboards.setSelectedIndex(initialKeyboard); + } catch (IllegalArgumentException e) { + keyboards.setSelectedIndex(0); // good ol' Wylie + } + installKeyboard(initialKeyboard); keyboards.addActionListener(new ThdlActionListener() { public void theRealActionPerformed(ActionEvent e) { - switch (keyboards.getSelectedIndex()) { - case 0: //Extended Wylie - installKeyboard("wylie"); - break; - - case 1: //TCC 1 - if (duff1Keyboard == null) - duff1Keyboard = installKeyboard("tcc1"); - else - dp.registerKeyboard(duff1Keyboard); - break; - - case 2: //TCC 2 - if (duff2Keyboard == null) - duff2Keyboard = installKeyboard("tcc2"); - else - dp.registerKeyboard(duff2Keyboard); - break; - - case 3: //Sambhota - if (sambhota1Keyboard == null) - sambhota1Keyboard = installKeyboard("sambhota1"); - else - dp.registerKeyboard(sambhota1Keyboard); - break; - } + installKeyboard(keyboards.getSelectedIndex()); } }); toolBar.add(keyboards); toolBar.add(Box.createHorizontalGlue()); - if (Boolean.getBoolean(Jskad.enableKeypressStatusProp)) { - dp = new DuffPane(statusBar); - } else { - dp = new DuffPane(); - } JScrollPane sp = new JScrollPane(dp, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); dp.getDocument().addDocumentListener(this); @@ -786,7 +773,7 @@ public class Jskad extends JPanel implements DocumentListener { try { BufferedReader in = new BufferedReader(new FileReader(txt_fileChosen)); DuffPane dp2; - if (Boolean.getBoolean(Jskad.enableKeypressStatusProp)) { + if (ThdlOptions.getBooleanOption(Jskad.enableKeypressStatusProp)) { dp2 = new DuffPane(statusBar); } else { dp2 = new DuffPane(); @@ -812,7 +799,41 @@ public class Jskad extends JPanel implements DocumentListener { } } - private TibetanKeyboard installKeyboard(String name) { + private TibetanKeyboard sambhota1Keyboard = null, duff1Keyboard = null, duff2Keyboard = null; + private void installKeyboard(int keyboardIndex) { + switch (keyboardIndex) { + case 1: //TCC 1 + if (duff1Keyboard == null) + duff1Keyboard = installKeyboard("tcc1"); + else + dp.registerKeyboard(duff1Keyboard); + break; + + case 2: //TCC 2 + if (duff2Keyboard == null) + duff2Keyboard = installKeyboard("tcc2"); + else + dp.registerKeyboard(duff2Keyboard); + break; + + case 3: //Sambhota + if (sambhota1Keyboard == null) + sambhota1Keyboard = installKeyboard("sambhota1"); + else + dp.registerKeyboard(sambhota1Keyboard); + break; + default: //Extended Wylie + if (0 != keyboardIndex + && ThdlOptions.getBooleanOption("thdl.debug")) { + throw new Error("You set thdl.default.tibetan.keyboard to an illegal value: " + + keyboardIndex); + } + installKeyboard("wylie"); + break; + } + } + + private TibetanKeyboard installKeyboard(String name) { TibetanKeyboard tk = null; if (!name.equalsIgnoreCase("wylie")) { @@ -833,6 +854,7 @@ public class Jskad extends JPanel implements DocumentListener { } catch (TibetanKeyboard.InvalidKeyboardException ike) { System.out.println("invalid keyboard file or file not found"); + ThdlDebug.noteIffyCode(); return null; } } @@ -840,6 +862,7 @@ public class Jskad extends JPanel implements DocumentListener { return null; } + ThdlDebug.verify("dp != null", dp != null); dp.registerKeyboard(tk); return tk; } @@ -1074,22 +1097,27 @@ public class Jskad extends JPanel implements DocumentListener { * you discover a bug, please send us an email, making sure to include * the jskad.log file as an attachment. */ public static void main(String[] args) { - ThdlDebug.attemptToSetUpLogFile("jskad", ".log"); + try { + ThdlDebug.attemptToSetUpLogFile("jskad", ".log"); - try { - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - } - catch (Exception e) { - } + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } + catch (Exception e) { + } - JFrame f = new JFrame("Jskad"); - Dimension d = f.getToolkit().getScreenSize(); - x_size = d.width/4*3; - y_size = d.height/4*3; - f.setSize(x_size, y_size); - f.setLocation(d.width/8, d.height/8); - f.getContentPane().add(new Jskad(f)); - f.setVisible(true); + JFrame f = new JFrame("Jskad"); + Dimension d = f.getToolkit().getScreenSize(); + x_size = d.width/4*3; + y_size = d.height/4*3; + f.setSize(x_size, y_size); + f.setLocation(d.width/8, d.height/8); + f.getContentPane().add(new Jskad(f)); + f.setVisible(true); + } catch (ThdlLazyException e) { + System.err.println("Jskad has a BUG:"); + e.getRealException().printStackTrace(System.err); + } } } diff --git a/source/org/thdl/util/ThdlDebug.java b/source/org/thdl/util/ThdlDebug.java index e35c304..177dcbf 100644 --- a/source/org/thdl/util/ThdlDebug.java +++ b/source/org/thdl/util/ThdlDebug.java @@ -23,6 +23,7 @@ import java.io.FileOutputStream; import java.io.File; import org.thdl.util.TeeStream; +import org.thdl.util.ThdlOptions; /** * This uninstantiable class provides assertions and the like in a @@ -64,7 +65,7 @@ public class ThdlDebug { throw new ThdlLazyException(new Error(((msg == null) ? "THDL Tools sanity check: " : msg) - + "An assertion failed. This means that there is a bug in this software." + + "\nAn assertion failed. This means that there is a bug in this software." + contactMsg)); } } @@ -73,15 +74,15 @@ public class ThdlDebug { * out. For example, if you have to catch an IOException, but * you're fairly certain that it'll never be thrown, call this * function if it is indeed thrown. Developers can set the - * THDL_DIE_EAGERLY property to true (using 'java - * -DTHDL_DIE_ON_IFFY_CODE=true') in order to test the - * code's robustness. + * thdl.debug property to true (using 'java + * -Dthdl.debug=true' or the properties files) in order to + * test the code's robustness. * * Throws a THDL-specific exception so that you can catch these * specially in case you want to ignore them. * - * @throws ThdlLazyException if the THDL_DIE_ON_IFFY_CODE system - * property is set to "true" */ + * @throws ThdlLazyException if the thdl.debug option is set to + * "true" */ public static void noteIffyCode() throws ThdlLazyException { @@ -89,8 +90,8 @@ public class ThdlDebug { /* FIXME: find all calls to this function and rethink or shore up the calling code. */ - if (Boolean.getBoolean("THDL_DIE_ON_IFFY_CODE")) - throw new ThdlLazyException(new Error("You've reached some iffy code, some code that's not well thought-out. Because you invoked the Java runtime environment with the property THDL_DIE_ON_IFFY_CODE set to true (developers: use 'ant -Dthdl.die.on.iffy=false' to prevent this), the program is now aborting.")); + if (ThdlOptions.getBooleanOption("thdl.debug")) + throw new ThdlLazyException(new Error("You've reached some iffy code, some code that's not well thought-out. Because you invoked the Java runtime environment with the property thdl.debug set to true (developers: use 'ant -Dthdl.debug=false' to prevent this), or because you set the thdl.debug preference to true in one of the preferences files, the program is now aborting.")); } /** Exits the program with a message that the CLASSPATH is not set @@ -101,7 +102,7 @@ public class ThdlDebug { /* FIXME */ System.err.println("Note that Savant and QuillDriver CANNOT be invoked via the"); - System.err.println("'java -jar Savant-xyz.jar' option, because that silently ignores"); + System.err.println("'java -jar Savant-xyz.jar' option, because that silently ignores"); // FIXME yes you can when Java Web Start is up and running System.err.println("the CLASSPATH. This means that double-clicking them won't work"); System.err.println("either, because we don't set the JARs' manifest files to contain"); System.err.println("Class-path attributes. See installation instructions."); /* FIXME: we don't HAVE installation instructions, do we? */ @@ -109,7 +110,7 @@ public class ThdlDebug { System.err.println("Details: Missing class: " + ((error == null) ? "unknown!" : error.getMessage())); - if (Boolean.getBoolean("THDL_DEBUG")) { + if (ThdlOptions.getBooleanOption("thdl.debug")) { System.err.println("Details: Stack trace: " + ((error == null) ? "unknown!" : error.getMessage())); @@ -124,18 +125,22 @@ public class ThdlDebug { * path, because we may put this file into an arbitrary * directory. */ public static void attemptToSetUpLogFile(String prefix, String suffix) { + if (ThdlOptions.getBooleanOption("thdl.disable.log.file")) + return; + // Else: final String tempDirProp = "thdl.use.temp.file.directory.for.log"; final String logDirProp = "thdl.log.directory"; File logFile = null; - if (Boolean.getBoolean(tempDirProp)) { + if (ThdlOptions.getBooleanOption(tempDirProp)) { /* The log file won't be named 'jskad.log', it'll be named 'jskad-SAKFJDS3134.log', and they'll all just pile up, because we don't deleteOnExit. */ /* First, ensure that the user hasn't set conflicting options. */ try { - if (null != System.getProperty("thdl.log.directory")) + if (!("".equals(ThdlOptions.getStringOption("thdl.log.directory", + "")))) throw new Error("You cannot set the property " + tempDirProp + " and the property " + logDirProp @@ -159,13 +164,14 @@ public class ThdlDebug { thdl.log.directory, respect their choice. */ String logDir = null; try { - logDir = System.getProperty(logDirProp); + logDir = ThdlOptions.getStringOption(logDirProp, ""); } catch (Exception e) { /* SecurityExceptions, e.g., will trigger this. We leave logDir null. */ noteIffyCode(); } - if (null != logDir) { + if (!("".equals(ThdlOptions.getStringOption("thdl.log.directory", + "")))) { logFile = new File(logDir, prefix + suffix); } else { /* Create the log file in the current directory. For diff --git a/source/org/thdl/util/ThdlOptions.java b/source/org/thdl/util/ThdlOptions.java new file mode 100644 index 0000000..cf961a5 --- /dev/null +++ b/source/org/thdl/util/ThdlOptions.java @@ -0,0 +1,353 @@ +/* +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.IOException; +import java.io.FileNotFoundException; +import java.util.Properties; + +import org.thdl.util.ThdlLazyException; + +/** + * @author David Chandler + * + * 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.

*/ +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. */ + } + 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; + + /** 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.file", + // FIXME this default is + // system-dependent: + "C:\\thdl_uopt.txt", + 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 + { + Properties props = defaultProps; + String systemPropFileName = System.getProperty(pName, 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 or + // Unix/Linux box, this'll happen every time + // at present. (FIXME) + } + } + + if (fis != null) { + props = getPropertiesFromStream(fis, + suppressErrors, + defaultProps); + } + return props; + } + + + // FIXMEDOC + private 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); + } + } + } +} + + + +