Skip to content

Add PropertyMapper.to(...) API designed for immutable instances #31323

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

Closed

Conversation

onobc
Copy link
Contributor

@onobc onobc commented Jun 9, 2022

Overview

This proposal adds an API to PropertyMapper.isInstance that returns a user-specified default instance when the mapped value has been filtered. The current behavior is to throw an exception when the mapped value has been filtered.

Motivation

Some target objects are immutable and return a new instance w/ the mapped value rather than updating the value on the target instance. Consider the following example:

class Target {

  private String foo;

  Target(String foo) {
     this.foo = foo;
  }
  
  Target updateFoo(String foo) {
    return new Target(foo);
  }
}

And suppose we have some config prop for "foo" and we want to update the target object's "foo" w/ that value. Typically we would use something like:

PropertyMapper.get().from(someProps::foo).to(someTarget::updateFoo);

However, Source.to() does not work in the immutable case because updateFoo returns the new updated instance. Ok, lets instead use Source.toInstance() such as:

someTarget =  PropertyMapper.get().from(someProps::foo).toInstance(someTarget::updateFoo);

This works fine when the value is not filtered. However, if we add some constraints on the mapper such as:

someTarget =  PropertyMapper.get().from(someProps::foo).whenNonNull().toInstance(someTarget::updateFoo);

throws a NoSuchElementException when someProps::foo is null. This results in having to test the filter predicate manually prior to calling toInstance.

Proposed Solution

Allow the caller to pass in a default instance to use when the value is filtered (rather than throw exception). Something like this:

someTarget =  PropertyMapper.get().from(someProps::foo).whenNonNull().toInstance(someTarget::updateFoo, someTarget);

You can see where I have worked around this in https://github.com/spring-projects/spring-boot/pull/31258/files#diff-7f2cc68fbde121f72bcff98a44dc29738be96efb911ffc54b80b6a6dd334b5ccR60

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Jun 9, 2022
@philwebb
Copy link
Member

philwebb commented Jun 9, 2022

I wonder if we should have something like toOptionalInstance(factory) that returns an Optional result? That way we get all the orElse methods for free:

someTarget =  PropertyMapper.get().from(someProps::foo).whenNonNull().toOptionalInstance(someTarget::updateFoo).orElse(someTarget);

@philwebb philwebb added type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged labels Jun 9, 2022
@philwebb philwebb added this to the 3.0.x milestone Jun 9, 2022
@philwebb philwebb added the for: merge-with-amendments Needs some changes when we merge label Jun 9, 2022
@onobc
Copy link
Contributor Author

onobc commented Jun 10, 2022

I wonder if we should have something like toOptionalInstance(factory) that returns an Optional result? That way we get all the orElse methods for free:

someTarget =  PropertyMapper.get().from(someProps::foo).whenNonNull().toOptionalInstance(someTarget::updateFoo).orElse(someTarget);

Absolutely! This gets us out of the "picking what to do in the case it is filtered value" business. No need to pass in default etc.. I will amend the PR shortly.

@onobc onobc changed the title Add PropertyMapper.toInstance API that accepts a default instance Add PropertyMapper toInstance that tolerates filtered values Jun 10, 2022
@snicoll snicoll added the for: team-attention An issue we'd like other members of the team to review label Jul 13, 2022
@onobc onobc force-pushed the cbono-propertymapper-defaults branch from 7343a18 to 6a9ed80 Compare July 19, 2022 00:09
@philwebb philwebb removed for: team-attention An issue we'd like other members of the team to review for: merge-with-amendments Needs some changes when we merge labels Jul 19, 2022
philwebb added a commit that referenced this pull request Jul 19, 2022
Add a new `to` method on `PropertyMapper` designed to work with
immutable instances. The new method takes an existing instance and
a mapping `BiFunction`.

See gh-31323

Co-authored-by: Phillip Webb <pwebb@vmware.com>
@philwebb philwebb modified the milestones: 3.0.x, 3.0.0-M4 Jul 19, 2022
@philwebb philwebb closed this in df381c5 Jul 19, 2022
@philwebb
Copy link
Member

philwebb commented Jul 19, 2022

Thanks for all the reviews. After a bit more discussion with @snicoll we decided to circle back to almost the original proposal. Rather than adding a toInstance method, I've added another to method and made it accept a BiFunction. This means you can write the immutable mapper code like this:

SenderOptions<K, V> senderOptions = SenderOptions.create(this.kafkaProperties.buildReactorProducerProperties());
KafkaProperties.Reactor.Sender senderProperties = this.kafkaProperties.getReactor().getSender();
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
senderOptions = map.from(senderProperties::getCloseTimeout).to(senderOptions, SenderOptions::closeTimeout);
senderOptions = map.from(senderProperties::getMaxInFlight).to(senderOptions, SenderOptions::maxInFlight);
senderOptions = map.from(senderProperties::isStopOnError).to(senderOptions, SenderOptions::stopOnError);
return senderOptions;

@snicoll snicoll changed the title Add PropertyMapper toInstance that tolerates filtered values Add PropertyMapper.to(...) API designed for immutable instances Jul 19, 2022
@philwebb philwebb self-assigned this Jul 19, 2022
philwebb added a commit that referenced this pull request Jul 19, 2022
@onobc onobc deleted the cbono-propertymapper-defaults branch May 28, 2024 21:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: enhancement A general enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants