apron
apron copied to clipboard
Advanced Properties — Read and write Java .properties files in a more sane manner.
Apron
Marco Herrn [email protected] 2018-05-14 :compat-mode!: :toc: :homepage: https://github.com/hupfdule/apron :download-page: https://github.com/hupfdule/apron/releases :javadoc-url: https://javadoc.io/doc/de.poiu.apron/apron/ :license-link: https://github.com/hupfdule/apron/blob/master/LICENSE.txt :kilt-homepage: https://github.com/hupfdule/kilt :log4j2-jul-bridge: https://logging.apache.org/log4j/2.x/log4j-jul/index.html :slf4j-jul-bridge: https://www.slf4j.org/legacy.html#jul-to-slf4j :source-highlighter: prettify :apron-version: 2.1.1
[.float-group]
image:apron-icon.svg[Apron,role="right", width="75"] Apron - Advanced Properties
Read and write Java .properties files in a more sane manner.
What is Apron
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-homepage}[Kilt toolset], but was intended from the start to be a general purpose library.
What can Apron be used for
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-homepage}[Kilt] does).
-
Reordering multiple .properties files to contain their entries in the same order.
-
Reformatting .properties files to conform to a specific format.
Prerequisites
Apron has no runtime dependencies on other libraries.
Apron can be used with Java 8 or higher.
Installation
To use Apron in a maven based project use the following maven coordinates:
[source,xml,subs="verbatim,attributes"]
<dependency>
<groupId>de.poiu.apron</groupId>
<artifactId>apron</artifactId>
<version>{apron-version}</version>
</dependency>
Otherwise download the jar-file of Apron from the {download-page}[Download page] and put it into the classpath of your application.
Usage
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:
[source,java]
// 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:
[source,java]
// 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-url}[Javadoc API] for more details.
Reformatting and Reordering
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.
=== Reformatting
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:
[source,java]
// 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("
// 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
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:
[source,java]
// 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) );
java.util.Properties wrapper
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.
=== Example
To use it create it either via
[source,java]
de.poiu.apron.PropertyFile propertyFile= … de.poiu.apron.java.util.Properties properties= new de.poiu.apron.java.util.Properties(propertyFile);
or via
[source,java]
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.
=== Differences to java.util.Properties
The wrapper tries to fulfil the java.util.Properties API as good as
possible. However there are a few differences:
-
java.util.Propertiesis derived from Hashtable and therefore non-String keys and values can be stored in it (although that is highly discouraged). As ApronsPropertyFileis 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
PropertyFileonly supports key-value-based.propertiesfiles. Asjava.util.Propertiesalso 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.Propertiesbeing derived from Hashtable is thread-safe. However ApronsPropertyFileis not thread-safe and therefore this wrapper is also not thread-safe.
Logging
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 {log4j2-jul-bridge} 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
[source,java]
SLF4JBridgeHandler.removeHandlersForRootLogger(); SLF4JBridgeHandler.install();
or setting the handler in the logging.properties:
[source,xml]
handlers = org.slf4j.bridge.SLF4JBridgeHandler
See {slf4j-jul-bridge} for more information.
// There are no known bugs at the moment //Known Bugs //----------
License
Apron is licensed under the terms of the link:{license-link}[Apache license 2.0].