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.
java reflection java-8 functional-programming
|
show 3 more comments
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.
java reflection java-8 functional-programming
So you don't want to touch theFoo
,X
,Y
, ... classes, right? AOP is probably your best bet.
– Robby Cornelissen
Nov 15 '18 at 8:30
1
The keyword to google here is probably "lenses".
– Thilo
Nov 15 '18 at 8:34
You can create a getter in class Foo in such a way that it returns X if not null else retruns a new object of X. X getX() { return x == null ? return new X() : x; }
– efex09
Nov 15 '18 at 8:51
@efex09 "I cannot modify any of the classes"
– Marco Bonelli
Nov 15 '18 at 8:59
1
@Thilo are you referring to Haskell's Lenses (eg stackoverflow.com/questions/8307370/functional-lenses)? I'm not familiar with Haskell so I'm not sure if that solves the problem
– Display Name
Nov 15 '18 at 9:27
|
show 3 more comments
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.
java reflection java-8 functional-programming
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.
java reflection java-8 functional-programming
java reflection java-8 functional-programming
edited Nov 15 '18 at 9:26
Mark Rotteveel
61k1478121
61k1478121
asked Nov 15 '18 at 8:26
Display NameDisplay Name
4082515
4082515
So you don't want to touch theFoo
,X
,Y
, ... classes, right? AOP is probably your best bet.
– Robby Cornelissen
Nov 15 '18 at 8:30
1
The keyword to google here is probably "lenses".
– Thilo
Nov 15 '18 at 8:34
You can create a getter in class Foo in such a way that it returns X if not null else retruns a new object of X. X getX() { return x == null ? return new X() : x; }
– efex09
Nov 15 '18 at 8:51
@efex09 "I cannot modify any of the classes"
– Marco Bonelli
Nov 15 '18 at 8:59
1
@Thilo are you referring to Haskell's Lenses (eg stackoverflow.com/questions/8307370/functional-lenses)? I'm not familiar with Haskell so I'm not sure if that solves the problem
– Display Name
Nov 15 '18 at 9:27
|
show 3 more comments
So you don't want to touch theFoo
,X
,Y
, ... classes, right? AOP is probably your best bet.
– Robby Cornelissen
Nov 15 '18 at 8:30
1
The keyword to google here is probably "lenses".
– Thilo
Nov 15 '18 at 8:34
You can create a getter in class Foo in such a way that it returns X if not null else retruns a new object of X. X getX() { return x == null ? return new X() : x; }
– efex09
Nov 15 '18 at 8:51
@efex09 "I cannot modify any of the classes"
– Marco Bonelli
Nov 15 '18 at 8:59
1
@Thilo are you referring to Haskell's Lenses (eg stackoverflow.com/questions/8307370/functional-lenses)? I'm not familiar with Haskell so I'm not sure if that solves the problem
– Display Name
Nov 15 '18 at 9:27
So you don't want to touch the
Foo
, X
, Y
, ... classes, right? AOP is probably your best bet.– Robby Cornelissen
Nov 15 '18 at 8:30
So you don't want to touch the
Foo
, X
, Y
, ... classes, right? AOP is probably your best bet.– Robby Cornelissen
Nov 15 '18 at 8:30
1
1
The keyword to google here is probably "lenses".
– Thilo
Nov 15 '18 at 8:34
The keyword to google here is probably "lenses".
– Thilo
Nov 15 '18 at 8:34
You can create a getter in class Foo in such a way that it returns X if not null else retruns a new object of X. X getX() { return x == null ? return new X() : x; }
– efex09
Nov 15 '18 at 8:51
You can create a getter in class Foo in such a way that it returns X if not null else retruns a new object of X. X getX() { return x == null ? return new X() : x; }
– efex09
Nov 15 '18 at 8:51
@efex09 "I cannot modify any of the classes"
– Marco Bonelli
Nov 15 '18 at 8:59
@efex09 "I cannot modify any of the classes"
– Marco Bonelli
Nov 15 '18 at 8:59
1
1
@Thilo are you referring to Haskell's Lenses (eg stackoverflow.com/questions/8307370/functional-lenses)? I'm not familiar with Haskell so I'm not sure if that solves the problem
– Display Name
Nov 15 '18 at 9:27
@Thilo are you referring to Haskell's Lenses (eg stackoverflow.com/questions/8307370/functional-lenses)? I'm not familiar with Haskell so I'm not sure if that solves the problem
– Display Name
Nov 15 '18 at 9:27
|
show 3 more comments
4 Answers
4
active
oldest
votes
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.
2
Note that nothing here assumes the default value for missing objects is what the no-args constructor returns (or that such a constructor exists). You can easily plug in something other thanX::new
into your accessor definitions.
– Thilo
Nov 15 '18 at 13:42
@Thilo Yes, that is an advantage of this approach. Will add a note.
– Federico Peralta Schaffner
Nov 15 '18 at 14:18
I ended up using an approach that was similar to yours. However, I used reflection to avoid having to pass the setter and the default values. See my Answer for more info.
– Display Name
Nov 17 '18 at 6:21
add a comment |
TL;DR: Don't try to force functional Java where there clearly is no place for it.
The only way you can do this functionally in Java 8 without modifying any of the classes is using Optional
s and their .orElse()
method. It gets really long really quick, but it's the only way that actually makes sense using functional if you want to do it in one line only.
Optional.ofNullable(foo.getX()).orElseGet(() -> { foo.setX(new X()); return foo.getX(); }).setY(...);
If foo.setX()
also returns the setted value it can be simplified as:
Optional.ofNullable(foo.getX()).orElseGet(() -> foo.setX(new X())).setY(...);
This is the only generic and functional way of doing it that I can think of. Stated the above, you can clearly see that this becomes huge and ugly even for just a chain of two getters, so I wouldn't advise it. I would definitely suggest you to use the classic multi-statement approach if you have to chain more than one call.
Another option, even thought not really that functional, is to use the tristate operator, still only if the setter returns the setted value:
(foo.getX() == null ? foo.setX(new X()) : foo.getX()).setY(...);
This has the probably unwanted side effect of calling the getter twice if the element is found, which you may not like, but could be possibly ignored if the getter caches the value somehow.
Yep, that becomes quite unwieldy quite fast. Thanks for the suggestion though!
– Display Name
Nov 15 '18 at 9:25
@DisplayName I added another option.
– Marco Bonelli
Nov 15 '18 at 9:29
add a comment |
To start off I just want to mention that this probably isn't the best solution and I'm sure there are ways to optimize this. That said, I wanted to try my hand at CGLIB and ObjenesisHelper again.
Using CGLIB and ObjenesisHelper we can wrap the data object in a proxy which will intercept the get
methods. Using this interceptor we can add the logic you described in your post. Lets start off by assume these are our data types (using lombok for brevity).
@Data class W { private X x; }
@Data class X { private Y y; }
@Data class Y { private Z z; }
@Data class Z { private int alpha; }
Our final solution can be used like the following:
public static void main(String args) {
final W w = ProxyUtil.withLazyDefaults(new W());
System.out.println(w.getX().getY().getZ().getAlpha());
}
Implementation
Currently, if we try to invoke new W().getX().getY().getZ().getAlpha()
we will get a NullPointerException
when invoking getY()
because getX()
returned null. Even if we manage to produce a default X
value, we will still need a default Y
value to not get a null pointer on getZ()
and getAlpha()
and so forth. The proxy we create needs to be generic and be able to wrap its sub components recursively.
Okay, so lets start. The first thing we need to do is create a MethodInterceptor
. Whenever any call hits our proxy instance it will perform the logic of our MethodInterceptor
. We need to first determine if the method called is a getter. If not we will ignore it. During this getter call, if the value is not present in our data we will create it and update the object. If the value contained by the getter is an original unwrapped class, we will replace it with a wraped version. Finally we will return the wrapped instance. Edit I updated this to not inject wrapped instances into the real Data objects. This will be less performant if the object is accessed mutliple times this way
public class ProxyUtil {
public static <T> T withLazyDefaults(final T data) {
final MethodInterceptor interceptor = (object, method, args, proxy) -> {
if (method.getName().startsWith("get")) {
final Class<?> returnType = method.getReturnType();
Object response = method.invoke(data, args);
if (response == null) {
response = returnType.newInstance();
data.getClass()
.getDeclaredMethod(
method.getName().replaceFirst("get", "set"),
returnType)
.invoke(data, response);
}
if (!returnType.isPrimitive()) {
response = withLazyDefaults(response);
}
return response;
}
return method.invoke(data, args);
};
...
The rest of this method involves using CGLIB and Objenisis Helper to construct the wrapper instance. CGLib will allow you to proxy both classes and interfaces and ObjenesisHelper will allow you to construct an instance of a class without having to invoke a constructor. See here for a CGLib example and here for a ObjenesisHelper example.
...
final Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(data.getClass());
final Set<Class<?>> interfaces = new LinkedHashSet<>();
if (data.getClass().isInterface()) {
interfaces.add(data.getClass());
}
interfaces.addAll(Arrays.asList(data.getClass().getInterfaces()));
enhancer.setInterfaces(interfaces.toArray(new Class[interfaces.size()]));
enhancer.setCallbackType(interceptor.getClass());
final Class<?> proxyClass = enhancer.createClass();
Enhancer.registerStaticCallbacks(proxyClass, new Callback{interceptor});
return (T) ObjenesisHelper.newInstance(proxyClass);
}
}
Caveats
- This is not a thread safe operation.
- Reflection will slow down your code.
- Better error handling needs to added for the reflection calls.
- If a class does not have a no-arg constructor this will not work.
- Does not account for inheritance of data classes
- This could be best effort by checking for a no-arg ctor/setter first.
add a comment |
I ended up using a combination of functional and reflection and tried to make the interface similar to Java's Optional. Here is an example of how I would write foo.getX().getY().setZ(val);
MutableGetter.of(foo).map(Foo::getX).map(x::getY).get().setZ(val);
This is the code (It's still WIP).
I used reflection to avoid having to pass the setter and constructor
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.ProxyFactory;
import lombok.Getter;
import lombok.NonNull;
public class MutableGetter<T>
{
private T object;
private MutableGetter(T object)
{
this.object = object;
}
public static <T> MutableGetter<T> of(@NonNull T object)
{
return new MutableGetter<>(object);
}
public <U> MutableGetter<U> map(Function<T, U> getter)
{
Method getterMethod = getGetterMethod(object.getClass(), getter);
BiConsumer<T, U> setter = getSetter(getterMethod);
Supplier<U> defaultValue = getDefaultValue(getterMethod);
U nextObject = getter.apply(object);
if (nextObject == null) {
nextObject = defaultValue.get();
setter.accept(object, nextObject);
}
return new MutableGetter<>(nextObject);
}
public T get()
{
return object;
}
private static <U> Supplier<U> getDefaultValue(Method getterMethod)
{
return () -> {
try {
Constructor<?> constructor = getterMethod.getReturnType().getConstructor();
constructor.setAccessible(true);
return (U) constructor.newInstance();
} catch (Exception e) {
throw new IllegalStateException(e);
}
};
}
private static <T, U> BiConsumer<T,U> getSetter(Method getterMethod)
{
return (obj, arg) -> {
Method setterMethod = getSetterFromGetter(getterMethod);
setterMethod.setAccessible(true);
try {
setterMethod.invoke(obj, arg);
} catch (Exception e) {
throw new IllegalStateException(e);
}
};
}
private static Method getSetterFromGetter(Method getter)
{
if (!getter.getName().startsWith("get")) {
throw new IllegalStateException("The getter method must start with 'get'");
}
String setterName = getter.getName().replaceFirst("get", "set");
Method methods = getter.getDeclaringClass().getMethods();
for (Method method: methods) {
if (method.getName().equals(setterName)) {
return method;
}
}
throw new IllegalStateException(String.format("Couldn't find setter in class %s with name %s", getter.getDeclaringClass(), setterName));
}
private static <T, U> Method getGetterMethod(Class<?> clazz, Function<T, U> getter)
{
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setSuperclass(clazz);
MethodRecorder methodRecorder = new MethodRecorder();
T proxy;
try {
proxy = (T) proxyFactory.create(new Class<?>[0], new Object[0], methodRecorder);
} catch (Exception e) {
throw new IllegalStateException(e);
}
getter.apply(proxy);
return methodRecorder.getLastInvokedMethod();
}
private static class MethodRecorder implements MethodHandler
{
@Getter
private Method lastInvokedMethod;
@Override
public Object invoke(Object self, Method thisMethod, Method proceed, Object args)
{
this.lastInvokedMethod = thisMethod;
return null; // the result is ignored
}
}
}
Let me know if you have any suggestions
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53315143%2fsafely-call-setter-after-getter-chain-eg-foo-getx-gety-setz%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
4 Answers
4
active
oldest
votes
4 Answers
4
active
oldest
votes
active
oldest
votes
active
oldest
votes
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.
2
Note that nothing here assumes the default value for missing objects is what the no-args constructor returns (or that such a constructor exists). You can easily plug in something other thanX::new
into your accessor definitions.
– Thilo
Nov 15 '18 at 13:42
@Thilo Yes, that is an advantage of this approach. Will add a note.
– Federico Peralta Schaffner
Nov 15 '18 at 14:18
I ended up using an approach that was similar to yours. However, I used reflection to avoid having to pass the setter and the default values. See my Answer for more info.
– Display Name
Nov 17 '18 at 6:21
add a comment |
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.
2
Note that nothing here assumes the default value for missing objects is what the no-args constructor returns (or that such a constructor exists). You can easily plug in something other thanX::new
into your accessor definitions.
– Thilo
Nov 15 '18 at 13:42
@Thilo Yes, that is an advantage of this approach. Will add a note.
– Federico Peralta Schaffner
Nov 15 '18 at 14:18
I ended up using an approach that was similar to yours. However, I used reflection to avoid having to pass the setter and the default values. See my Answer for more info.
– Display Name
Nov 17 '18 at 6:21
add a comment |
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.
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.
edited Nov 15 '18 at 14:20
answered Nov 15 '18 at 13:26
Federico Peralta SchaffnerFederico Peralta Schaffner
23.4k43677
23.4k43677
2
Note that nothing here assumes the default value for missing objects is what the no-args constructor returns (or that such a constructor exists). You can easily plug in something other thanX::new
into your accessor definitions.
– Thilo
Nov 15 '18 at 13:42
@Thilo Yes, that is an advantage of this approach. Will add a note.
– Federico Peralta Schaffner
Nov 15 '18 at 14:18
I ended up using an approach that was similar to yours. However, I used reflection to avoid having to pass the setter and the default values. See my Answer for more info.
– Display Name
Nov 17 '18 at 6:21
add a comment |
2
Note that nothing here assumes the default value for missing objects is what the no-args constructor returns (or that such a constructor exists). You can easily plug in something other thanX::new
into your accessor definitions.
– Thilo
Nov 15 '18 at 13:42
@Thilo Yes, that is an advantage of this approach. Will add a note.
– Federico Peralta Schaffner
Nov 15 '18 at 14:18
I ended up using an approach that was similar to yours. However, I used reflection to avoid having to pass the setter and the default values. See my Answer for more info.
– Display Name
Nov 17 '18 at 6:21
2
2
Note that nothing here assumes the default value for missing objects is what the no-args constructor returns (or that such a constructor exists). You can easily plug in something other than
X::new
into your accessor definitions.– Thilo
Nov 15 '18 at 13:42
Note that nothing here assumes the default value for missing objects is what the no-args constructor returns (or that such a constructor exists). You can easily plug in something other than
X::new
into your accessor definitions.– Thilo
Nov 15 '18 at 13:42
@Thilo Yes, that is an advantage of this approach. Will add a note.
– Federico Peralta Schaffner
Nov 15 '18 at 14:18
@Thilo Yes, that is an advantage of this approach. Will add a note.
– Federico Peralta Schaffner
Nov 15 '18 at 14:18
I ended up using an approach that was similar to yours. However, I used reflection to avoid having to pass the setter and the default values. See my Answer for more info.
– Display Name
Nov 17 '18 at 6:21
I ended up using an approach that was similar to yours. However, I used reflection to avoid having to pass the setter and the default values. See my Answer for more info.
– Display Name
Nov 17 '18 at 6:21
add a comment |
TL;DR: Don't try to force functional Java where there clearly is no place for it.
The only way you can do this functionally in Java 8 without modifying any of the classes is using Optional
s and their .orElse()
method. It gets really long really quick, but it's the only way that actually makes sense using functional if you want to do it in one line only.
Optional.ofNullable(foo.getX()).orElseGet(() -> { foo.setX(new X()); return foo.getX(); }).setY(...);
If foo.setX()
also returns the setted value it can be simplified as:
Optional.ofNullable(foo.getX()).orElseGet(() -> foo.setX(new X())).setY(...);
This is the only generic and functional way of doing it that I can think of. Stated the above, you can clearly see that this becomes huge and ugly even for just a chain of two getters, so I wouldn't advise it. I would definitely suggest you to use the classic multi-statement approach if you have to chain more than one call.
Another option, even thought not really that functional, is to use the tristate operator, still only if the setter returns the setted value:
(foo.getX() == null ? foo.setX(new X()) : foo.getX()).setY(...);
This has the probably unwanted side effect of calling the getter twice if the element is found, which you may not like, but could be possibly ignored if the getter caches the value somehow.
Yep, that becomes quite unwieldy quite fast. Thanks for the suggestion though!
– Display Name
Nov 15 '18 at 9:25
@DisplayName I added another option.
– Marco Bonelli
Nov 15 '18 at 9:29
add a comment |
TL;DR: Don't try to force functional Java where there clearly is no place for it.
The only way you can do this functionally in Java 8 without modifying any of the classes is using Optional
s and their .orElse()
method. It gets really long really quick, but it's the only way that actually makes sense using functional if you want to do it in one line only.
Optional.ofNullable(foo.getX()).orElseGet(() -> { foo.setX(new X()); return foo.getX(); }).setY(...);
If foo.setX()
also returns the setted value it can be simplified as:
Optional.ofNullable(foo.getX()).orElseGet(() -> foo.setX(new X())).setY(...);
This is the only generic and functional way of doing it that I can think of. Stated the above, you can clearly see that this becomes huge and ugly even for just a chain of two getters, so I wouldn't advise it. I would definitely suggest you to use the classic multi-statement approach if you have to chain more than one call.
Another option, even thought not really that functional, is to use the tristate operator, still only if the setter returns the setted value:
(foo.getX() == null ? foo.setX(new X()) : foo.getX()).setY(...);
This has the probably unwanted side effect of calling the getter twice if the element is found, which you may not like, but could be possibly ignored if the getter caches the value somehow.
Yep, that becomes quite unwieldy quite fast. Thanks for the suggestion though!
– Display Name
Nov 15 '18 at 9:25
@DisplayName I added another option.
– Marco Bonelli
Nov 15 '18 at 9:29
add a comment |
TL;DR: Don't try to force functional Java where there clearly is no place for it.
The only way you can do this functionally in Java 8 without modifying any of the classes is using Optional
s and their .orElse()
method. It gets really long really quick, but it's the only way that actually makes sense using functional if you want to do it in one line only.
Optional.ofNullable(foo.getX()).orElseGet(() -> { foo.setX(new X()); return foo.getX(); }).setY(...);
If foo.setX()
also returns the setted value it can be simplified as:
Optional.ofNullable(foo.getX()).orElseGet(() -> foo.setX(new X())).setY(...);
This is the only generic and functional way of doing it that I can think of. Stated the above, you can clearly see that this becomes huge and ugly even for just a chain of two getters, so I wouldn't advise it. I would definitely suggest you to use the classic multi-statement approach if you have to chain more than one call.
Another option, even thought not really that functional, is to use the tristate operator, still only if the setter returns the setted value:
(foo.getX() == null ? foo.setX(new X()) : foo.getX()).setY(...);
This has the probably unwanted side effect of calling the getter twice if the element is found, which you may not like, but could be possibly ignored if the getter caches the value somehow.
TL;DR: Don't try to force functional Java where there clearly is no place for it.
The only way you can do this functionally in Java 8 without modifying any of the classes is using Optional
s and their .orElse()
method. It gets really long really quick, but it's the only way that actually makes sense using functional if you want to do it in one line only.
Optional.ofNullable(foo.getX()).orElseGet(() -> { foo.setX(new X()); return foo.getX(); }).setY(...);
If foo.setX()
also returns the setted value it can be simplified as:
Optional.ofNullable(foo.getX()).orElseGet(() -> foo.setX(new X())).setY(...);
This is the only generic and functional way of doing it that I can think of. Stated the above, you can clearly see that this becomes huge and ugly even for just a chain of two getters, so I wouldn't advise it. I would definitely suggest you to use the classic multi-statement approach if you have to chain more than one call.
Another option, even thought not really that functional, is to use the tristate operator, still only if the setter returns the setted value:
(foo.getX() == null ? foo.setX(new X()) : foo.getX()).setY(...);
This has the probably unwanted side effect of calling the getter twice if the element is found, which you may not like, but could be possibly ignored if the getter caches the value somehow.
edited Nov 15 '18 at 13:08
answered Nov 15 '18 at 9:19
Marco BonelliMarco Bonelli
23.5k116373
23.5k116373
Yep, that becomes quite unwieldy quite fast. Thanks for the suggestion though!
– Display Name
Nov 15 '18 at 9:25
@DisplayName I added another option.
– Marco Bonelli
Nov 15 '18 at 9:29
add a comment |
Yep, that becomes quite unwieldy quite fast. Thanks for the suggestion though!
– Display Name
Nov 15 '18 at 9:25
@DisplayName I added another option.
– Marco Bonelli
Nov 15 '18 at 9:29
Yep, that becomes quite unwieldy quite fast. Thanks for the suggestion though!
– Display Name
Nov 15 '18 at 9:25
Yep, that becomes quite unwieldy quite fast. Thanks for the suggestion though!
– Display Name
Nov 15 '18 at 9:25
@DisplayName I added another option.
– Marco Bonelli
Nov 15 '18 at 9:29
@DisplayName I added another option.
– Marco Bonelli
Nov 15 '18 at 9:29
add a comment |
To start off I just want to mention that this probably isn't the best solution and I'm sure there are ways to optimize this. That said, I wanted to try my hand at CGLIB and ObjenesisHelper again.
Using CGLIB and ObjenesisHelper we can wrap the data object in a proxy which will intercept the get
methods. Using this interceptor we can add the logic you described in your post. Lets start off by assume these are our data types (using lombok for brevity).
@Data class W { private X x; }
@Data class X { private Y y; }
@Data class Y { private Z z; }
@Data class Z { private int alpha; }
Our final solution can be used like the following:
public static void main(String args) {
final W w = ProxyUtil.withLazyDefaults(new W());
System.out.println(w.getX().getY().getZ().getAlpha());
}
Implementation
Currently, if we try to invoke new W().getX().getY().getZ().getAlpha()
we will get a NullPointerException
when invoking getY()
because getX()
returned null. Even if we manage to produce a default X
value, we will still need a default Y
value to not get a null pointer on getZ()
and getAlpha()
and so forth. The proxy we create needs to be generic and be able to wrap its sub components recursively.
Okay, so lets start. The first thing we need to do is create a MethodInterceptor
. Whenever any call hits our proxy instance it will perform the logic of our MethodInterceptor
. We need to first determine if the method called is a getter. If not we will ignore it. During this getter call, if the value is not present in our data we will create it and update the object. If the value contained by the getter is an original unwrapped class, we will replace it with a wraped version. Finally we will return the wrapped instance. Edit I updated this to not inject wrapped instances into the real Data objects. This will be less performant if the object is accessed mutliple times this way
public class ProxyUtil {
public static <T> T withLazyDefaults(final T data) {
final MethodInterceptor interceptor = (object, method, args, proxy) -> {
if (method.getName().startsWith("get")) {
final Class<?> returnType = method.getReturnType();
Object response = method.invoke(data, args);
if (response == null) {
response = returnType.newInstance();
data.getClass()
.getDeclaredMethod(
method.getName().replaceFirst("get", "set"),
returnType)
.invoke(data, response);
}
if (!returnType.isPrimitive()) {
response = withLazyDefaults(response);
}
return response;
}
return method.invoke(data, args);
};
...
The rest of this method involves using CGLIB and Objenisis Helper to construct the wrapper instance. CGLib will allow you to proxy both classes and interfaces and ObjenesisHelper will allow you to construct an instance of a class without having to invoke a constructor. See here for a CGLib example and here for a ObjenesisHelper example.
...
final Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(data.getClass());
final Set<Class<?>> interfaces = new LinkedHashSet<>();
if (data.getClass().isInterface()) {
interfaces.add(data.getClass());
}
interfaces.addAll(Arrays.asList(data.getClass().getInterfaces()));
enhancer.setInterfaces(interfaces.toArray(new Class[interfaces.size()]));
enhancer.setCallbackType(interceptor.getClass());
final Class<?> proxyClass = enhancer.createClass();
Enhancer.registerStaticCallbacks(proxyClass, new Callback{interceptor});
return (T) ObjenesisHelper.newInstance(proxyClass);
}
}
Caveats
- This is not a thread safe operation.
- Reflection will slow down your code.
- Better error handling needs to added for the reflection calls.
- If a class does not have a no-arg constructor this will not work.
- Does not account for inheritance of data classes
- This could be best effort by checking for a no-arg ctor/setter first.
add a comment |
To start off I just want to mention that this probably isn't the best solution and I'm sure there are ways to optimize this. That said, I wanted to try my hand at CGLIB and ObjenesisHelper again.
Using CGLIB and ObjenesisHelper we can wrap the data object in a proxy which will intercept the get
methods. Using this interceptor we can add the logic you described in your post. Lets start off by assume these are our data types (using lombok for brevity).
@Data class W { private X x; }
@Data class X { private Y y; }
@Data class Y { private Z z; }
@Data class Z { private int alpha; }
Our final solution can be used like the following:
public static void main(String args) {
final W w = ProxyUtil.withLazyDefaults(new W());
System.out.println(w.getX().getY().getZ().getAlpha());
}
Implementation
Currently, if we try to invoke new W().getX().getY().getZ().getAlpha()
we will get a NullPointerException
when invoking getY()
because getX()
returned null. Even if we manage to produce a default X
value, we will still need a default Y
value to not get a null pointer on getZ()
and getAlpha()
and so forth. The proxy we create needs to be generic and be able to wrap its sub components recursively.
Okay, so lets start. The first thing we need to do is create a MethodInterceptor
. Whenever any call hits our proxy instance it will perform the logic of our MethodInterceptor
. We need to first determine if the method called is a getter. If not we will ignore it. During this getter call, if the value is not present in our data we will create it and update the object. If the value contained by the getter is an original unwrapped class, we will replace it with a wraped version. Finally we will return the wrapped instance. Edit I updated this to not inject wrapped instances into the real Data objects. This will be less performant if the object is accessed mutliple times this way
public class ProxyUtil {
public static <T> T withLazyDefaults(final T data) {
final MethodInterceptor interceptor = (object, method, args, proxy) -> {
if (method.getName().startsWith("get")) {
final Class<?> returnType = method.getReturnType();
Object response = method.invoke(data, args);
if (response == null) {
response = returnType.newInstance();
data.getClass()
.getDeclaredMethod(
method.getName().replaceFirst("get", "set"),
returnType)
.invoke(data, response);
}
if (!returnType.isPrimitive()) {
response = withLazyDefaults(response);
}
return response;
}
return method.invoke(data, args);
};
...
The rest of this method involves using CGLIB and Objenisis Helper to construct the wrapper instance. CGLib will allow you to proxy both classes and interfaces and ObjenesisHelper will allow you to construct an instance of a class without having to invoke a constructor. See here for a CGLib example and here for a ObjenesisHelper example.
...
final Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(data.getClass());
final Set<Class<?>> interfaces = new LinkedHashSet<>();
if (data.getClass().isInterface()) {
interfaces.add(data.getClass());
}
interfaces.addAll(Arrays.asList(data.getClass().getInterfaces()));
enhancer.setInterfaces(interfaces.toArray(new Class[interfaces.size()]));
enhancer.setCallbackType(interceptor.getClass());
final Class<?> proxyClass = enhancer.createClass();
Enhancer.registerStaticCallbacks(proxyClass, new Callback{interceptor});
return (T) ObjenesisHelper.newInstance(proxyClass);
}
}
Caveats
- This is not a thread safe operation.
- Reflection will slow down your code.
- Better error handling needs to added for the reflection calls.
- If a class does not have a no-arg constructor this will not work.
- Does not account for inheritance of data classes
- This could be best effort by checking for a no-arg ctor/setter first.
add a comment |
To start off I just want to mention that this probably isn't the best solution and I'm sure there are ways to optimize this. That said, I wanted to try my hand at CGLIB and ObjenesisHelper again.
Using CGLIB and ObjenesisHelper we can wrap the data object in a proxy which will intercept the get
methods. Using this interceptor we can add the logic you described in your post. Lets start off by assume these are our data types (using lombok for brevity).
@Data class W { private X x; }
@Data class X { private Y y; }
@Data class Y { private Z z; }
@Data class Z { private int alpha; }
Our final solution can be used like the following:
public static void main(String args) {
final W w = ProxyUtil.withLazyDefaults(new W());
System.out.println(w.getX().getY().getZ().getAlpha());
}
Implementation
Currently, if we try to invoke new W().getX().getY().getZ().getAlpha()
we will get a NullPointerException
when invoking getY()
because getX()
returned null. Even if we manage to produce a default X
value, we will still need a default Y
value to not get a null pointer on getZ()
and getAlpha()
and so forth. The proxy we create needs to be generic and be able to wrap its sub components recursively.
Okay, so lets start. The first thing we need to do is create a MethodInterceptor
. Whenever any call hits our proxy instance it will perform the logic of our MethodInterceptor
. We need to first determine if the method called is a getter. If not we will ignore it. During this getter call, if the value is not present in our data we will create it and update the object. If the value contained by the getter is an original unwrapped class, we will replace it with a wraped version. Finally we will return the wrapped instance. Edit I updated this to not inject wrapped instances into the real Data objects. This will be less performant if the object is accessed mutliple times this way
public class ProxyUtil {
public static <T> T withLazyDefaults(final T data) {
final MethodInterceptor interceptor = (object, method, args, proxy) -> {
if (method.getName().startsWith("get")) {
final Class<?> returnType = method.getReturnType();
Object response = method.invoke(data, args);
if (response == null) {
response = returnType.newInstance();
data.getClass()
.getDeclaredMethod(
method.getName().replaceFirst("get", "set"),
returnType)
.invoke(data, response);
}
if (!returnType.isPrimitive()) {
response = withLazyDefaults(response);
}
return response;
}
return method.invoke(data, args);
};
...
The rest of this method involves using CGLIB and Objenisis Helper to construct the wrapper instance. CGLib will allow you to proxy both classes and interfaces and ObjenesisHelper will allow you to construct an instance of a class without having to invoke a constructor. See here for a CGLib example and here for a ObjenesisHelper example.
...
final Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(data.getClass());
final Set<Class<?>> interfaces = new LinkedHashSet<>();
if (data.getClass().isInterface()) {
interfaces.add(data.getClass());
}
interfaces.addAll(Arrays.asList(data.getClass().getInterfaces()));
enhancer.setInterfaces(interfaces.toArray(new Class[interfaces.size()]));
enhancer.setCallbackType(interceptor.getClass());
final Class<?> proxyClass = enhancer.createClass();
Enhancer.registerStaticCallbacks(proxyClass, new Callback{interceptor});
return (T) ObjenesisHelper.newInstance(proxyClass);
}
}
Caveats
- This is not a thread safe operation.
- Reflection will slow down your code.
- Better error handling needs to added for the reflection calls.
- If a class does not have a no-arg constructor this will not work.
- Does not account for inheritance of data classes
- This could be best effort by checking for a no-arg ctor/setter first.
To start off I just want to mention that this probably isn't the best solution and I'm sure there are ways to optimize this. That said, I wanted to try my hand at CGLIB and ObjenesisHelper again.
Using CGLIB and ObjenesisHelper we can wrap the data object in a proxy which will intercept the get
methods. Using this interceptor we can add the logic you described in your post. Lets start off by assume these are our data types (using lombok for brevity).
@Data class W { private X x; }
@Data class X { private Y y; }
@Data class Y { private Z z; }
@Data class Z { private int alpha; }
Our final solution can be used like the following:
public static void main(String args) {
final W w = ProxyUtil.withLazyDefaults(new W());
System.out.println(w.getX().getY().getZ().getAlpha());
}
Implementation
Currently, if we try to invoke new W().getX().getY().getZ().getAlpha()
we will get a NullPointerException
when invoking getY()
because getX()
returned null. Even if we manage to produce a default X
value, we will still need a default Y
value to not get a null pointer on getZ()
and getAlpha()
and so forth. The proxy we create needs to be generic and be able to wrap its sub components recursively.
Okay, so lets start. The first thing we need to do is create a MethodInterceptor
. Whenever any call hits our proxy instance it will perform the logic of our MethodInterceptor
. We need to first determine if the method called is a getter. If not we will ignore it. During this getter call, if the value is not present in our data we will create it and update the object. If the value contained by the getter is an original unwrapped class, we will replace it with a wraped version. Finally we will return the wrapped instance. Edit I updated this to not inject wrapped instances into the real Data objects. This will be less performant if the object is accessed mutliple times this way
public class ProxyUtil {
public static <T> T withLazyDefaults(final T data) {
final MethodInterceptor interceptor = (object, method, args, proxy) -> {
if (method.getName().startsWith("get")) {
final Class<?> returnType = method.getReturnType();
Object response = method.invoke(data, args);
if (response == null) {
response = returnType.newInstance();
data.getClass()
.getDeclaredMethod(
method.getName().replaceFirst("get", "set"),
returnType)
.invoke(data, response);
}
if (!returnType.isPrimitive()) {
response = withLazyDefaults(response);
}
return response;
}
return method.invoke(data, args);
};
...
The rest of this method involves using CGLIB and Objenisis Helper to construct the wrapper instance. CGLib will allow you to proxy both classes and interfaces and ObjenesisHelper will allow you to construct an instance of a class without having to invoke a constructor. See here for a CGLib example and here for a ObjenesisHelper example.
...
final Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(data.getClass());
final Set<Class<?>> interfaces = new LinkedHashSet<>();
if (data.getClass().isInterface()) {
interfaces.add(data.getClass());
}
interfaces.addAll(Arrays.asList(data.getClass().getInterfaces()));
enhancer.setInterfaces(interfaces.toArray(new Class[interfaces.size()]));
enhancer.setCallbackType(interceptor.getClass());
final Class<?> proxyClass = enhancer.createClass();
Enhancer.registerStaticCallbacks(proxyClass, new Callback{interceptor});
return (T) ObjenesisHelper.newInstance(proxyClass);
}
}
Caveats
- This is not a thread safe operation.
- Reflection will slow down your code.
- Better error handling needs to added for the reflection calls.
- If a class does not have a no-arg constructor this will not work.
- Does not account for inheritance of data classes
- This could be best effort by checking for a no-arg ctor/setter first.
edited Nov 15 '18 at 12:18
answered Nov 15 '18 at 9:43
flakesflakes
6,79812051
6,79812051
add a comment |
add a comment |
I ended up using a combination of functional and reflection and tried to make the interface similar to Java's Optional. Here is an example of how I would write foo.getX().getY().setZ(val);
MutableGetter.of(foo).map(Foo::getX).map(x::getY).get().setZ(val);
This is the code (It's still WIP).
I used reflection to avoid having to pass the setter and constructor
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.ProxyFactory;
import lombok.Getter;
import lombok.NonNull;
public class MutableGetter<T>
{
private T object;
private MutableGetter(T object)
{
this.object = object;
}
public static <T> MutableGetter<T> of(@NonNull T object)
{
return new MutableGetter<>(object);
}
public <U> MutableGetter<U> map(Function<T, U> getter)
{
Method getterMethod = getGetterMethod(object.getClass(), getter);
BiConsumer<T, U> setter = getSetter(getterMethod);
Supplier<U> defaultValue = getDefaultValue(getterMethod);
U nextObject = getter.apply(object);
if (nextObject == null) {
nextObject = defaultValue.get();
setter.accept(object, nextObject);
}
return new MutableGetter<>(nextObject);
}
public T get()
{
return object;
}
private static <U> Supplier<U> getDefaultValue(Method getterMethod)
{
return () -> {
try {
Constructor<?> constructor = getterMethod.getReturnType().getConstructor();
constructor.setAccessible(true);
return (U) constructor.newInstance();
} catch (Exception e) {
throw new IllegalStateException(e);
}
};
}
private static <T, U> BiConsumer<T,U> getSetter(Method getterMethod)
{
return (obj, arg) -> {
Method setterMethod = getSetterFromGetter(getterMethod);
setterMethod.setAccessible(true);
try {
setterMethod.invoke(obj, arg);
} catch (Exception e) {
throw new IllegalStateException(e);
}
};
}
private static Method getSetterFromGetter(Method getter)
{
if (!getter.getName().startsWith("get")) {
throw new IllegalStateException("The getter method must start with 'get'");
}
String setterName = getter.getName().replaceFirst("get", "set");
Method methods = getter.getDeclaringClass().getMethods();
for (Method method: methods) {
if (method.getName().equals(setterName)) {
return method;
}
}
throw new IllegalStateException(String.format("Couldn't find setter in class %s with name %s", getter.getDeclaringClass(), setterName));
}
private static <T, U> Method getGetterMethod(Class<?> clazz, Function<T, U> getter)
{
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setSuperclass(clazz);
MethodRecorder methodRecorder = new MethodRecorder();
T proxy;
try {
proxy = (T) proxyFactory.create(new Class<?>[0], new Object[0], methodRecorder);
} catch (Exception e) {
throw new IllegalStateException(e);
}
getter.apply(proxy);
return methodRecorder.getLastInvokedMethod();
}
private static class MethodRecorder implements MethodHandler
{
@Getter
private Method lastInvokedMethod;
@Override
public Object invoke(Object self, Method thisMethod, Method proceed, Object args)
{
this.lastInvokedMethod = thisMethod;
return null; // the result is ignored
}
}
}
Let me know if you have any suggestions
add a comment |
I ended up using a combination of functional and reflection and tried to make the interface similar to Java's Optional. Here is an example of how I would write foo.getX().getY().setZ(val);
MutableGetter.of(foo).map(Foo::getX).map(x::getY).get().setZ(val);
This is the code (It's still WIP).
I used reflection to avoid having to pass the setter and constructor
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.ProxyFactory;
import lombok.Getter;
import lombok.NonNull;
public class MutableGetter<T>
{
private T object;
private MutableGetter(T object)
{
this.object = object;
}
public static <T> MutableGetter<T> of(@NonNull T object)
{
return new MutableGetter<>(object);
}
public <U> MutableGetter<U> map(Function<T, U> getter)
{
Method getterMethod = getGetterMethod(object.getClass(), getter);
BiConsumer<T, U> setter = getSetter(getterMethod);
Supplier<U> defaultValue = getDefaultValue(getterMethod);
U nextObject = getter.apply(object);
if (nextObject == null) {
nextObject = defaultValue.get();
setter.accept(object, nextObject);
}
return new MutableGetter<>(nextObject);
}
public T get()
{
return object;
}
private static <U> Supplier<U> getDefaultValue(Method getterMethod)
{
return () -> {
try {
Constructor<?> constructor = getterMethod.getReturnType().getConstructor();
constructor.setAccessible(true);
return (U) constructor.newInstance();
} catch (Exception e) {
throw new IllegalStateException(e);
}
};
}
private static <T, U> BiConsumer<T,U> getSetter(Method getterMethod)
{
return (obj, arg) -> {
Method setterMethod = getSetterFromGetter(getterMethod);
setterMethod.setAccessible(true);
try {
setterMethod.invoke(obj, arg);
} catch (Exception e) {
throw new IllegalStateException(e);
}
};
}
private static Method getSetterFromGetter(Method getter)
{
if (!getter.getName().startsWith("get")) {
throw new IllegalStateException("The getter method must start with 'get'");
}
String setterName = getter.getName().replaceFirst("get", "set");
Method methods = getter.getDeclaringClass().getMethods();
for (Method method: methods) {
if (method.getName().equals(setterName)) {
return method;
}
}
throw new IllegalStateException(String.format("Couldn't find setter in class %s with name %s", getter.getDeclaringClass(), setterName));
}
private static <T, U> Method getGetterMethod(Class<?> clazz, Function<T, U> getter)
{
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setSuperclass(clazz);
MethodRecorder methodRecorder = new MethodRecorder();
T proxy;
try {
proxy = (T) proxyFactory.create(new Class<?>[0], new Object[0], methodRecorder);
} catch (Exception e) {
throw new IllegalStateException(e);
}
getter.apply(proxy);
return methodRecorder.getLastInvokedMethod();
}
private static class MethodRecorder implements MethodHandler
{
@Getter
private Method lastInvokedMethod;
@Override
public Object invoke(Object self, Method thisMethod, Method proceed, Object args)
{
this.lastInvokedMethod = thisMethod;
return null; // the result is ignored
}
}
}
Let me know if you have any suggestions
add a comment |
I ended up using a combination of functional and reflection and tried to make the interface similar to Java's Optional. Here is an example of how I would write foo.getX().getY().setZ(val);
MutableGetter.of(foo).map(Foo::getX).map(x::getY).get().setZ(val);
This is the code (It's still WIP).
I used reflection to avoid having to pass the setter and constructor
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.ProxyFactory;
import lombok.Getter;
import lombok.NonNull;
public class MutableGetter<T>
{
private T object;
private MutableGetter(T object)
{
this.object = object;
}
public static <T> MutableGetter<T> of(@NonNull T object)
{
return new MutableGetter<>(object);
}
public <U> MutableGetter<U> map(Function<T, U> getter)
{
Method getterMethod = getGetterMethod(object.getClass(), getter);
BiConsumer<T, U> setter = getSetter(getterMethod);
Supplier<U> defaultValue = getDefaultValue(getterMethod);
U nextObject = getter.apply(object);
if (nextObject == null) {
nextObject = defaultValue.get();
setter.accept(object, nextObject);
}
return new MutableGetter<>(nextObject);
}
public T get()
{
return object;
}
private static <U> Supplier<U> getDefaultValue(Method getterMethod)
{
return () -> {
try {
Constructor<?> constructor = getterMethod.getReturnType().getConstructor();
constructor.setAccessible(true);
return (U) constructor.newInstance();
} catch (Exception e) {
throw new IllegalStateException(e);
}
};
}
private static <T, U> BiConsumer<T,U> getSetter(Method getterMethod)
{
return (obj, arg) -> {
Method setterMethod = getSetterFromGetter(getterMethod);
setterMethod.setAccessible(true);
try {
setterMethod.invoke(obj, arg);
} catch (Exception e) {
throw new IllegalStateException(e);
}
};
}
private static Method getSetterFromGetter(Method getter)
{
if (!getter.getName().startsWith("get")) {
throw new IllegalStateException("The getter method must start with 'get'");
}
String setterName = getter.getName().replaceFirst("get", "set");
Method methods = getter.getDeclaringClass().getMethods();
for (Method method: methods) {
if (method.getName().equals(setterName)) {
return method;
}
}
throw new IllegalStateException(String.format("Couldn't find setter in class %s with name %s", getter.getDeclaringClass(), setterName));
}
private static <T, U> Method getGetterMethod(Class<?> clazz, Function<T, U> getter)
{
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setSuperclass(clazz);
MethodRecorder methodRecorder = new MethodRecorder();
T proxy;
try {
proxy = (T) proxyFactory.create(new Class<?>[0], new Object[0], methodRecorder);
} catch (Exception e) {
throw new IllegalStateException(e);
}
getter.apply(proxy);
return methodRecorder.getLastInvokedMethod();
}
private static class MethodRecorder implements MethodHandler
{
@Getter
private Method lastInvokedMethod;
@Override
public Object invoke(Object self, Method thisMethod, Method proceed, Object args)
{
this.lastInvokedMethod = thisMethod;
return null; // the result is ignored
}
}
}
Let me know if you have any suggestions
I ended up using a combination of functional and reflection and tried to make the interface similar to Java's Optional. Here is an example of how I would write foo.getX().getY().setZ(val);
MutableGetter.of(foo).map(Foo::getX).map(x::getY).get().setZ(val);
This is the code (It's still WIP).
I used reflection to avoid having to pass the setter and constructor
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.ProxyFactory;
import lombok.Getter;
import lombok.NonNull;
public class MutableGetter<T>
{
private T object;
private MutableGetter(T object)
{
this.object = object;
}
public static <T> MutableGetter<T> of(@NonNull T object)
{
return new MutableGetter<>(object);
}
public <U> MutableGetter<U> map(Function<T, U> getter)
{
Method getterMethod = getGetterMethod(object.getClass(), getter);
BiConsumer<T, U> setter = getSetter(getterMethod);
Supplier<U> defaultValue = getDefaultValue(getterMethod);
U nextObject = getter.apply(object);
if (nextObject == null) {
nextObject = defaultValue.get();
setter.accept(object, nextObject);
}
return new MutableGetter<>(nextObject);
}
public T get()
{
return object;
}
private static <U> Supplier<U> getDefaultValue(Method getterMethod)
{
return () -> {
try {
Constructor<?> constructor = getterMethod.getReturnType().getConstructor();
constructor.setAccessible(true);
return (U) constructor.newInstance();
} catch (Exception e) {
throw new IllegalStateException(e);
}
};
}
private static <T, U> BiConsumer<T,U> getSetter(Method getterMethod)
{
return (obj, arg) -> {
Method setterMethod = getSetterFromGetter(getterMethod);
setterMethod.setAccessible(true);
try {
setterMethod.invoke(obj, arg);
} catch (Exception e) {
throw new IllegalStateException(e);
}
};
}
private static Method getSetterFromGetter(Method getter)
{
if (!getter.getName().startsWith("get")) {
throw new IllegalStateException("The getter method must start with 'get'");
}
String setterName = getter.getName().replaceFirst("get", "set");
Method methods = getter.getDeclaringClass().getMethods();
for (Method method: methods) {
if (method.getName().equals(setterName)) {
return method;
}
}
throw new IllegalStateException(String.format("Couldn't find setter in class %s with name %s", getter.getDeclaringClass(), setterName));
}
private static <T, U> Method getGetterMethod(Class<?> clazz, Function<T, U> getter)
{
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setSuperclass(clazz);
MethodRecorder methodRecorder = new MethodRecorder();
T proxy;
try {
proxy = (T) proxyFactory.create(new Class<?>[0], new Object[0], methodRecorder);
} catch (Exception e) {
throw new IllegalStateException(e);
}
getter.apply(proxy);
return methodRecorder.getLastInvokedMethod();
}
private static class MethodRecorder implements MethodHandler
{
@Getter
private Method lastInvokedMethod;
@Override
public Object invoke(Object self, Method thisMethod, Method proceed, Object args)
{
this.lastInvokedMethod = thisMethod;
return null; // the result is ignored
}
}
}
Let me know if you have any suggestions
answered Nov 17 '18 at 6:19
Display NameDisplay Name
4082515
4082515
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53315143%2fsafely-call-setter-after-getter-chain-eg-foo-getx-gety-setz%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
So you don't want to touch the
Foo
,X
,Y
, ... classes, right? AOP is probably your best bet.– Robby Cornelissen
Nov 15 '18 at 8:30
1
The keyword to google here is probably "lenses".
– Thilo
Nov 15 '18 at 8:34
You can create a getter in class Foo in such a way that it returns X if not null else retruns a new object of X. X getX() { return x == null ? return new X() : x; }
– efex09
Nov 15 '18 at 8:51
@efex09 "I cannot modify any of the classes"
– Marco Bonelli
Nov 15 '18 at 8:59
1
@Thilo are you referring to Haskell's Lenses (eg stackoverflow.com/questions/8307370/functional-lenses)? I'm not familiar with Haskell so I'm not sure if that solves the problem
– Display Name
Nov 15 '18 at 9:27