Skip to content

Custom property types

ljacqu edited this page Feb 2, 2020 · 5 revisions

You can implement your own Property or PropertyType if you require additional types or custom behavior. This page shows you the different ways in which you can implement specific types for ConfigMe.

Constructing with PropertyBuilder

If you need something like a custom array type or a "fluent" API to define custom values, it's worth having a look at the PropertyBuilder class.

Examples:

Property<Long[]> property = new PropertyBuilder.ArrayPropertyBuilder<>(PrimitivePropertyType.LONG, Long[]::new)
    .path("given.path")
    .defaultValue(5L, 11L, 23L)
    .build();

Creates a Property which holds a Long array. The default value is an array with the values {5, 11, 23}.

MapProperty<Double> result = new PropertyBuilder.MapPropertyBuilder<>(PrimitivePropertyType.DOUBLE)
    .path("the.path")
    .defaultEntry("leet", 1337.0)
    .defaultEntry("all", 411.0)
    .build();

Creates a Property<Map<String, Double>> whose default value is a map with entries {"leet" -> 1337.0, "all" -> 411.0}

Implementing PropertyType

The easiest way to implement a custom type is to implement your own PropertyType. The property type has two methods which define how the value is converted from and to the property resource. For example, if we want to have a Byte type:

public class ByteType implements PropertyType<Byte> {

    @Override
    public Byte convert(Object object, ConvertErrorRecorder errorRecorder) {
        if (object instanceof Number) {
            return ((Number) object).byteValue();
        }
        return null;
    }

    @Override
    public Object toExportValue(Byte value) {
        return value.intValue();
    }
}

Then, create a TypeBasedProperty with it:

new TypeBasedProperty<>("my.path", (byte) 3, new ByteType());

Or, for convenience, create an extension of this class like IntegerProperty etc. for easier use.

Implementing (Base)Property

You can also directly extend BaseProperty. In this case, two methods have to be implemented which are almost identical to the PropertyType methods we've seen above. The difference is that you get the PropertyReader directly instead of the value at the property's path, and you can override other methods of BaseProperty if so desired.

Typically, this approach should be chosen if your new property has a collection type or is otherwise more complicated to construct.

public class ByteProperty extends BaseProperty<Byte> {

    public ByteProperty(String path, Byte defaultValue) {
        super(path, defaultValue);
    }

    @Override
    protected Byte getFromReader(PropertyReader reader,
                                 ConvertErrorRecorder errorRecorder) {
        Object value = reader.getObject(getPath());
        if (value instanceof Number) {
            return ((Number) value).byteValue();
        }
        return null;
    }

    @Override
    public Object toExportValue(Byte value) {
        return value.intValue();
    }
}

Custom validation

Alternatively, if you want to restrict the range of values that are admissible for a property, consider implementing validation where you use the property, or implement the logic in the migration service. This would also allow you to log some meaningful error.

For example, if we implemented a ByteProperty but then kept having to cast it to an integer everywhere it's used, it would be a more elegant approach to declare an integer property and to have a check in the migration service that ensures that the value could be represented with a Byte. Since the migration service is always called, it is guaranteed that we'll never retrieve any invalid value from the SettingsManager.

Conversion outside of ConfigMe

Similar to the Custom validation approach, in certain cases if you want to map values to a very specific type or with specific rules where bean properties aren't satisfactory, it may make sense for you to perform this conversion outside of ConfigMe based on simpler properties that ConfigMe manages.

This way, you have full control on how simpler values are converted to your complicated structure. This can be especially interesting if you only need to use the complicated structure in one place in your code.

Quiz questions

  • In the export value methods, why is it OK to immediately call a method on the value without a null check?
  • In the export value methods, why is Byte#intValue called and not Byte#toString?

Navigation

« Bean properties Custom property types Technical documentation »