The Property Binder Java library provides typed access to entries in properties files or other sources of configuration. It offers such access by allowing a programmer to provide it a Java interface whose methods represent the keys of the properties file. Programmers annotate these methods to indicate:
- What property the method represents
- What default value(s) it should assume if the property is not present
- What pattern separates the individual values of multi-valued properties, and what formatting hints to use, if any
Property Binder is compatible with JDK 8 or better, and its JAR includes JPMS metadata. Thus, you can use Property Binder as a module on the module path, or as a regular JAR on the class path.
Property Binder builds on work described in this blog post. That post, along with a similar technique used in JewelCli and Preon, demonstrate a means of declarative programming in Java that seemed unique and powerful and enough to deserve its own name: PICA (Proxied Interfaces Configured with Annotations). This article describes the PICA technique and the beginnings of Property Binder.
- Create an interface whose methods represent keys in a properties file, map, resource bundle, or other source of configuration.
- Annotate the methods of the interface as needed to designate property keys, default values, etc.
- Create an instance of
PropertyBinder
, giving it theClass
object of your interface. - Stamp out instances of your interface that are bound to specific
configuration sources using
bind()
.
Given this properties file:
and this interface:
Then the following tests should pass:
By presenting bits of configuration to your application as instances of a Java interface, you decouple the things that use the configuration from the means by which they're read/stored. You thereby enable easier testing of those pieces of your application that use the configuration -- supply mocks or stubs of the interface that answer different values for the configuration properties.
By letting Property Binder create instances of those interfaces for you, you relieve your application of the grunt work of converting configuration values to sensible Java types, supplying default values, and so forth.
Out of the box, Property Binder can convert property values to these and more types:
- all the Java primitives and their object counterparts
- any
enum
type java.math.Big(Decimal|Integer)
java.util.Date
- many date/time concepts from
java.time
java.io.File
java.net.(InetAddress|URI|URL)
java.lang.Class
- arrays of the above
java.util.List
s of the aboveOptional
of the above; alsoOptional(Int|Long|Double)
To convert different kinds of values, or to complement the conversions
for supported types, register subclasses of Conversion
as a
service
of type com.pholser.util.properties.conversions.Conversion
.
When converting a given property value, Property Binder tries the
registered conversions in an unspecified order until one succeeds.
If none succeeds, Property Binder raises an IllegalArgumentException
.
For congruency with the supported conversions, you should arrange for
your custom conversions to raise IllegalArgumentException
if something
goes wrong during the conversion of a property value.
Property Binder admits properties files, resource bundles, and string-keyed
maps out of the box. You can bind other types of string-keyed configuration
by providing an implementation of interface PropertySource
.
You may mark return types and/or parameter types of interface methods with
Bean Validation API annotations. To enforce the validations, call fluent
method validated()
on your instance of PropertyBinder
. Then:
- Any zero-parameter methods will have their corresponding property values
validated when the instance
bind()
s a source of configuration - Any methods with one or more parameters, upon invocation, will validate the corresponding arguments and resulting property value.
-
Java 8 date/time artifacts (+ array/list/opt) tests
-
Optional(Int|Long|Double)
(+ array/list/opt) tests -
Allow for simple reflective cases? One-arg String ctor, valueOf?
-
List<String> patterns
->List<U>
, where U is another converted thing? (DateTimeFormatter? SimpleDateFormat? Pattern? ...) -
Chained conversions (e.g. Path <- URI <- String)?
-
Docs:
- Demonstrate in tests whether modifying the underlying config store affects the answers a proxy gives
- Publish Javadocs via javadocs.io