Apron is a small library for reading and writing Java .properties files.
The main goal of this library is to be compatible with the
java.util.Properties
class. Not API-wise (the API is quite different),
but being able to read every Java .properties file and getting exactly the
same key-value pairs as java.util.Properties
does.
However Apron maintains the order of the entries in the properties files and also the comments, blank lines and whitespace before keys and around separators.
This allows writing .properties files back that do not differ from the original ones.
Since version 2.0.0 Apron provides the ability to reformat and reorder the content of .properties files according to different constraints. Refer to Reformatting and Reordering for a more detailled description.
Apron was mainly written to be used in the Kilt toolset, but was intended from the start to be a general purpose library.
Some examples for usage scenarios of Apron are:
-
Using .properties files as config files for an application that may be manually edited by a user as well as modified by the application itself (e.g. via a configuration dialog). The manual modifications (like the order of entries, as well as comments, empty lines and even the formatting of entries) will remain.
-
Exporting and importing Java i18n resource bundles for translation (like Kilt does).
-
Reordering multiple .properties files to contain their entries in the same order.
-
Reformatting .properties files to conform to a specific format.
Apron has no runtime dependencies on other libraries.
Apron can be used with Java 8 or higher.
To use Apron in a maven based project use the following maven coordinates:
<dependency>
<groupId>de.poiu.apron</groupId>
<artifactId>apron</artifactId>
<version>2.1.1</version>
</dependency>
Otherwise download the jar-file of Apron from the Download page and put it into the classpath of your application.
The main important class in Apron is de.poiu.apron.PropertyFile
.
It provides methods to create a new instance by reading a .properties file
from File or InputStream as well as methods for populating an instance
programmatically.
The main difference to the usual java.util.Properties
is that this class
does not implement the java.util.Map
interface and provides access to the
content of the PropertyFile in two different ways:
-
as key-value pairs
-
as Entries
The key-value pairs are the actual interesting content of the .properties
files and are the same as when read via java.util.Properties
. However,
since PropertyFile is able to retain all comments, blank lines and even the
formatting of the file it stores all this information in objects of type
Entry
. There are two implementations of Entry
:
- BasicEntry
-
A non-key-value pair like a comment or an empty line
- PropertyEntry
-
An actual key-value pair
The Entry objects store their content in escaped form. That means all whitespaces, linebreaks, escape characters, etc. are contained in exactly the same form as in the written .properties file.
The key-value pairs instead contain the content in unescaped form (as you
would expect from java.util.Properties
).
To minimize confusion the escaped values are stored as CharSequence whereas the unescaped values are stored as String.
A PropertyFile instance also allows writing its content back to disk. It provides 3 methods (each in two variants) for doing so:
- overwrite
-
Writes the contents of the PropertyFile to a new file or overwrite an existing file.
- update
-
Update an existing .properties file with the values in the written PropertyFile.
- save
-
Use either the above mentioned overwrite method if the given file does not exist or the update method if the file already exists.
The most interesting method is the update
method, since this
differentiates PropertyFile from java.util.Properties
. It actually only
updates the values of the key-value pairs without touching any other
formatting. Blank lines, comments, whitespaces and even escaping and
special formatting of the keys are not altered at all. Also the order of
the key-value pairs remains the same.
The behaviour when writing a PropertyFile can be altered by providing it an
optional ApronOptions
object.
This is an example for a typical usage of PropertyFile as a replacement for
java.util.Properties
:
// Read the file "application.properties" into a PropertyFile
final PropertyFile propertyFile= PropertyFile.from(
new File("application.properties"));
// Read the value of the key "someKey"
final String someValue= propertyFile.get("someKey");
// Set the value of "someKey" to a new value
propertyFile.set("someKey", "aNewValue");
// Write the PropertyFile back to file by only updating the modified values
propertyFile.update(new File("application.properties"));
This is an example for a more advanced usage of PropertyFile that allows acessing comment lines and explicitly formatted (escaped) entries:
// Read all Entries (that means BasicEntries as well as PropertyEntries)
final List<Entry> entries= propertyFile.getAllEntries();
// Add a comment line to this PropertyFile
propertyFile.appendEntry(new BasicEntry("# A new key-value pair follows"));
// Add a new key-value pair to this PropertyFile
// Be aware that by using appendEntry() it could be possible to insert
// duplicate keys into this PropertyFile. The behaviour is then undefined.
// It is the responsibility of the user of PropertyFile to avoid this.
// PropertyEntries contain their content in _escaped_ form. Therefore the
// Backslashes and newline character are not really part of the key and value
propertyFile.appendEntry(new PropertyEntry("a new \\\nkey", "a new \\\nvalue"));
// key-value pairs are _unescaped_. Therefore the following method call
// will return the string "a new value"
final String myNewValue= propertyFile.get("a new key");
// Specify an ApronOptions object that writes with ISO-8859-1 encoding
// instead of the default UTF-8.
final ApronOptions apronOptions= ApronOptions.create()
.with(java.nio.charset.StandardCharsets.ISO_8859_1);
// Write the PropertyFile back to file by only updating the modified values
propertyFile.update(new File("application.properties"), apronOptions);
See the Javadoc API for more details.
Since version 2.0.0 Apron provides a de.poiu.apron.reformatting.Reformatter
class that allows reformatting and reordering the content of .properties
files.
The specific behaviour when reformatting and reordering can be specified
via a de.poiu.apron.reformatting.ReformatOptions
object.
For convenience the de.poiu.apron.PropertyFile
class provides some methods
to reformat or reorder the entries in that PropertyFile.
When reformatting a format string can be given to specify how to format
leading whitespace, separators and line endings. The default format string
is <key> = <value>\n
for
-
no leading whitespace
-
an equals sign surrounded by a single whitespace on each side as separator
-
a
\n
(line feed) character as new line character
By default the keys and values of the reformatted files are not modified. That means any special formatting (like insignificant whitespace, newlines and escape characters) remain after reformatting.
This can be changed via the reformatKeyAndValue
option in which case
these will be modified as well.
This is an example for reformatting a PropertyFile:
// Create the ReformatOptions to use to read and write with UTF-8 (which is the default anyway),
// reformat via a custom format string and also reformat the keys and values.
final ReformatOptions reformatOptions= ReformatOptions.create()
.with(UTF_8)
.withFormat("<key>: <value>\r\n")
.withReformatKeyAndValue(true)
;
// Create a Reformatter with the specified ReformatOptions
final Reformatter reformatter= new Reformatter(reformatOptions);
// Reformat a single .properties file according to the specified ReformatOptions
reformatter.reformat(new File("myproperties.properties"));
Reordering the content of .properties files can be done either by alphabetically sorting the keys of the key-value pairs or by referring to a template file in which case the keys are ordered in the same order as in the template file.
Apron allows specifying how to handle non-property lines (comments and empty lines) when reordering. It is possible to move them along with the key-value pair that follows them or the key-value pair that precedes them or be just left at the same position as they are.
This is an example for reordering a PropertyFile:
// Create the ReformatOptions to use that does not reorder empty lines and comments
final ReformatOptions reorderOptions= ReformatOptions.create()
.with(AttachCommentsTo.ORIG_LINE)
;
// Create a Reformatter with the specified ReformatOptions
final Reformatter reformatter= new Reformatter(reorderOptions);
// Reorder a single .properties file alphabetically according to the specified ReformatOptions
reformatter.reorderByKey(new File("myproperties.properties"));
// Reorder a single .properties file according to the order in another .properties file.
// This time we want to reorder comments and empty lines along with the key-value pair that
// follows them. This is possible by specifying a ReformatOptions object when calling the
// corresponding reorder method.
reformatter.reorderByTemplate(
new File("template.properties"),
new File("someOther.properties"),
reorderOptions.with(AttachCommentsTo.NEXT_PROPERTY)
);
Since version 2.1.0 Apron provides a de.poiu.apron.java.util.Properties
class as a wrapper to be used as a drop-in replacement where a
java.util.Properties
object is required.
This wrapper derives from java.util.Properties
, but uses an Apron
PropertyFile
as the actual implementation.
To use it create it either via
de.poiu.apron.PropertyFile propertyFile= …
de.poiu.apron.java.util.Properties properties=
new de.poiu.apron.java.util.Properties(propertyFile);
or via
de.poiu.apron.PropertyFile propertyFile= …
de.poiu.apron.java.util.Properties properties= propertyFile.asProperties();
All access via the properties
object will then access to the
propertyFile
object. Both objects can be used interchangebly to access
the actual contents.
The wrapper tries to fulfil the java.util.Properties
API as good as
possible. However there are a few differences:
-
java.util.Properties
is derived from Hashtable and therefore non-String keys and values can be stored in it (although that is highly discouraged). As ApronsPropertyFile
is not derived from Hashtable it doesn’t share this flaw. Therefore trying to use any other objects than Strings as keys or values will fail. -
Aprons
PropertyFile
only supports key-value-based.properties
files. Asjava.util.Properties
also provides methods to read and write to XML files and those formats are not supported by Apron, the corresponding methods will always throw an UnsupportedOperationException. -
java.util.Properties
being derived from Hashtable is thread-safe. However ApronsPropertyFile
is not thread-safe and therefore this wrapper is also not thread-safe.
There are a few cases this library issues some logging statements (when closing a writer didn’t succeed and if an invalid unicode sequence was found that will be left as is). Those few logging statements don’t justify a dependency on a logging framework. Therefore we just use java.util.logging for that purpose.
When using Apron in an application that uses another logging framework please use those logging frameworks ability to bridge java.util.logging to their actual implementation.
For log4j2 this can be done by including the log4j2-jul
and log4j2-api
jar
(and some implemention, e.g. log4j2-core
) and setting the system property
java.util.logging.manager
to org.apache.logging.log4j.jul.LogManager
.
See https://logging.apache.org/log4j/2.x/log4j-jul/index.html for more information.
For slf4j this can be done by including the jul-to-slf4j
jar (and some
implementation, e.g. logback
) and programmatically calling
SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();
or setting the handler in the logging.properties
:
handlers = org.slf4j.bridge.SLF4JBridgeHandler
See https://www.slf4j.org/legacy.html#jul-to-slf4j for more information.
Apron is licensed under the terms of the Apache license 2.0.