Tools for Java template-based String construction - java

I am trying to refactor some legacy code. The task here is to construct lengthy messages/strings based on some pre-defined template that looks like this:
field1,8,String
filed2,5,Integer
field3,12,String
......
Then I am handed a java object that has all those fields. What needs to be done here is simply get data from the object fields and use them to construct a long message/string based on the template. Some of these fields also may be converted based on some simple rules. For example:
abc => a
def => d
ghi => g
As a result we need to check the values of these fields from time to time. Also there are rules about padding (mostly adding empty space to the right). So a conostructed message/string may look like this:
uater 4751 enterprise ......
Currently we are just using brutal force to do this job. First we feed the template into an ArrayList, each element is a line, eg, "field1,8,String". During the actual message construction, we loop through this ArrayList, and then fill the data into a StringBuffer. Here is some sample snippet
StringBuffer message = new StringBuffer(1000);
for (String field : templateFields) {
String[] fieldArray = field.split(Constants.SEPARATOR);
if (fieldArray[0].equalsIgnoreCase(Constants.WORKFLOW)) {
message.append(rightPad(object.getFieldOne(), Integer.parseInt(fieldArray[1])));
} else if (fieldArray[0].equalsIgnoreCase(Constants.WORKVOLUME)) {
message.append(rightPad(object.getFieldTwo(), Integer.parseInt(fieldArray[1]));
} else if (fieldArray[0].equalsIgnoreCase(Constants.WORKTYPE)) {
if (object.getFieldThree().equalsIgnoreCase("abc")) {
message.append(rightPad("a", Integer.parseInt(fieldArray[1]));
} else if (object.getFieldThree().equalsIgnoreCase("def")) {
message.append(rightPad("d", Integer.parseInt(fieldArray[1]));
} else {
message.append(rightPad("g", Integer.parseInt(fieldArray[1]));
}
} else if ......
}
As you can see, as hidious as it is, it gets the job done. But such code is error-prone, and is hard to maintain. I wonder if you guys have any tools or libraries or some elegant solutions to recommend. Thanks so much! Hua

If I understand your question right, you have an approach where you are looping over possible templateFields. That's not necessary.
Since every fieldArray[0] is compared to some Constants values and in case of a match is processed further, we can replace the for-loop by a Map. Its keys are the possible Constants values and its values are mappers. A mapper is a BiFunction which takes the object and the value of fieldArray[1] and returns for these a message of type String.
Let's start with the mappers:
public class FieldToMessageMapper {
private static final Map<String, Function<String, String>> WORKINGTYPE_MESSAGE_MAPPER = new HashMap<>();
static {
WORKINGTYPE_MESSAGE_MAPPER.put("abc", fieldArray1 -> rightPad("a", Integer.parseInt(fieldArray1)));
WORKINGTYPE_MESSAGE_MAPPER.put("def", fieldArray1 -> rightPad("d", Integer.parseInt(fieldArray1)));
WORKINGTYPE_MESSAGE_MAPPER.put("DEFAULT", fieldArray1 -> rightPad("g", Integer.parseInt(fieldArray1)));
}
private static Map<String, BiFunction<MyObject, String, String>> MESSAGE_MAPPER = new HashMap<>();
static {
MESSAGE_MAPPER.put(Constants.WORKFLOW, (o, fieldArray1) -> rightPad(o.getFieldOne(), Integer.parseInt(fieldArray1)));
MESSAGE_MAPPER.put(Constants.WORKVOLUME, (o, fieldArray1) -> rightPad(o.getFieldTwo(), Integer.parseInt(fieldArray1)));
MESSAGE_MAPPER.put(Constants.WORKTYPE,
(o, fieldArray1) -> WORKINGTYPE_MESSAGE_MAPPER.getOrDefault(o.getFieldThree().toLowerCase(), WORKINGTYPE_MESSAGE_MAPPER.get("DEFAULT")).apply(fieldArray1));
}
public static Optional<String> map(MyObject o, String fieldArray0, String fieldArray1) {
return Optional.ofNullable(MESSAGE_MAPPER.get(fieldArray0.toLowerCase()))
.map(mapper -> mapper.apply(o, fieldArray1));
}
private static String rightPad(String string, int pad) {
// TODO right pad
return string;
}
}
We do not return a mapper itself. FieldToMessageMapper offers the method map which does the mapping. It returns an Optional<String> which shows the result might be empty if there is no mapping for the input.
To ensure to get a mapper independent of the characters case, all keys are String..toLowerCase().
Let's go on with the overall processing:
protected StringBuffer process(Collection<String> templateFields, MyObject object) {
StringBuffer message = new StringBuffer(1000);
for (String field : templateFields) {
String[] fieldArray = field.split(Constants.SEPARATOR);
String msg = FieldToMessageMapper.map(object, fieldArray[0], fieldArray[1])
.orElseThrow(() -> new IllegalArgumentException(String.format("Unsupported field %s", field)));
message.append(msg);
}
return message;
}
I don't know how you need to handle missing mappings. I choose fail fast by throwing an exception.
Please note: StringBuffer is
A thread-safe, mutable sequence of characters. A string buffer is like
a String, but can be modified.
If your processing isn't multithreaded you could use StringBuilder. If the result isn't modified further, you could use String.
Let me show a further alternative using Stream which returns a String:
protected String process(Collection<String> templateFields, MyObject object) {
return templateFields.stream()
.map(field -> field.split(Constants.SEPARATOR))
.map(fieldArray -> FieldToMessageMapper.map(object, fieldArray[0], fieldArray[1])
.orElseThrow(() -> new IllegalArgumentException(String.format("Unsupported field %s", Arrays.toString(fieldArray)))))
.collect(Collectors.joining());
}
If I got the code from the question right, there should be the following implementation of Constants:
public class Constants {
public static final String SEPARATOR = ",";
public static final String WORKFLOW = "field1";
public static final String WORKVOLUME = "filed2";
public static final String WORKTYPE = "field3";
}
EDIT:
If you want to have a configuration approach you can elaborate this code further to use Spring configuration:
Define an interface MessageMapper which has two methods: String getKey() and String map(MyObject o, String fieldArray1). getKey() returns the Constants value for which the mapper provides the mapping.
Implement each of above MESSAGE_MAPPER using this interface.
Add a CommonMessageMapper which has a constructor CommonMessageMapper(MessageMapper... messageMappers). The messageMappers has to be put in a Map<String, BiFunction<MyObject, String, String>> mappers like: mappers.put(messageMapper.getKey(), messageMapper). Define a method String map(MyObject o, String fieldArray0, String fieldArray1) which will lookup the appropriate MessageMapper mm using fieldArray0: MessageMapper mm = mappers.get(fieldArray0). Invoke then mm.map(o, feldArray1). (You may use here also an Optional to handle the case when no appropriate mapper is present.)
To use Spring configuration all MessageMapper and the CommonMessageMapper have to be annotated as Bean or Component. The constructor of CommonMessageMapper has to be annotated with #Autowired.
Define a Spring configuration (either using XML or as #Configuration) which will inject the desired MessageMapper into a CommonMessageMapper and has a factory method for such a CommonMessageMapper.
Use CommonMessageMapper instead of FieldToMessageMapper above.

Related

How to modify an element of the Stream based on the value in a HashMap

I want to overwrite my objects status value if a corresponding key/value pair can be found in my HashMap.
Sample Model:
public class Url {
private String url;
private String status;
}
private List<Url> overwriteStatus(List<Url> myObjects) {
final var myChecklist = Map.of("www.foo.com", "blocked");
//if the key exists in `myChecklist`, then overwrite the status
myObjects.stream().forEach(item -> Optional.ofNullable(myChecklist.get(item.getUrl())).ifPresent(item::setStatus));
return myObjects;
}
My current approach with Optionals feels very messy.
What is the best approach to check if the value exists in a HashMap, and if so, use that value in a next step?
Documentation for the Stream API warns't against the usage of stateful streams.
Functions used in streams has to be pure, i.e. don't cause mutations of the stream elements, don't modify objects outside the stream (that's what basically happens in your code).
Your task can be fulfilled with streams without violating the guidelines mentioned above. For that, instead of changing the state if a new status has to be applied, a new object needs to be created.
I also suggest to Url objects immutable. So that the same instances could be safely reused in different parts of the application.
private List<Url> overwriteStatus(List<Url> myObjects,
Map<String, String> myChecklist) {
return myObjects.stream()
.map(item -> !myChecklist.containsKey(item.getUrl()) ? item :
new Url(item.getUrl(), myChecklist.get(item.getUrl())))
.collect(Collectors.toList());
}
Immutable class Url (no setters, all fields are final)
public class Url {
private final String url;
private final String status;
// constructor and getters
}
Note:
With regard to the Optional type, it was introduced in the JDK for only one particular purpose: to represent the return type of methods that could potentially yield null and that's it. The practice of utilizing the Optional just in order of chaining methods on it is considered to be an antipattern.
I don't think you should use a Stream since you are modifying the objects as you are iterating the collection.
Just use a loop
for (Url item : myObjects) {
String status = myCheckList.get(item.getUrl());
if (status == null) continue;
u.setStatus(status);
}

Hazelcast not working correctly with SqlPredicate and Index on optional field

We are storing complex objects in Hazelcast maps and need the possibility to search for objects not only based on the key but also on the content of these complex objects. In order to not take too large a performance hit, we are using indices on those search terms.
We are also using spring-data-hazelcast which provides repositories that allow us to use findByAbcXyz() type semantic queries. For some of the more complex queries we are using the #Query annotation (which spring-data-hazelcast internally translates to SqlPredicates).
We have now encountered an issue where under certain situations these #Query based search methods did not return any values, even if we could verify that the searched objects did in fact exist in the map.
I have managed to reproduce this issue with core hazelcast (i.e. without the use of spring-data-hazelcast).
Here is our object structure:
BetriebspunktKey.java
public class BetriebspunktKey implements Serializable {
private Integer uicLand;
private Integer nummer;
public BetriebspunktKey(final Integer uicLand, final Integer nummer) {
this.uicLand = uicLand;
this.nummer = nummer;
}
public Integer getUicLand() {
return uicLand;
}
public Integer getNummer() {
return nummer;
}
}
Betriebspunkt.java
public class Betriebspunkt implements Serializable {
private BetriebspunktKey key;
private List<BetriebspunktVersion> versionen;
public Betriebspunkt(final BetriebspunktKey key, final List<BetriebspunktVersion> versionen) {
this.key = key;
this.versionen = versionen;
}
public BetriebspunktKey getKey() {
return key;
}
}
BetriebspunktVersion.java
public class BetriebspunktVersion implements Serializable {
private List<BetriebspunktKey> zusatzbetriebspunkte;
public BetriebspunktVersion(final List<BetriebspunktKey> zusatzbetriebspunkte) {
this.zusatzbetriebspunkte = zusatzbetriebspunkte;
}
}
In my main file, I am now setting up hazelcast:
Config config = new Config();
final MapConfig mapConfig = config.getMapConfig("points");
mapConfig.addMapIndexConfig(new MapIndexConfig("versionen[any].zusatzbetriebspunkte[any].nummer", false));
HazelcastInstance instance = Hazelcast.newHazelcastInstance(config);
IMap<BetriebspunktKey, Betriebspunkt> map = instance.getMap("points");
I am also preparing my search criteria for later on:
Predicate equalPredicate = Predicates.equal("versionen[any].zusatzbetriebspunkte[any].nummer", 53090);
Predicate sqlPredicate = new SqlPredicate("versionen[any].zusatzbetriebspunkte[any].nummer=53090");
Next, I am creating two objects, one with the "full depth" of information, the other does not contain any "zusatzbetriebspunkte":
final Betriebspunkt abc = new Betriebspunkt(
new BetriebspunktKey(80, 166),
Collections.singletonList(new BetriebspunktVersion(
Collections.singletonList(new BetriebspunktKey(80, 53090))
))
);
final Betriebspunkt def = new Betriebspunkt(
new BetriebspunktKey(83, 141),
Collections.singletonList(new BetriebspunktVersion(
Collections.emptyList()
))
);
Here is, where things become interesting. If I first insert the "full" object into the map, the search using both the EqualPredicate as well as the SqlPredicate works:
map.put(abc.getKey(), abc);
map.put(def.getKey(), def);
Collection<Betriebspunkt> equalResults = map.values(equalPredicate);
Collection<Betriebspunkt> sqlResults = map.values(sqlPredicate);
assertEquals(1, equalResults.size()); // contains "abc"
assertEquals(1, sqlResults.size()); // contains "abc"
However, if I insert the objects into my map in reverse order (i.e. first the "partial" object and then the "full" one), only the EqualPredicate works correctly, the SqlPredicate returns an empty list, no matter what the content of the map or the search criteria.
map.put(abc.getKey(), abc);
map.put(def.getKey(), def);
Collection<Betriebspunkt> equalResults = map.values(equalPredicate);
Collection<Betriebspunkt> sqlResults = map.values(sqlPredicate);
assertEquals(1, equalResults.size()); // contains "abc"
assertEquals(1, sqlResults.size()); // --> this fails, it returns en empty list
What is the reason for this behaviour? It looks like a bug in the hazelcast code.
The reason for failing
After a lot of debugging, I have found the reason for this issue. The reasons can indeed be found in the hazelcast code.
When putting a value into a hazelcast map DefaultRecordStore.putInternal is called. At the end of this method DefaultRecordStore.saveIndex is called which finds the corresponding indexes and then calls Indexes.saveEntryIndex. This method iterates over each index and calls InternalIndex.saveEntryIndex (or rather its implementation IndexImpl.saveEntryIndex. The interesting part of that method are the following lines:
if (this.converter == null || this.converter == TypeConverters.NULL_CONVERTER) {
this.converter = entry.getConverter(this.attributeName);
}
Aparently each index stores a converter class when the first element is put into the map. Looking at QueryableEntry.getConverter explains what happens:
TypeConverter getConverter(String attributeName) {
Object attribute = this.getAttributeValue(attributeName);
if (attribute == null) {
return TypeConverters.NULL_CONVERTER;
} else {
AttributeType attributeType = this.extractAttributeType(attributeName, attribute);
return attributeType == null ? TypeConverters.IDENTITY_CONVERTER : attributeType.getConverter();
}
}
When first inserting the "full" object, extractAttributeType() will follow the "path" of our index definition "versionen[any].zusatzbetriebspunkte[any].nummer" and find out that nummer is an integer type, accordingly a TypeConverters.IntegerConverter will be returned and stored.
When first inserting the "partial" object, "zusatzbetriebspunkte[any]" is emtpy, and there is no way for extractAttributeType to find out what type nummer hast, it therefore returns null which means that TypeConverters.IdentityConverter is used.
Also, whenever a "full" element is inserted an entry is written into the index map using nummer as key, i.e. the index-map is of type Map.
So much for writing to the map. Let's now look at how data is read from the map. When calling map.values(predicate) we will eventually get to QueryRunner.runUsingGlobalIndexSafely which contains a line:
Collection<QueryableEntry> entries = indexes.query(predicate);
this will in turn after some boilerplate code call
Set<QueryableEntry> result = indexAwarePredicate.filter(queryContext);
For both of our predicates we will eventually get to IndexImpl.getRecords() which looks as follows:
public Set<QueryableEntry> getRecords(Comparable attributeValue) {
long timestamp = this.stats.makeTimestamp();
if (this.converter == null) {
this.stats.onIndexHit(timestamp, 0L);
return new SingleResultSet((Map)null);
} else {
Set<QueryableEntry> result = this.indexStore.getRecords(this.convert(attributeValue));
this.stats.onIndexHit(timestamp, (long)result.size());
return result;
}
}
The crucial call is this.convert(attributeValue) where attributeValue is the value of the predicate.
If we compare our two predicates, we can see that the EqualPredicate has two members:
attributeName = "versionen[any].zusatzbetriebspunkte[any].nummer"
value = {Integer} 53090
The SqlPredicate contains the initial string (which we passed to its constructor) but which at constructions was also parsed and mapped to a internal EqualPredicate (which when evaluating the predicate is eventually used and passed to getRecords() above):
sql = "versionen[any].zusatzbetriebspunkte[any].nummer=53090"
predicate = {EqualPredicate}
attributeName = "versionen[any].zusatzbetriebspunkte[any].nummer"
value = {String} "53090"
And this explains why the manually created EqualPredicate works in both cases: Its value is an integer. When passed to the converter, it does not matter whether it is the IntegerConverter or the IdentityConverter, as both will return the integer which can then be used as key in the index-map (which uses an integer as key).
With the SqlPredicate however, the value is a String. If this is passed to the IntegerConverter, it is converted to its corresponding integer value and accessing the index-map works. If it is passed to the IdentityConverter, the string is returned by the conversion and trying to access the index-map with a string will never find any results.
A possible solution
How can we solve this issue? I see several possibilities:
insert a "fully built" dummy value into our map during startup to ensure the converter is correctly initialised. While this works, it is ugly and not maintenance friendly
avoid using SqlPredicate and use the integer based EqualPredicate. This is not an option when working with spring-data-hazelcast as it always converts #Query based searches to SqlPredicates. We could of course use hazelcast directly and circumvent the spring-data wrapper but while that would work it means having two ways of accessing hazelcast which is also not very maintainable
use hazelcast's ValueExtractor class. This is the elegant solution that works both natively and using spring-data-hazelcast. I will outline what that looks like:
First we need to implement a value extractor which returns all zusatzbetriebspunkte of our Betriebspunkt in a form suitable for us
public class BetriebspunktExtractor extends ValueExtractor<Betriebspunkt, String> implements Serializable {
#Override
public void extract(final Betriebspunkt betriebspunkt, final String argument, final ValueCollector valueCollector) {
betriebspunkt.getVersionen().stream()
.map(BetriebspunktVersion::getZusatzbetriebspunkte)
.flatMap(List::stream)
.map(zbp -> zbp.getUicLand() + "_" + zbp.getNummer())
.forEach(valueCollector::addObject);
}
}
You'll notice that I am not only returning the nummer field but also include the uicLand field this is something we really wanted but couldn't get working using the "...[any]..." notation. We could of course only return the nummer if we wanted the exact same behavior as outlined above.
Now we need to modify our hazelcast configuration slightly:
Config config = new Config();
final MapConfig mapConfig = config.getMapConfig("points");
//mapConfig.addMapIndexConfig(new MapIndexConfig("versionen[any].zusatzbetriebspunkte[any].nummer", false));
mapConfig.addMapIndexConfig(new MapIndexConfig("zusatzbetriebspunkt", false));
mapConfig.addMapAttributeConfig(new MapAttributeConfig("zusatzbetriebspunkt", BetriebspunktExtractor.class.getName()));
You'll notice that the "long" index definition using the "...[any]..." notation is no longer needed.
Now we can use this "pseudo attribute" to query our values and it doesn't matter in which order the objects have been added to the map:
Predicate keyPredicate = Predicates.equal("zusatzbetriebspunkt", "80_53090");
Collection<Betriebspunkt> keyResults = map.values(keyPredicate);
assertEquals(1, keyResults.size()); // always contains "abc"
And in our spring-data-hazelcast repository we can now do this:
#Query("zusatzbetriebspunkt=%d_%d")
List<StammdatenBetriebspunkt> findByZusatzbetriebspunkt(Integer uicLand, Integer nummer);
If you do not need to use spring-data-hazelcast, instead of returning a string to the ValueCollector, you could return the BetriebspunktKey directly and then use it in the predicate as well. That would be the cleanest solution:
public class BetriebspunktExtractor extends ValueExtractor<Betriebspunkt, String> implements Serializable {
#Override
public void extract(final Betriebspunkt betriebspunkt, final String argument, final ValueCollector valueCollector) {
betriebspunkt.getVersionen().stream()
.map(BetriebspunktVersion::getZusatzbetriebspunkte)
.flatMap(List::stream)
//.map(zbp -> zbp.getUicLand() + "_" + zbp.getNummer())
.forEach(valueCollector::addObject);
}
}
and then
Predicate keyPredicate = Predicates.equal("zusatzbetriebspunkt", new BetriebspunktKey(80, 53090));
However, for this to work, BetriebspunktKey needs to implement Comparable and must also provide its own equals and hashCode methods.

How to create reusable optional mapping with method references?

While I'm trying to use Optional features with method references, it really confused me how to optimize it with reusable code. I think I'm stuck while trying to use all those new features (for me) at the same time i decided to get rid of java-6 style, now I think i can't think simple, i feel that it gets overcomplicated. How can i create
List<BooleanExpression> expressionMapping = new ArrayList<>();
if (request != null) { // request is input parameter, a DTO
Optional.ofNullable(request.getPlantId())
.map(campaign.plant.id::contains) // campaign is static created by Querydsl
.ifPresent(expressionMapping::add);
Optional.ofNullable(request.getTitle())
.map(campaign.title::containsIgnoreCase)
.ifPresent(expressionMapping::add);
Optional.ofNullable(request.getCampaignNumber())
.map(this::getLikeWrapped)
.map(campaign.campaignNumber::like)
.ifPresent(expressionMapping::add);
... 20 more Optional bunch of code like this
}
also having trouble with writing this code with Optional like previous ones:
if (request.getLockVehicle() != null) {
if (request.getLockVehicle()) {
expressionMapping.add(campaign.lockVehicle.isNotNull());
} else {
expressionMapping.add(campaign.lockVehicle.isNull());
}
}
What about use enum to declare all fields from Request and use it as common part of the code. I did not check it, this is only to show my approach:
public enum RequestField {
PLANT_ID(Request::getPlantId, (val, campaign) -> campaign.plant.id::contains),
TITLE(Request::getTitle, (val, campaign) -> campaign.title::containsIgnoreCase),
CAMPAIGN_NUMBER(Request::getCampaignNumber, (val, campaign) -> campaign.campaignNumber::like),
// ... more fields here ...
;
private final Function<Request, Optional<Object>> get;
private final BiFunction<Object, Campaign, BooleanExpression> map;
RequestField(Function<Request, Object> get, BiFunction<Object, Campaign, BooleanExpression> map) {
this.get = get.andThen(Optional::ofNullable);
this.map = map;
}
public static List<BooleanExpression> getBooleanExpressions(Request request, Campaign campaign) {
if (request == null)
return Collections.emptyList();
List<BooleanExpression> res = new LinkedList<>();
for (RequestField field : values())
field.get.apply(request)
.map(r -> field.map.apply(r, campaign))
.ifPresent(res::add);
return res.isEmpty() ? Collections.emptyList() : Collections.unmodifiableList(res);
}
}
And your client code will be looking like:
List<BooleanExpression> booleanExpressions = RequestField.getBooleanExpressions(request, campaign);
P.S.
Your last code could be look like:
if (request.getLockVehicle() != null)
expressionMapping.add(request.getLockVehicle() ? campaign.lockVehicle.isNotNull() : campaign.lockVehicle.isNull());
The aim of using Optional is informing who is calling that method / parameter that it could be null.
In the first part of your code, you are not getting any advantage from this, you are just rewriting some code wrapping it around Optional logic but, as you said, without any "reusable" purpose.
A useful way is using it as returning value of a method: for example, if you know that your title could be null, you can refactor your getter like
public Optional<String> getTitle(){
return Optional.ofNullable(this.title); //I'm guessing the 'title' variable here
}
This will help you: every time you call getTitle() , you will know that could be null, because you are obtaining an Optional<String> instead of a String.
This will bring then you to:
request.getTitle().ifPresent(title-> title.doSomething())
// you can also add something like .orElse("anotherStringValue")
The second example could be reworked as the first one, making the return of getLockVehicle() as Optional<Boolean>, even if I suggest here setting that with a default value in your class, probably to false... Optional<Boolean> is pretty senseless imho
Hope this helps clearing your mind

How to use Java 8 Optionals, performing an action if all three are present?

I have some (simplified) code that uses Java Optionals:
Optional<User> maybeTarget = userRepository.findById(id1);
Optional<String> maybeSourceName = userRepository.findById(id2).map(User::getName);
Optional<String> maybeEventName = eventRepository.findById(id3).map(Event::getName);
maybeTarget.ifPresent(target -> {
maybeSourceName.ifPresent(sourceName -> {
maybeEventName.ifPresent(eventName -> {
sendInvite(target.getEmail(), String.format("Hi %s, $s has invited you to $s", target.getName(), sourceName, meetingName));
}
}
}
Needless to say, this looks and feels bad. But I can't think of another way to do this in a less-nested and more readable way. I considered streaming the 3 Optionals, but discarded the idea as doing a .filter(Optional::isPresent) then a .map(Optional::get) feels even worse.
So is there a better, more 'Java 8' or 'Optional-literate' way of dealing with this situation (essentially multiple Optionals all needed to compute a final operation)?
I think to stream the three Optionals is an overkill, why not the simple
if (maybeTarget.isPresent() && maybeSourceName.isPresent() && maybeEventName.isPresent()) {
...
}
In my eyes, this states the conditional logic more clearly compared to the use of the stream API.
Using a helper function, things at least become un-nested a little:
#FunctionalInterface
interface TriConsumer<T, U, S> {
void accept(T t, U u, S s);
}
public static <T, U, S> void allOf(Optional<T> o1, Optional<U> o2, Optional<S> o3,
TriConsumer<T, U, S> consumer) {
o1.ifPresent(t -> o2.ifPresent(u -> o3.ifPresent(s -> consumer.accept(t, u, s))));
}
allOf(maybeTarget, maybeSourceName, maybeEventName,
(target, sourceName, eventName) -> {
/// ...
});
The obvious downside being that you'd need a separate helper function overload for every different number of Optionals
How about something like this
if(Stream.of(maybeTarget, maybeSourceName,
maybeEventName).allMatch(Optional::isPresent))
{
sendinvite(....)// do get on all optionals.
}
Having said that. If your logic to find in database is only to send mail, then if maybeTarget.ifPresent() is false, then there is no point to fetch the other two values, ain't it?. I am afraid, this kinda logic can be achieved only through traditional if else statements.
Since the original code is being executed for its side effects (sending an email), and not extracting or generating a value, the nested ifPresent calls seem appropriate. The original code doesn't seem too bad, and indeed it seems rather better than some of the answers that have been proposed. However, the statement lambdas and the local variables of type Optional do seem to add a fair amount of clutter.
First, I'll take the liberty of modifying the original code by wrapping it in a method, giving the parameters nice names, and making up some type names. I have no idea if the actual code is like this, but this shouldn't really be surprising to anyone.
// original version, slightly modified
void inviteById(UserId targetId, UserId sourceId, EventId eventId) {
Optional<User> maybeTarget = userRepository.findById(targetId);
Optional<String> maybeSourceName = userRepository.findById(sourceId).map(User::getName);
Optional<String> maybeEventName = eventRepository.findById(eventId).map(Event::getName);
maybeTarget.ifPresent(target -> {
maybeSourceName.ifPresent(sourceName -> {
maybeEventName.ifPresent(eventName -> {
sendInvite(target.getEmail(), String.format("Hi %s, %s has invited you to %s",
target.getName(), sourceName, eventName));
});
});
});
}
I played around with different refactorings, and I found that extracting the inner statement lambda into its own method makes the most sense to me. Given source and target users and an event -- no Optional stuff -- it sends mail about it. This is the computation that needs to be performed after all the optional stuff has been dealt with. I've also moved the data extraction (email, name) in here instead of mixing it with the Optional processing in the outer layer. Again, this makes sense to me: send mail from source to target about event.
void setupInvite(User target, User source, Event event) {
sendInvite(target.getEmail(), String.format("Hi %s, %s has invited you to %s",
target.getName(), source.getName(), event.getName()));
}
Now, let's deal with the optional stuff. As I said above, ifPresent is the way to go here, since we want to do something with side effects. It also provides a way to "extract" the value from an Optional and bind it to a name, but only within the context of a lambda expression. Since we want to do this for three different Optionals, nesting is called for. Nesting allows names from outer lambdas to be captured by inner lambdas. This lets us bind names to values extracted from the Optionals -- but only if they're present. This can't really be done with a linear chain, since some intermediate data structure like a tuple would be necessary to build up the partial results.
Finally, in the innermost lambda, we call the helper method defined above.
void inviteById(UserId targetId, UserId sourceID, EventId eventId) {
userRepository.findById(targetId).ifPresent(
target -> userRepository.findById(sourceID).ifPresent(
source -> eventRepository.findById(eventId).ifPresent(
event -> setupInvite(target, source, event))));
}
Note that I've inlined the Optionals instead of holding them in local variables. This reveals the nesting structure a bit better. It also provides for "short-circuiting" of the operation if one of the lookups doesn't find anything, since ifPresent simply does nothing on an empty Optional.
It's still a bit dense to my eye, though. I think the reason is that this code still depends on some external repositories on which to do the lookups. It's a bit uncomfortable to have this mixed together with the Optional processing. A possibility is simply to extract the lookups into their own methods findUser and findEvent. These are pretty obvious so I won't write them out. But if this were done, the result would be:
void inviteById(UserId targetId, UserId sourceID, EventId eventId) {
findUser(targetId).ifPresent(
target -> findUser(sourceID).ifPresent(
source -> findEvent(eventId).ifPresent(
event -> setupInvite(target, source, event))));
}
Fundamentally, this isn't that different from the original code. It's subjective, but I think I prefer this to the original code. It has the same, fairly simple structure, although nested instead of the typical linear chain of Optional processing. What's different is that the lookups are done conditionally within Optional processing, instead of being done up front, stored in local variables, and then doing only conditional extraction of Optional values. Also, I've separated out data manipulation (extraction of email and name, sending of message) into a separate method. This avoids mixing data manipulation with Optional processing, which I think tends to confuse things if we're dealing with multiple Optional instances.
I think you should consider taking another approach.
I'd start by not issuing the three calls to the DB at the beginning. Instead, I'd issue the 1st query and only if the result is present, I'd issue the 2nd one. I'd then apply the same rationale with regard to the 3rd query and finally, if the last result is also present, I'd send the invite. This would avoid unnecessary calls to the DB when either one of the first two results is not present.
In order to make the code more readable, testable and maintainable, I'd also extract each DB call to its own private method, chaining them with Optional.ifPresent:
public void sendInvite(Long targetId, Long sourceId, Long meetingId) {
userRepository.findById(targetId)
.ifPresent(target -> sendInvite(target, sourceId, meetingId));
}
private void sendInvite(User target, Long sourceId, Long meetingId) {
userRepository.findById(sourceId)
.map(User::getName)
.ifPresent(sourceName -> sendInvite(target, sourceName, meetingId));
}
private void sendInvite(User target, String sourceName, Long meetingId) {
eventRepository.findById(meetingId)
.map(Event::getName)
.ifPresent(meetingName -> sendInvite(target, sourceName, meetingName));
}
private void sendInvite(User target, String sourceName, String meetingName) {
String contents = String.format(
"Hi %s, $s has invited you to $s",
target.getName(),
sourceName,
meetingName);
sendInvite(target.getEmail(), contents);
}
You can use the following if you want to stick to Optional and not commit to consuming the value immediately. It makes use of Triple<L, M, R> from Apache Commons:
/**
* Returns an optional contained a triple if all arguments are present,
* otherwise an absent optional
*/
public static <L, M, R> Optional<Triple<L, M, R>> product(Optional<L> left,
Optional<M> middle, Optional<R> right) {
return left.flatMap(l -> middle.flatMap(m -> right.map(r -> Triple.of(l, m, r))));
}
// Used as
product(maybeTarget, maybeSourceName, maybeEventName).ifPresent(this::sendInvite);
One could imagine a similar approach for two, or multiple Optionals, although java unfortunately doesn't have a general tuple type (yet).
The first approach is not perfect (it does not support laziness - all 3 database calls will be triggered anyway):
Optional<User> target = userRepository.findById(id1);
Optional<String> sourceName = userRepository.findById(id2).map(User::getName);
Optional<String> eventName = eventRepository.findById(id3).map(Event::getName);
if (Stream.of(target, sourceName, eventName).anyMatch(obj -> !obj.isPresent())) {
return;
}
sendInvite(target.get(), sourceName.get(), eventName.get());
The following example is a little bit verbose, but it supports laziness and readability:
private void sendIfValid() {
Optional<User> target = userRepository.findById(id1);
if (!target.isPresent()) {
return;
}
Optional<String> sourceName = userRepository.findById(id2).map(User::getName);
if (!sourceName.isPresent()) {
return;
}
Optional<String> eventName = eventRepository.findById(id3).map(Event::getName);
if (!eventName.isPresent()) {
return;
}
sendInvite(target.get(), sourceName.get(), eventName.get());
}
private void sendInvite(User target, String sourceName, String eventName) {
// ...
}
Well I took the same approach of Federico to only call the DB when needed, it's quite verbose too, but lazy. I also simplified this a bit. Considering you have these 3 methods:
public static Optional<String> firstCall() {
System.out.println("first call");
return Optional.of("first");
}
public static Optional<String> secondCall() {
System.out.println("second call");
return Optional.empty();
}
public static Optional<String> thirdCall() {
System.out.println("third call");
return Optional.empty();
}
I've implemented it like this:
firstCall()
.flatMap(x -> secondCall().map(y -> Stream.of(x, y))
.flatMap(z -> thirdCall().map(n -> Stream.concat(z, Stream.of(n)))))
.ifPresent(st -> System.out.println(st.collect(Collectors.joining("|"))));
You can create an infrastructure to handle a variable amount of inputs. For this to be a good design though, your inputs should not be Optional<?>; but Supplier<Optional<?>> so you can short-circuit the unnecessary evaluation of Optionals while trying to determine whether or not all are present.
Because of this, it'd be better to create a utility wrapper around your Optionals that provides transparent access to the evaluated value using a singleton pattern, like the following:
class OptionalSupplier {
private final Supplier<Optional<?>> optionalSupplier;
private Optional<?> evaluatedOptional = null;
public OptionalSupplier(Supplier<Optional<?>> supplier) {
this.optionalSupplier = supplier;
}
public Optional<?> getEvaluatedOptional() {
if (evaluatedOptional == null)
evaluatedOptional = optionalSupplier.get();
return evaluatedOptional;
}
}
Then you can create another class that handles a List of these wrappers and provides a programmatic API to execute a Function that takes as parameters the evaluated values of the actual optionals, hiding further the users involvement in the process. You can overload the method to execute a Consumer with the same parameters. Such class would look something like this:
class OptionalSemaphores {
private List<OptionalSupplier> optionalSuppliers;
private List<Object> results = null;
private boolean allPresent;
public OptionalSemaphores(Supplier<Optional<?>>... suppliers) {
optionalSuppliers = Stream.of(suppliers)
.map(OptionalSupplier::new)
.collect(Collectors.toList());
allPresent = optionalSuppliers.stream()
.map(OptionalSupplier::getEvaluatedOptional)
.allMatch(Optional::isPresent);
if (allPresent)
results = optionalSuppliers.stream()
.map(OptionalSupplier::getEvaluatedOptional)
.map(Optional::get)
.collect(Collectors.toList());
}
public boolean isAllPresent() {
return allPresent;
}
public <T> T execute(Function<List<Object>, T> function, T defaultValue) {
return (allPresent) ? function.apply(results) : defaultValue;
}
public void execute(Consumer<List<Object>> function) {
if (allPresent)
function.accept(results);
}
}
Finally all you have left to do is to create objects of this class (OptionalSemaphores) using Suppliers of your Optionals (Supplier<Optional<?>>) and invoking any of the overloaded execute methods to run (IF all Optionals are present) with a List containing the corresponding evaluated values from your Optionals. The following is a full working demo of this:
public class OptionalsTester {
public static void main(String[] args) {
Supplier<Optional<?>> s1 = () -> Optional.of("Hello");
Supplier<Optional<?>> s2 = () -> Optional.of(1L);
Supplier<Optional<?>> s3 = () -> Optional.of(55.87);
Supplier<Optional<?>> s4 = () -> Optional.of(true);
Supplier<Optional<?>> s5 = () -> Optional.of("World");
Supplier<Optional<?>> failure = () -> Optional.ofNullable(null);
Supplier<Optional<?>> s7 = () -> Optional.of(55);
System.out.print("\nFAILING SEMAPHORES: ");
new OptionalSemaphores(s1, s2, s3, s4, s5, failure, s7).execute(System.out::println);
System.out.print("\nSUCCESSFUL SEMAPHORES: ");
new OptionalSemaphores(s1, s2, s3, s4, s5, s7).execute(System.out::println);
}
static class OptionalSemaphores {
private List<OptionalSupplier> optionalSuppliers;
private List<Object> results = null;
private boolean allPresent;
public OptionalSemaphores(Supplier<Optional<?>>... suppliers) {
optionalSuppliers = Stream.of(suppliers)
.map(OptionalSupplier::new)
.collect(Collectors.toList());
allPresent = optionalSuppliers.stream()
.map(OptionalSupplier::getEvaluatedOptional)
.allMatch(Optional::isPresent);
if (allPresent)
results = optionalSuppliers.stream()
.map(OptionalSupplier::getEvaluatedOptional)
.map(Optional::get)
.collect(Collectors.toList());
}
public boolean isAllPresent() {
return allPresent;
}
public <T> T execute(Function<List<Object>, T> function, T defaultValue) {
return (allPresent) ? function.apply(results) : defaultValue;
}
public void execute(Consumer<List<Object>> function) {
if (allPresent)
function.accept(results);
}
}
static class OptionalSupplier {
private final Supplier<Optional<?>> optionalSupplier;
private Optional<?> evaluatedOptional = null;
public OptionalSupplier(Supplier<Optional<?>> supplier) {
this.optionalSupplier = supplier;
}
public Optional<?> getEvaluatedOptional() {
if (evaluatedOptional == null)
evaluatedOptional = optionalSupplier.get();
return evaluatedOptional;
}
}
}
Complete code on GitHub
Hope this helps.
If you treat Optional just as a marker for method return values, the code becomes very simple:
User target = userRepository.findById(id1).orElse(null);
User source = userRepository.findById(id2).orElse(null);
Event event = eventRepository.findById(id3).orElse(null);
if (target != null && source != null && event != null) {
String message = String.format("Hi %s, %s has invited you to %s",
target.getName(), source.getName(), event.getName());
sendInvite(target.getEmail(), message);
}
The point of Optional is not that you must use it everywhere. Instead, it serves as a marker for method return values to inform the caller to check for absentness. In this case, the orElse(null) takes care of this, and the calling code is fully concious about the possible nullness.
return userRepository.findById(id)
.flatMap(target -> userRepository.findById(id2)
.map(User::getName)
.flatMap(sourceName -> eventRepository.findById(id3)
.map(Event::getName)
.map(eventName-> createInvite(target, sourceName, eventName))))
First of all you return an Optional as well. It's better to have a method first that creates an invite, which you can call and then send if it's not empty.
Among other things, it's easier to test. Using flatMap you also get the benefit of laziness, since if the first result is empty, nothing else will be evaluated.
When you want to use multiple optionals, you always should use a combination of map and flatMap.
I'm also not using target.getEmail() and target.getName(), those should be safely extracted in createInvite method, since I don't know if they can be nulls or not.
Keeping in mind that Exceptions should not be used in this fashion,
for conciseness you can consider as well:
try {
doSomething( optional1.get(), optional2.get(), optional3.get() );
} catch( NoSuchElementException e ) {
// report, log, do nothing
}
Remember, you can define Classes and Records inline to keep the state explicit and flattened vs. nested using callbacks/closures. It might seem a bit overkill for a small example like this, but it really helps when each nested 'chain' ends up doing work.
For example, given your 3 Optionals using lombok:
#Value #With class Temp {User target; String source; String eventName;}
maybeTarget
.map(target -> new Temp(target, null, null))
.flatMap(tmp -> maybeSourceName.map(tmp::withSource))
.flatMap(tmp -> maybeEventName.map(tmp::withEventName))
.ifPresent(tmp -> System.out.printf("Hi %s, %s has invited you to %s%n", tmp.target.getName(), tmp.source, tmp.eventName));
You can do the same w/records but you'll have to do a bit more work since you have to copy everything by hand:
record TempRecord(User target, String source, String eventName) {}
maybeTarget
.map(target -> new TempRecord(target, null, null))
.flatMap(tmp -> maybeSourceName.map(source -> new TempRecord(tmp.target, source, null)))
.flatMap(tmp -> maybeEventName.map(eventName -> new TempRecord(tmp.target, tmp.source, eventName)))
.ifPresent(tmp -> System.out.printf("Hi %s, %s has invited you to %s%n", tmp.target.getName(), tmp.source, tmp.eventName));
I've tried to keep the data immutable and functions pure.

Java8 stream lines and aggregate with action on terminal line

Question
How to execute an action after the last item of an ordered stream is processed but before it's closed ?
This Action should be able to inject zero or more items in the stream pipe.
Context
I've got a very large file of the form :
MASTER_REF1
SUBREF1
SUBREF2
SUBREF3
MASTER_REF2
MASTER_REF3
SUBREF1
...
Where SUBREF (if any) is applicable to MASTER_REF and both are complex objects (you can imagine it somewhat like JSON).
On first look I tried something like :
public void process(Path path){
MyBuilder builder = new MyBuilder();
Files.lines(path)
.map(line->{
if(line.charAt(0)==' '){
builder.parseSubRef(line);
return null;
}else{
Result result = builder.build()
builder.parseMasterRef(line);
return result;
}
})
//eliminate null
.filter(Objects::nonNull)
//some processing on results
.map(Utils::doSomething)
//terminal op
.forEachOrdered(System.out::println);
}
[EDIT] using forEach here was a bad idea ... the good way was to use forEachOrdered
But, for obvious reasons, the last item is never appended to the stream : it is still being built.
Therefore I'm wondering how to flush it in the stream at the end of line processing.
Your question sounds confusing. The Stream is closed when the close() method is called explicitly or when try-with-resources construct is used. In your code sample the stream is not closed at all. To perform custom action before the stream is closed, you can just write something at the end of try-with-resource statement.
In your case it seems that you want to concatenate some bogus entry to the stream. There's Stream.concat() method to do this:
Stream.concat(Files.lines(path), Stream.of("MASTER"))
.map(...) // do all your other steps
Finally note that my StreamEx library which enhances the Stream API provides partial reduction methods which are good to parse multi-line entries. The same thing can be done using StreamEx.groupRuns() which combines adjacent elements into intermediate list by given BiPredicate:
public void process(Path path){
StreamEx.of(Files.lines(path))
.groupRuns((line1, line2) -> line2.charAt(0) == ' ')
// Now Stream elements are List<String> starting with MASTER and having
// all subref strings after that
.map(record -> {
MyBuilder builder = new MyBuilder();
builder.parseMasterRef(record.get(0));
record.subList(1, record.size()).forEach(builder::parseSubRef);
return record.build();
})
//eliminate null
.filter(Objects::nonNull)
//some processing on results
.map(Utils::doSomething)
//terminal op
.forEach(System.out::println);
}
Now you don't need to use side-effect operations.
The primary problem here is that you are streaming - effectively - two types of records and this makes it difficult to manage because streams are primarily for amorphous data.
I would pre-process the file data and collect it into MasterAndSub records. You can then groupingBy these by the Master field.
class MasterAndSub {
final String master;
final String sub;
public MasterAndSub(String master, String sub) {
this.master = master;
this.sub = sub;
}
}
/**
* Allows me to use a final Holder of a mutable value.
*
* #param <T>
*/
class Holder<T> {
T it;
public T getIt() {
return it;
}
public T setIt(T it) {
return this.it = it;
}
}
public void process(Path path) throws IOException {
final Holder<String> currentMaster = new Holder<>();
Files.lines(path)
.map(line -> {
if (line.charAt(0) == ' ') {
return new MasterAndSub(currentMaster.getIt(), line);
} else {
return new MasterAndSub(currentMaster.setIt(line), null);
}
})
...

Categories