Added a flexible mechanism for persistent boolean-, integer-, and

string-valued preferences built atop java.util.Properties.

How it works: the jvm is asked first, and then the user's prefs file, if it exists,
then the system-wide prefs file, and then the built-in preferences.  Finally, for
robustness, a default may be optionally hard-coded in the source.

I made several things configurable, too:

the default Tibetan keyboard
the default font sizes and faces
whether you want developer-only features enabled
Savant's file extension (.savant)
etc.

The only known problems are the following:

The default location for the user's preferences file is windows-specific,
arbitrary, and not in the user documentation.  Likewise for the location of the
system-wide preferences file.  You can change them using 'java -D', though.

There is no "Save preferences" option yet, and closing the program does
not save preferences either.
This commit is contained in:
dchandler 2002-10-14 04:06:05 +00:00
parent b914309dba
commit 08e4e2fc57
8 changed files with 591 additions and 101 deletions

View File

@ -57,13 +57,12 @@ info on where to find these.
<property name="privatejavadocs" location="${docs}/private-javadocs"/>
<property name="j2ee.sdk.home" location="F:\Program Files\j2sdkee1.3.1\lib"/>
<!-- Set to false for less output when running the tools: -->
<!-- Set to true for more output when running the tools and if you
want the code to abort when it reaches poorly thought-out
control flow paths (i.e., iffy code).
-->
<property name="thdl.debug" value="true"/>
<!-- Set to true if you want the code to abort when it reaches
poorly thought-out control flow paths (i.e., iffy code). -->
<property name="thdl.die.on.iffy" value="true"/>
<!-- Set to the name of the dictionary used by the Translation Tool: -->
<property name="arch.dict" value="architecture_dictionary"/>
@ -160,7 +159,7 @@ info on where to find these.
<antcall target="copy-ini-files-to-bin-dir-for-jarring">
<param name="mybin" value="${jskadbin}"/>
</antcall>
<antcall target="copy-license-to-bin-dir-for-jarring">
<antcall target="copy-license-etc-to-bin-dir-for-jarring">
<param name="mybin" value="${jskadbin}"/>
</antcall>
<copy todir="${jskadbin}/org/thdl/tib/input">
@ -191,7 +190,7 @@ info on where to find these.
<antcall target="copy-ini-files-to-bin-dir-for-jarring">
<param name="mybin" value="${savantbin}"/>
</antcall>
<antcall target="copy-license-to-bin-dir-for-jarring">
<antcall target="copy-license-etc-to-bin-dir-for-jarring">
<param name="mybin" value="${savantbin}"/>
</antcall>
</target>
@ -213,7 +212,7 @@ info on where to find these.
<antcall target="copy-ini-files-to-bin-dir-for-jarring">
<param name="mybin" value="${ttstandalonebin}"/>
</antcall>
<antcall target="copy-license-to-bin-dir-for-jarring">
<antcall target="copy-license-etc-to-bin-dir-for-jarring">
<param name="mybin" value="${ttstandalonebin}"/>
</antcall>
</target>
@ -230,7 +229,7 @@ info on where to find these.
<param name="my.included.source.file"
value="org/thdl/tib/scanner/WindowScannerFilter.java"/>
</antcall>
<antcall target="copy-license-to-bin-dir-for-jarring">
<antcall target="copy-license-etc-to-bin-dir-for-jarring">
<param name="mybin" value="${tthandheldbin}"/>
</antcall>
</target>
@ -252,7 +251,7 @@ info on where to find these.
<antcall target="copy-ini-files-to-bin-dir-for-jarring">
<param name="mybin" value="${ttappletjwsbin}"/>
</antcall>
<antcall target="copy-license-to-bin-dir-for-jarring">
<antcall target="copy-license-etc-to-bin-dir-for-jarring">
<param name="mybin" value="${ttappletjwsbin}"/>
</antcall>
</target>
@ -265,7 +264,7 @@ info on where to find these.
<param name="my.included.source.file"
value="org/thdl/tib/scanner/ConsoleScannerFilter.java"/>
</antcall>
<antcall target="copy-license-to-bin-dir-for-jarring">
<antcall target="copy-license-etc-to-bin-dir-for-jarring">
<param name="mybin" value="${ttbin}"/>
</antcall>
</target>
@ -292,7 +291,7 @@ info on where to find these.
<antcall target="copy-ini-files-to-bin-dir-for-jarring">
<param name="mybin" value="${qdbin}"/>
</antcall>
<antcall target="copy-license-to-bin-dir-for-jarring">
<antcall target="copy-license-etc-to-bin-dir-for-jarring">
<param name="mybin" value="${qdbin}"/>
</antcall>
</target>
@ -538,8 +537,7 @@ info on where to find these.
<pathelement location="${dist}/lib/QuillDriver-${my.jar.suffix}.jar"/>
<path refid="entire.class.path"/>
</classpath>
<jvmarg value="-DTHDL_DEBUG=${thdl.debug}"/>
<jvmarg value="-DTHDL_DIE_ON_IFFY_CODE=${thdl.die.on.iffy}"/>
<jvmarg value="-Dthdl.debug=${thdl.debug}"/>
</java>
</target>
@ -549,8 +547,7 @@ info on where to find these.
<pathelement location="${dist}/lib/Savant-${my.jar.suffix}.jar"/>
<path refid="entire.class.path"/>
</classpath>
<jvmarg value="-DTHDL_DEBUG=${thdl.debug}"/>
<jvmarg value="-DTHDL_DIE_ON_IFFY_CODE=${thdl.die.on.iffy}"/>
<jvmarg value="-Dthdl.debug=${thdl.debug}"/>
</java>
</target>
@ -560,8 +557,7 @@ info on where to find these.
<pathelement location="${dist}/lib/Jskad-${my.jar.suffix}.jar"/>
<path refid="entire.class.path"/>
</classpath>
<jvmarg value="-DTHDL_DEBUG=${thdl.debug}"/>
<jvmarg value="-DTHDL_DIE_ON_IFFY_CODE=${thdl.die.on.iffy}"/>
<jvmarg value="-Dthdl.debug=${thdl.debug}"/>
</java>
</target>
@ -572,8 +568,7 @@ info on where to find these.
location="${dist}/lib/DictionarySearchStandalone-${my.jar.suffix}.jar"/>
<path refid="entire.class.path"/>
</classpath>
<jvmarg value="-DTHDL_DEBUG=${thdl.debug}"/>
<jvmarg value="-DTHDL_DIE_ON_IFFY_CODE=${thdl.die.on.iffy}"/>
<jvmarg value="-Dthdl.debug=${thdl.debug}"/>
<!-- For non-swing version: <arg value="-simple"/> -->
<arg value="${arch.dict}"/>
</java>
@ -710,12 +705,19 @@ info on where to find these.
</target>
<target name="copy-license-to-bin-dir-for-jarring"
<target name="copy-license-etc-to-bin-dir-for-jarring"
depends="init"
description="INTERNAL TASK: Copies the license documents to the bin directory. Usually not called directly.">
description="INTERNAL TASK: Copies the license documents and the default properties file (hence the et cetera) to the bin directory. Usually not called directly.">
<copy todir="${mybin}">
<fileset dir="${license}"/>
</copy>
<!-- The default properties file, shared by all our tools: -->
<copy todir="${mybin}">
<fileset dir="${source}">
<include name="options.txt"/>
</fileset>
</copy>
</target>

81
source/options.txt Normal file
View File

@ -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

View File

@ -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 <code>*.savant</code> 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 <code>*.savant</code> 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();

View File

@ -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

View File

@ -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++;

View File

@ -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);
}
}
}

View File

@ -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 <code>'java
* -DTHDL_DIE_ON_IFFY_CODE=true'</code>) in order to test the
* code's robustness.
* thdl.debug property to true (using <code>'java
* -Dthdl.debug=true'</code> 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

View File

@ -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).
*
* <p>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 <code>java -Dpref=value -jar jskad_or_whatever.jar</code>. 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.</p>
*
* <p>ThdlOptions is not instantiable. It contains only static
* methods for answering questions about the user's preferences.</p>
*
* <p>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.</p>
*
* <p>ThdlOptions is a final class so that compilers can make this
* code run efficiently.</p> */
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);
}
}
}
}