Properties.load(Reader)
.
A separate class, ConfigPropUtilities
,
can be used to read files produced by property editors when
editing is not needed.
The editor displays a window (whether a frame or dialog) containing a menubar, some buttons, and a table with two columns: one labeled Property and one labeled Value. The initial properties may have keys that cannot be edited, although their values can be edited. When this is the case, the background for this portion of the table will have a different color than the rest of the table. To insert a new row above a row, or to move or delete the row, the entire row must be selected. A table can also be configured to suppress some of the buttons located just above the table. Some keys in the table may consist of one or more dashes (minus signs). There are treated as spacers and are ignored when the properties are written or used outside of the editor.
Property values may be encrypted using GPG. The keys for these properties start with the substring "ebase64." and the values are stored in an encrypted form. GPG Agent should be configured so that decryption can be used conveniently. To use GPG Agent on Debian Linux systems, add the following lines to the .bashrc script:
GPG_TTY=$(tty) export GPG_TTY
The file format is actually a stylized version of a standard Java properties file. The first additional constraint is that the first line, should be a comment (hence the initial '#') of the form
where#(M.T MEDIATYPE)
MEDIATYPE
is the media type assigned to the
property file. This convention is used when ConfigPropertyEditor
reads or writes a configuration file. In addition each line will
be terminated with a carriage return followed by a line feed (the
convention used in most RFCs for text-based formats). The first
line must appear exactly as shown, with no leading or trailing
white space, with a single space before the media type, and no
spaces between the media type and the terminating closing
parenthesis.
This format must use UTF-8 as its character set. While Java
historically used ISO 8859-1 for a property file's character set,
the methods Properties.load(Reader)
and
Properties.store(Writer,String)
are used by this class,
with the readers and writers configured to use the UTF-8 character
set, and those methods, with UTF-8, are used by this class.
Keys are restricted to ones that consist of a sequence of tokens separated by periods, with each token starting with a letter followed by 0 or more letters, digits, and underscores. There are two special initial tokens:
- base64. This token indicates that the value is base-64 encoded.
- ebase64. This token indicates that the value is first base64 encoded and then encrypted using GPG or PGP. One use of this field is to store passwords (e.g., for a database). To encrypt, one will provide a list of recipients: when configuring a database, for instance, one can use this feature so that an administrator and a user can have access to the same password.
Values that are not encoded or encrypted allow variable substitutions. the expression
will be replaced with the value of the key. The order of the keys does not matter, but references must not be circular. The sequence$(KEY)
$$
will be replaced with $
. It may be
more convenient to use base-64 encoding, in which case variable
substitution will not occur. Leading and trailing whitespace is
preserved for keys whose first token is "base64" or "ebase64", but
not otherwise. With any "base64" or "ebase64" token and its
following delimited removed, each key must be unique.
To create an instance of this class for editing a configuration file for a specific application, a subclass has to be defined The constructor for such a subclass is expected to call the following methods (most are optional):
addIcon(java.awt.Image)
oraddIcon(java.lang.Class<?>,java.lang.String)
. These methods provide an icon to display when a configuration editor's window is iconified. Normally there are multiple icons, corresponding to different sizes required by a window manager.addReservedKeys(String...)
. There may be some number of distinguished properties that are nominally expected to be present. This method will define a group of such keys. When called multiple times, each group will be separated from the others by a series of dashes in a table's first column.addAltReservedKeys(String,String...)
. This method adds a set of reserved properties that will appear in a single row. The property names consist of a prefix, followed by a period, followed by a suffix. A combo box will allow one to choose the appropriate property. The values can use different renderers and editors.setupCompleted()
. This method must be called by the constructor, and indicates that all the reserved keys, and any default values associated with these, have been provided.setDefaultProperty(String,String)
. Some properties have default values, typically in cases where the defaults are likely to be the ones the user needs. This method should be used to define what these defaults are.addRE(String,TableCellRenderer,TableCellEditor)
. This method associates a table-cell renderer and editor with the final components of a key (components are separated by periods). One use is for configuring specialized renderers and editors for properties that provide colors.changedPropertyClears(java.lang.String,java.lang.String...)
This method indicates that when one property's value is changed, or the property is removed, other properties should have their values set to null. It is useful in cases where the change of one property indicates that the value of some other properties is almost certainly wrong. Generally these will reserved properties and should be listed in close proximity to each other.
freezeRows()
. This method prevents the user from adding, moving, or deleting rows. The user may, however, change a row's value or key (unless the row is a reserved row).setInitialExtraRows(int)
. This method sets the number of blank table entries that appear after any predefined keys.
- errorTitle(). This contains the title used in dialog boxes associated with error messages.
- configTitle(). This contains the title used in an ConfigPropertyEditor window (whether a frame or a dialog).
- mediaType(). This contains the application-specific media type for a properties file, and appears in the first line of files saved by this methods in this class.
- extensionFilterTitle(). This contains the title to use in a file-chooser dialog for the extension associated with an application's configuration file.
- extension. This contains the file-name extension for an application's configuration file.
The following statements provide some examples of frequently used
operations, assuming an instance named editor
has
been created.
- Loading from a file:
editor.loadFile(file);
- Loading using a dialog:
editor.showLoadDialog(component)
- Opening the editor:
editor.edit(null, ConfigPropertyEditor.Mode.MODAL, null, true);
- Getting decoded/decrypted properties:
Properties config = editor.getDecodedProperties();
ConfigPropertyEditor
is not a Swing or AWT
component, although it uses such components, and its public methods
do not have to be called on the AWT event dispatch thread. The
rationale is that one use case is for providing a dialog box for
configuring a program that otherwise runs as a command-line program
and exits when done.-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic enum
Mode to determine options for closing a property-editor window or dialog.static class
Event for changes to monitored properties belonging to aConfigPropertyEditor
.static interface
Listener for changes in ConfigPropertyEditor properties.static class
Table-Cell renderer that describes its contents rather than displays its contents.static enum
The window-type mode for this editor. -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionprotected void
addAltReservedKeys
(String prefix, String... suffixes) Add a reserved-key entry where the key can be changed to use various suffixes.void
Add aConfigPropertyEditor.ConfigPropertyListener
to thisConfigPropertyEditor
.protected void
Add an icon for use by a window system when this object is iconified.protected void
Add an icon for use by a window system when this object is iconified, given a class and resource.void
addRE
(String tail, TableCellRenderer r, TableCellEditor e) Provide a custom table-cell renderer and/or editor for the value in the second column of some row.protected void
addReservedKeys
(String... keys) Add a set of reserved keys.void
changedPropertyClears
(String p, String... others) Indicate that if one property's value changes, various other properties should have their values set to null.void
Remove the current GPG passphrase.void
Clear the recipients list.protected abstract String
Get the title for the window containing this editor.void
edit
(Component owner, ConfigPropertyEditor.Mode mode, Callable continuation, ConfigPropertyEditor.CloseMode quitCloseMode) Start the editor.protected abstract String
Get the title to use in modal dialog boxes reporting errorsprotected abstract String
Get the filename extension for configuration files.protected abstract String
Get the title for a file-chooser filter for the extension supported by this configuration property editor.protected void
configure the table so that new rows cannot be added and rows cannot be moved or deleted.Get the decoded properties.Get the encoded properties.Obtain a list of images that can be used by a GUI when a window associated with this instance is closed.protected String
getMonitoredPropertyValue
(String property) Get the value for a monitored property.static Supplier<char[]>
gpgPassphraseSupplier
(Component owner, boolean ignoreConsole) Create a supplier that can be used to ask for a GPG passphrase.boolean
Test if a key exists.void
Load a file to set up this editor.protected abstract String
Get the media type for the configuration file.protected void
monitorProperty
(String property) Monitor a property.protected boolean
prohibitEditing
(int row, int col) Prevent editing of specific rows and columns.void
Remove aConfigPropertyEditor.ConfigPropertyListener
from thisConfigPropertyEditor
.static char[]
requestGPGPassphrase
(Component owner, boolean ignoreConsole) Request a GPG passphrase.void
requestPassphrase
(Component owner) Request a GPG passphrase when one has not already been provided.void
requestPassphrase
(Component owner, boolean ignoreConsole) Request a GPG passphrase when one has not already been provided, optionally suppressing any use of the system console..void
Save the configuration in a file.boolean
Set a key.protected void
setDefaultProperty
(String key, String value) Set the default value for a property.void
setHelpMenuItem
(JMenuItem helpMenuItem) Provide a menu item for displaying 'help' documentation.protected void
setInitialExtraRows
(int count) Set the number of blank rows at the end of the table.void
Set the list of recipients to use when values are encrypted.void
setRecipients
(String[] list) Set the list of recipients to use when values are encrypted, given an array of recipients.void
setRecipients
(List<String> list) Set the list of recipients to use when values are encrypted, given a list of recipients.void
setSaveQuestion
(boolean mode) Set whether or not a warning message about needing to save the state of the table should be shown.protected void
Indicate that all reserved keywords have been added.void
showLoadDialog
(Component owner) Load a file, chosen using a dialog, to set up this editor.
-
Constructor Details
-
ConfigPropertyEditor
protected ConfigPropertyEditor()Constructor.
-
-
Method Details
-
getIconList
Obtain a list of images that can be used by a GUI when a window associated with this instance is closed.- Returns:
- the images
-
errorTitle
Get the title to use in modal dialog boxes reporting errors- Returns:
- the title
-
configTitle
Get the title for the window containing this editor.- Returns:
- the title
-
mediaType
Get the media type for the configuration file.- Returns:
- the media type
-
setDefaultProperty
Set the default value for a property. If the key starts with "base64.", this method will apply the Base-64 encoding. If the key starts with "ebase64.", the value must be first encrypted with GPG and then Base-64 encoded.- Parameters:
key
- the property keyvalue
- the default value for the specified key
-
extensionFilterTitle
Get the title for a file-chooser filter for the extension supported by this configuration property editor. This will appear as the title for a pull-down box that allows one to choose specific file extensions in an 'open' or 'save' dialog box- Returns:
- the title
-
extension
Get the filename extension for configuration files.- Returns:
- the filename extension
-
addIcon
Add an icon for use by a window system when this object is iconified.- Parameters:
icon
- the icon
-
addIcon
Add an icon for use by a window system when this object is iconified, given a class and resource. The name will be used to find a resource using the class loader for the specified class.To work well with Java modules, the resource should be in the same package as the class.
- Parameters:
clasz
- the classname
- the name of a resource
-
setRecipients
Set the list of recipients to use when values are encrypted. The list will be built interactively. A recipient is a string acceptable to the '-r' argument for GPG.- Parameters:
c
- the component on which to center dialog boxes
-
setRecipients
Set the list of recipients to use when values are encrypted, given an array of recipients. A recipient is a string acceptable to the '-r' argument for GPG.- Parameters:
list
- the recipient list; null to remove the recipients list
-
setRecipients
Set the list of recipients to use when values are encrypted, given a list of recipients. A recipient is a string acceptable to the '-r' argument for GPG.- Parameters:
list
- the recipient list; null to remove the recipients list
-
clearRecipients
public void clearRecipients()Clear the recipients list. After this is called, and until eithersetRecipients(Component)
orsetRecipients(String[])
is called, the user will be asked for recipients each time a value is encrypted. -
setSaveQuestion
public void setSaveQuestion(boolean mode) Set whether or not a warning message about needing to save the state of the table should be shown.- Parameters:
mode
- true if the user should be warned to save a modified table; false otherwise
-
save
Save the configuration in a file.- Parameters:
f
- the file- Throws:
IOException
- if an IO error occurred
-
addReservedKeys
Add a set of reserved keys.- Parameters:
keys
- the keys
-
addAltReservedKeys
protected void addAltReservedKeys(String prefix, String... suffixes) throws IllegalArgumentException Add a reserved-key entry where the key can be changed to use various suffixes. This will cause a spacer (a string of '-' characters where a key is expected) to be added after the entry, unless the prefix was already entered as a key by a call toaddReservedKeys(String...)
with that key given as the concatenation of the prefix, a period, and the first suffix. In this case, the call toaddReservedKeys(String...)
must immediately precede the call to this method or a sequence of calls to this method.- Parameters:
prefix
- the start of a keysuffixes
- the possible suffixes following the prefix and separated from the prefix by a period.- Throws:
IllegalArgumentException
- a key is already in use
-
setupCompleted
protected void setupCompleted()Indicate that all reserved keywords have been added. If called multiple times, only the first call will have any effect.This should be called in a constructor.
-
showLoadDialog
Load a file, chosen using a dialog, to set up this editor. This will be called before the editor's top-level window is created.- Parameters:
owner
- the component on which any file-chooser dialog should be centered; null if there is none- Throws:
IOException
- an IO error occurred
-
loadFile
Load a file to set up this editor. This will be called before the editor's top-level window is created.Calling this method directly will not result in the user being prompted to save changes if values are edited.
- Parameters:
f
- the file to open; null if no file should be loaded.- Throws:
IOException
- an IO exception occurred
-
requestPassphrase
Request a GPG passphrase when one has not already been provided. This method will typically open a dialog box to request a GPG passphrase for decryption if the passphrase is not already known. However, if the argument is null, this method is not called from the event dispatch thread, and a system console exists, the system console will be used to obtain the passphrase.To use a dialog box when 'owner' is null and a console exists, use
ConfigPropertyEditor cpe = ...; ... SwingUtilities.invokeAndWait(() -> { cpe.requestPassphrase(null); });
NOTE: tests indicate that in Java, a system console exists only when both standard input and standard output are connected to a terminal.
- Parameters:
owner
- a component over which a dialog box should be displayed
-
requestPassphrase
Request a GPG passphrase when one has not already been provided, optionally suppressing any use of the system console.. This method will typically open a dialog box to request a GPG passphrase for decryption if the passphrase is not already known. However, if the first argument is null, this method is not called from the event dispatch thread, and a system console exists, the system console will be used to obtain the passphrase unless the second argument true.To use a dialog box when 'owner' is null and a console exists, use
ConfigPropertyEditor cpe = ...; ... SwingUtilities.invokeAndWait(() -> { cpe.requestPassphrase(null); });
NOTE: tests indicate that in Java, a system console exists only when both standard input and standard output are connected to a terminal.
- Parameters:
owner
- a component over which a dialog box should be displayedignoreConsole
- true if a console should always be ignored; false otherwise
-
gpgPassphraseSupplier
Create a supplier that can be used to ask for a GPG passphrase. The supplier will callrequestGPGPassphrase(Component,boolean)
, which will typically open a dialog box to request a GPG passphrase. However, if the first argument is null, this method is not called from the event dispatch thread, and a system console exists, the system console will be used to obtain the passphrase (unless the second argument is true, in which case the current thread will block until a passphrase is provided via a dialog box or the dialog box is canceled).- Parameters:
owner
- a component over which a dialog box should be displayedignoreConsole
- true if a console should always be ignored; false otherwise- Returns:
- the supplier
- See Also:
-
requestGPGPassphrase
Request a GPG passphrase. This method will typically open a dialog box to request a GPG passphrase. However, if the first argument is null, this method is not called from the event dispatch thread, and a system console exists, the system console will be used to obtain the passphrase (unless the second argument is true, in which case the current thread will block until a password is provided or the dialog box is canceled).To use a dialog box when 'owner' is null, the call is not from the event dispatch thread, and a console exists, use
SwingUtilities.invokeAndWait(() -> { char[] passphrase = ConfigPropertyEditor .requestGPGPassphrase(null, true); });
NOTE: tests indicate that in Java, a system console exists only when both standard input and standard output are connected to a terminal.
- Parameters:
owner
- a component over which a dialog box should be displayedignoreConsole
- true if a console should always be ignored; false otherwise
-
clearPassphrase
public void clearPassphrase()Remove the current GPG passphrase. As a general rule, this method should be called as soon as a passphrase is no longer needed, or will not be needed for some time. -
freezeRows
protected void freezeRows()configure the table so that new rows cannot be added and rows cannot be moved or deleted. -
setInitialExtraRows
protected void setInitialExtraRows(int count) Set the number of blank rows at the end of the table. These rows are added after any rows containing reserved keys. The default is 10. Set to zero if only the reserved rows should be in the table.- Parameters:
count
- the number of rows
-
addRE
Provide a custom table-cell renderer and/or editor for the value in the second column of some row. The key provided in column 1 of a row will be tested against the tail argument provided by this method. If there is an exact match for a key, the renderer or editor is used. Otherwise the key is replaced by the remainder of the key after its first period and the test is repeated until there is a match or the key can no longer by shorted. If there are multiple possible matches, the longest match is used. A match is based on the first argument, not the second or third.- Parameters:
tail
- the last components of a key.r
- the table cell renderer to use to display the value corresponding to a key; null if the normal choice is not overridden.e
- the table cell editor to use to modify or create a value corresponding to a key; null if the normal choice is not overridden.
-
setHelpMenuItem
Provide a menu item for displaying 'help' documentation. One should be cautious about using an instance ofHelpMenuItem
as the argument to this method due to this menu item's action opening a new window, which can be problematic with modal dialogs.If not called with a non-null argument, a Help menu will not be included.
- Parameters:
helpMenuItem
- the menuItem; null if there is not such a menu item
-
addConfigPropertyListener
Add aConfigPropertyEditor.ConfigPropertyListener
to thisConfigPropertyEditor
. The listeners will be called when a monitored property's value has changed.- Parameters:
l
- the listener to add- See Also:
-
removeConfigPropertyListener
Remove aConfigPropertyEditor.ConfigPropertyListener
from thisConfigPropertyEditor
.- Parameters:
l
- the listener to remove
-
monitorProperty
Monitor a property. Registered listeners will be called when the property changes value.- Parameters:
property
- the property to monitor- Throws:
IllegalArgumentException
- the property's name starts with "ebase64.", indicating an encrypted value- See Also:
-
getMonitoredPropertyValue
Get the value for a monitored property.- Parameters:
property
- a property that is being monitored- Returns:
- the value for the specified property
- Throws:
IllegalArgumentException
- the key was not monitored
-
changedPropertyClears
Indicate that if one property's value changes, various other properties should have their values set to null.This is useful in cases such as one property providing a file format and a second providing a file name that is required to have a particular extension.
- Parameters:
p
- a propertyothers
- a list of properties whose values should be set to null if property p changes
-
hasKey
Test if a key exists.- Parameters:
key
- the key- Returns:
- true if the key exists; false otherwise
-
set
Set a key. The first argument is used when a key starts with "ebase64." as that indicates that GPG encryption will be used, in which case dialog boxes will be used to get the names of recipients. If a key already exists, its value will be overwritten.- Parameters:
owner
- a component over which a dialog box may appear; null if a dialog box's location is not constrainedkey
- the keyvalue
- the value for the key- Returns:
- true if successful; false otherwise (e.g., GPG failed to encrypt, the key was missing, or the value was null)
-
prohibitEditing
protected boolean prohibitEditing(int row, int col) Prevent editing of specific rows and columns. The default implementation returnsfalse
for any pair of arguments. If the value returned is dependent on a cell's row and column instead of its contents, the behavior of this class may be erratic unless those cells cannot be moved.This method can not override the prohibition on editing reserved keys or spacers.
- Parameters:
row
- the table rowcol
- the table column- Returns:
- true if editing is explicitly prohibited for the given row and column; false otherwise
-
edit
public void edit(Component owner, ConfigPropertyEditor.Mode mode, Callable continuation, ConfigPropertyEditor.CloseMode quitCloseMode) Start the editor. This will cause a window to appear that will allow configuration parameters to be edited. Depending on the mode argument, the window may be a JFrame or a JDialog, and for a dialog, modal or modeless. One should callloadFile(File)
if the parameters should be loaded from a known file orshowLoadDialog(Component)
if a dialog box should be used to select a file. Otherwise default values may be provided for some of the parameters.- Parameters:
owner
- the component on which the editor should be centered; null if there is nonemode
- the window mode (ConfigPropertyEditor.Mode.JFRAME
,ConfigPropertyEditor.Mode.MODAL
, orConfigPropertyEditor.Mode.MODELESS
)continuation
- aCallable
that provides some code to run while or just after this editor's top-level window is closing.quitCloseMode
-ConfigPropertyEditor.CloseMode.QUIT
if the process should exit when the editor closes;ConfigPropertyEditor.CloseMode.CLOSE
if the process should continue after the editor closes, orConfigPropertyEditor.CloseMode.BOTH
if both a Quit and a Close menu item should appear in the File menu.
-
getEncodedProperties
Get the encoded properties. Keys starting with 'base64.' will have their value Base-64 encoded and keys starting with 'ebase64.' will have their valued encrypted with GPG and then Base-64 encoded.- Returns:
- the properties, encoded if necessary
-
getDecodedProperties
Get the decoded properties. Base-64 encoding will be removed for unencrypted properties and all string substitution will be performed before the results are returned. For keys starting with the component "base64", the first token and its following delimiter ("base64.") will be stripped from the key. Encrypted data is handled so as to allow the unencrypted data to be kept for as short a time as possible. For other keys, the value for a key will have parameter references replaced with the corresponding values.The
Properties
object returned by this method has been customized: for keys starting with "ebase64.", the methodProperties.getProperty(String)
will return the encrypted value, whereas the methodget(Object)
will return anObject
that is actually an array of characters and that contains the decrypted data. For these keys, a new array will be returned each timeProperties.get(Object)
is called. This is somewhat atypical &emdash; normallyProperties.getProperty(String)
andProperties.get(Object)
return the same object but with a different type. The rationale is that encrypted data is typically sensitive and should be removed as soon as it is no longer needed. You can overwrite a character array, but you cannot overwrite a string, which will persist until reclaimed by the garbage collector.NOTE: For encrypted values the method
requestPassphrase(Component)
should be called before a call toProperties.get(Object)
if the dialog box is to be placed over a specific component.- Returns:
- the properties
- See Also:
-