Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Safely call setter after getter chain eg foo.getX().getY().setZ(...);

How do I safely call setter after getter chain eg foo.getX().getY().setZ(...);? For example, suppose I have a nested POJO, and I want to be able to set a field of a nested object.

Foo foo = ...
foo.getX().getY().setZ(...);

I want the behavior to be such that if X and Y do not exist then they are created automatically; otherwise it reuses the existing object.

In other words, I want it to be behave equivalent to

Foo foo = ...
X x = foo.getX();
if (x == null) { 
  x = new X();
  foo.setX(x);
}

Y y = x.getY();
if (y == null) {
  y = newY();
  x.setY(y);
}

y.setZ(...);

I'm wondering if there is a trick out there using reflection/functional that comes close to this.

I also have the following constraints:

  • I cannot modify any of the classes
  • The solution must know about only the public getters and setters, not the private instance variables
  • I want the getter to modify the internal state only when specifically requested; I don't want x = foo.getX() to modify foo.
like image 814
Display Name Avatar asked Nov 15 '18 08:11

Display Name


1 Answers

Use functional programming. Create a method that accepts a getter, a setter and a supplier for the default value, that returns a getter encapsulating the logic you need:

public static <T, U> Function<T, U> getOrSetDefault(
        Function<T, U> getter,
        BiConsumer<T, U> setter,
        Supplier<U> defaultValue) {

    return t -> {
        U u = getter.apply(t);
        if (u == null) {
            u = defaultValue.get();
            setter.accept(t, u);
        }
        return u;
    };
}

Then create these decorated getters:

Function<Foo, X> getX = getOrSetDefault(Foo::getX, Foo::setX, X::new);
Function<X, Y> getY = getOrSetDefault(X::getY, X::setY, Y::new);

Finally, chain them and apply the resulting function passing in your foo instance as an argument:

Foo foo = ...
getX.andThen(getY).apply(foo).setZ(...);

EDIT: This assumes that both X and Y have a no-args constructor that is referenced by X::new and Y::new, respectively. But you could use anything as the Supplier, i.e. an already created instance, or the return value of a method, etc.

like image 190
fps Avatar answered Oct 18 '22 20:10

fps