Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix setter selection in presence of multiple setters #2386

Merged
merged 5 commits into from
Jul 6, 2017

Conversation

jjohannes
Copy link
Contributor

No description provided.

@eskatos
Copy link
Member

eskatos commented Jul 5, 2017

I'm still wondering if this can be applied to user types where there could be multiple setters for a given property, none taking Object.

writeablePropertyIfExists() is used directly only for setting properties on Project instances. Should be fine.

writeableProperty(), that uses the latter, is used for setting properties on AbstractOptions instances. AbstractOptions is public and may be extended by user types. This could be problematic.

@jjohannes
Copy link
Contributor Author

Could you elaborate why you think that this is still a problem?

The implementation now uses MethodUtils.getMatchingAccessibleMethod() (from Apache Commons Lang) to find the setter that fits best. There is no special handling of setters with Object type anymore.

@eskatos
Copy link
Member

eskatos commented Jul 5, 2017

I missed that. Awesome!

Copy link
Member

@eskatos eskatos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⭕️ It would be nice to add coverage for the case where multiple setters are available but none taking object

property.type == Iterable.class
}

def "can handle null as property type"() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

null behaviour feels underspecified here.

The test should make clear which setter gets called, specially when there's more than a possible null receiver (like with the multiValue property, for instance).

Another question that comes to mind: What's the effect of passing null to a property of a primitive type?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, it is not deterministic which setter gets called. All are equally correct. Depends again of the order the methods are returned by Class.getMethods() - see implementation of getMatchingAccessibleMethod() in commons lang. I think we need to accept this.

I added a test for the primitive type case. (It will not find a suitable setter as null cannot be assigned.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, getMatchingAccessibleMethod() does its best to do the right thing here.
It won't return the first match but the best match according to MemberUtils.compareParameterTypes().
Well, in some cases were the "distance between types" is equal for several setters then the order of Class.getMethods() will prevail, but it should not be that bad.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

if (!method.getName().equals(setterName) || PropertyAccessorType.of(method) != PropertyAccessorType.SETTER) {
continue;
}
Method method = MethodUtils.getMatchingAccessibleMethod(target, setterName, new Class<?>[]{valueType});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice 👍

Array formatting looks off.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checkstyle wanted it that way... :)

@@ -38,7 +38,8 @@ public void define(@Nullable Map<String, Object> args) {
return;
}
for (Map.Entry<String, Object> arg: args.entrySet()) {
JavaReflectionUtil.writeableProperty(getClass(), arg.getKey()).setValue(this, arg.getValue());
Object value = arg.getValue();
JavaReflectionUtil.writeableProperty(getClass(), arg.getKey(), value == null ? null : value.getClass()).setValue(this, value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd extract a method here:

setProperty(this, arg.getKey(), arg.getValue())

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@jjohannes
Copy link
Contributor Author

It would be nice to add coverage for the case where multiple setters are available but none taking object

@eskatos good idea. I added a test.

Copy link
Member

@eskatos eskatos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

⭕️ A test asserting we use the closest matching setter and not the most generic one would also be nice, see #2386 (comment)


then:
def e = thrown(NoSuchPropertyException)
e.message == "Could not find setter method for property 'myBooleanProperty' on class JavaTestSubject."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be confusing to read that message when you're sure there's a setter method just not one that accepts null.

How about mentioning the value was null? Something like:

Could not find setter method for property 'myBooleanProperty' accepting null value on class JavaTestSubject."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion. I changed it to your proposal.

@jjohannes
Copy link
Contributor Author

A test asserting we use the closest matching setter and not the most generic one would also be nice

I missed that one. I added another test now.

@jjohannes jjohannes added this to the 4.0.1 milestone Jul 6, 2017
@eskatos
Copy link
Member

eskatos commented Jul 6, 2017

I was more thinking about some thing like:

interface JavaSubjectType {
    CharSequence getMyProp();
    void setMyProp(CharSequence value);
    void setMyProp(Collection<?> value);
    void setMyProp(Object value);
}

And asserting that:

subject.setMyProp(Arrays.asList("foo", "bar"));
// goes to the `Collection` setter, not the `Object` one

subject.setMyProp("bazar");
// goes to the `CharSequence` setter, not the `Object` one

@jjohannes
Copy link
Contributor Author

@eskatos I added one more test :)

@jjohannes jjohannes merged commit 38e5dc0 into release_4.0.1 Jul 6, 2017
@jjohannes jjohannes deleted the jj/multipleSetters/select-setter-by-type branch July 6, 2017 14:48
@jjohannes jjohannes removed this from the 4.0.1 milestone Jul 6, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in:core DO NOT USE
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants