How to extend mapstruct`s #mapping annotation - java

I have a dictionaries which has multiple fields like: id, code, ruName, enName. id is a UUID, the others are Strings.
What I want is something like that:
#Mapping(source = "sourceName", target = "targetName", dictionary = "dictName", dictionaryField = "dictionaryField")
and based on target type it will generate something like that
if target type UUID
return target.targetName(getId(dictionary ,dictionaryField , sourceName));
if target type String
return target.targetName(getValue(dictionary, dictionaryField, sourceName));
What I have now is a generator which generates mappers for every dictionary and for every field in format dictionaryByFieldName, so I can use this format:
#Mapping(source="sourceName", target="targetName", qualifiedByName = "dictionaryByFieldName")
But I don't like it cos most of created mappers have no uses in project and aren't valid cos not every field is unique to get id by field-_-

Currently it is not possible to retrieve the fieldname within mapstruct, what however is possible is using an #Mapping per field to minimize the amount of mapping code.
for example:
#Mapping( target = "myUuidFieldName", expression = 'java(dict.getId("myUuidFieldName", source.getMyUuidFieldName()))' )
#Mapping( target = "myStringFieldName", expression = 'java(dict.getValue("myStringFieldName", source.getMyStringFieldName()))' )
Target map(Source source, #Context Dictionary dict);
and have a separate class called Dictionary in which you store the mappings for retrieval. This way you can easily replace the Dictionary with another Dictionary implementation in case you need a different translation.
Example of a Dictionary class:
private class Dictionary{
DbRecordAccessor dbRecord;
Map<String, RetrievalInformation> retrievalMap;
// constructor and retrievalMap initialization methods
UUID getId(String fieldName, String value){
RetrievalInformation info = retrievalMap.get(fieldName);
return dbRecord.getId(info.getDictionaryName(), fieldName, info.getDictionaryField());
}
String getValue(String fieldName, String value){
RetrievalInformation info = retrievalMap.get(fieldName);
return dbRecord.getValue(info.getDictionaryName(), fieldName, getId(fieldName, value));
}
}
The following is not (yet) supported by mapstruct. See here for more information.
It would be nice if you could have done the following:
Target map(Source source, #Context Dictionary dict);
UUID getId(String value, #TargetProperty String targetField, #Context Dictionary dict) {
return dict.getId(targetField, value);
}

Related

Mapstruct, mapping to nested objects from sevral input parameters

Given a set of five objects:
KeyDto{String id}
ValueDto{String name, String value, String description}
Key{String id, String name}
Value{String value, String description}
Target{Key key, Value value}
I would like to create a mapper with two parameters:
Target dtosToTarget(KeyDto keyDto, ValueDto valueDto);
However, just defining helper methods for Key and Value seems not to be enough:
#Mapping(source = "keyDto.id", target = "id")
#Mapping(source = "valueDto.name", target = "name")
Key keyDtoAndValueDtoToKey(KeyDto keyDto, ValueDto valueDto);
Value valueDtoToValue(ValueDto valueDto);
This gives an error on the actual dtosToTarget method:
Error:(17, 19) java: Can't map property "java.lang.String value" to "mapping.Value value". Consider to declare/implement a mapping method: "mapping.Value map(java.lang.String value)".
The only solution I could think of - is defining custom java expressions to call necessary methods, like
#Mapping(target = "key", expression = "java(keyDtoAndValueDtoToKey(keyDto, valueDto))")
#Mapping(target = "value", expression = "java(valueDtoToValue(valueDto))")
Is there a cleaner approach?
The error you are seeing is because by default MapStruct will try to map valueDto.value into Target.value which is String to Value.
However, you can configure this like this:
#Mapper
public MyMapper {
#Mapping( target = "key.id", source = "keyDto.id")
#Mapping( target = "key.name", source = "valueDto.name")
#Mapping( target = "value", source = "valueDto")
Target dtosToTarget(KeyDto keyDto, ValueDto valueDto);
Value valueDtoToValue(ValueDto valueDto);
}
Try :
#Mapping(source = "valueDto.name", target = "name")
void keyDtoAndValueDtoToKey(#MappingTarget KeyDto keyDto, ValueDto valueDto);
This will keep all fields from Key Dto as it is and mapping required fields from valueDto as configured.

MapStruct map fields to target only when target's fields are null

I am trying to map this object
public class Source {
private String value1;
private String value2;
private String value3;
}
Into this object
public class Target {
private String targetValue1;
private String targetValue2;
private String targetValue3;
}
This is the Mapper definition.
#Mapper
public interface SourceMapper {
void toTarget(Source source, #MappingTarget Target target);
}
What I am trying to achieve is only map fields in source into target only when the fields in target are null. For example, source.value1 only maps to target.targetValue1 when target.targetValue1 is null. If it is not null, the mapping for that field is ignored.
Is it possible with MapStruct without having to write custom code?
Edit
I changed the field names of Target to make it clear that the names of the Target may/may not match the names of the fields in Source.
I don't think that can be done with mapstruct. If you still want to use mapstruct, you can ignore the target variables that could be null with #Mapping (target =" propName ", ignore = true) and decide yourself with a #AfterMapping method when you set your target variables.
You can achieve that by doing the following trick:
first, you need to know that you can control the mapping behavior on 'source' nulls fields
using Mapping.nullValuePropertyMappingStrategy()
So the following should do the work:
Target target = new Target(); // the actual target, first grade values
Source source = new Source(); // the actual source, secund grade values
Target result = new Target(); // the result
SourceMapper.toTarget(source, result); // now you have secund grade value
SourceMapper.toTarget(target, result); /* now you overide the secund grade value only
if the first grade isn't null */
#Mapper
public interface SourceMapper {
#Mapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
void toTarget(Target source, #MappingTarget Target target);
void toTarget(Source source, #MappingTarget Target target);
}

How to create list of Maps from List of Object in java without having getKey method?

How to create a list of maps, where each key name is inferred from name of the class attribute, and value is to be put by getter method
I am having following class in java
class DTA {
private String id;
private String age;
#Override
public String toString() {
return "DTA{" +
"id='" + id + '\'' +
", age='" + age + '\'' +
'}';
}
public DTA(String id, String age) {
this.id = id;
this.age = age;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
I am having a list of objects of type DTA
List<DTA> listA = new ArrayList<>();
listA.add(new DTA("A", "15"));
listA.add(new DTA("B", "25"));
I want to create an ordered list of maps (somewhat like scala) which has following content.
List<? extends Map<String, String>>
List(Map("id"->"A", "age"->"15"), Map("id"->"B", "age"->"25"))
Without "dynamics", the straight forward thing might look like:
List<Map<String, String>> x = listA
.stream()
.map(this::toMap)
.collect(Collectors.toList());
with a local helper, such as:
private Map<String, String> toMap(DTA dta) {
Map<String, String> rv = new HashMap<>();
rv.put("id", dta.getId());
rv.put("age", dta.getAge());
return rv;
}
In order to be fully dynamic here, you would have to use reflection to query the field names. You can find examples how to do that here.
But I strongly suggest to not do that: reflection should always be your last resort. The notion of DTA suggests that you have that object data coming from some "service" anyway. If so, why first serialize into a specific DTA class, to then "flatten" that information into some generic Map structure?!
Meaning: when that service gives you objects that are serialized as, say JSON, or XML ... then it would be much better to simply use a library like gson or jackson to directly deserialize that data into such generic "flat" Map-based objects. Jackson for example has a JsonNode class. When you deserialize into such objects, you get that mapping of field names for free! See here more example code.
The point is: identifying fields using reflection is possible. But reflection code is always tedious, and error prone. If possible, stay away from doing that yourself.
Basically, the tool used to "look into" the contents of classes in Java is called reflection. For example, if your object is a POJO (Plain Old Java Object), you can iterate over all fields in the class in the following way:
DTA obj; // some object, I assume it's initialized
Field[] fields = DTA.class.getDeclaredFields();
Map<String, Object> valuesMap = new HashMap<>();
for (field : fields) {
boolean wasAccessible = field.isAccessible(); // check if class has access control disabled
field.setAccessible(true); // disable access control (private/protected) to extract value
valuesMap.put(field.getName(), field.get(obj));
field.setAccessible(wasAccessible); // return flag to initial value
}
However, accessing values via reflection this way is notoriously hacky. Unless you have good reasons to do it yourself, try using a framework that automates tasks like that rather than writing code like this from scratch.
Also, reflection is slow. Accessing Field entities like that for every single object is suboptimal, if you ever want to really write code like this, you should cache the Field objects in a Map<String, Field> and only do the setAccessible override and the Field retrieval once for every collection of DTA objects.

Huge constructor for messages/names definition

I want to allow name and message customisation in both my lexer and parsers. The parser/lexer must be able to select a specific message or name, e.g.:
messageManager.tokenName.eofToken
Here, eofToken would be a String. tokenName would be a set of names for lexer tokens and messageManager would be a structure like { parserMessage, lexerMessage, contextName, tokenName }
However, I want the message customisation very directly constructed. I don't want something like:
TokenName tokenName = new TokenName();
tokenName.eofToken = "end of file";
tokenName.identifier = "identifier";
tokenName.keyword = "keyword";
tokenName.regExpLiteral = "regexp' literal";
// much more...
I want something like:
new TokenName(
"end of file",
"identifier",
"keyword",
...
)
I know we just need to define parameters and assign them to the corresponding variables, but I don't want a huge constructor like the one I had in my previous parser:
public TokenNames(
String booleanLiteral,
String eofToken,
String identifier,
String punctuator,
String keyword,
String numericLiteral,
String nullLiteral,
String regExpLiteral,
String stringLiteral,
String xmlName,
String xmlMarkup,
String xmlPunctuator,
String xmlTagCharacters,
String xmlText
)
{
this.booleanLiteral = booleanLiteral;
this.eofToken = eofToken;
this.identifier = identifier;
this.punctuator = punctuator;
this.keyword = keyword;
this.numericLiteral = numericLiteral;
this.nullLiteral = nullLiteral;
this.regExpLiteral = regExpLiteral;
this.stringLiteral = stringLiteral;
this.xmlName = xmlName;
this.xmlMarkup = xmlMarkup;
this.xmlPunctuator = xmlPunctuator;
this.xmlTagCharacters = xmlTagCharacters;
this.xmlText = xmlText;
}
I believe it's possible with arrays or varargs (more readable). How to?
Define class like this:
public class Token {
private String booleanLiteral;
private String eofToken;
...
public Token withBooleanLiteral(String booleanLiteral) {
this.booleanLiteral = booleanLiteral;
return this;
}
public Token withEofToken(String eofToken) {
this.eofToken = eofToken;
return this;
}
...
}
You'll get
Token token = new Token()
.withBooleanLiteral("something");
Check out Lombok library and #Wither annotation. It does everything for you.
Of course, the builder pattern is the most obvious solution in here (and the correct one).
But I would like to draw your attention to the fact there is a lot of fields that can be encapsulated by their own classes.
The following fields can be collected into an XMLDescription class:
String xmlName;
String xmlMarkup;
String xmlPunctuator;
String xmlTagCharacters;
String xmlText;
The next ones can be grouped by a LiteralDescription class:
String numericLiteral;
String nullLiteral;
String regExpLiteral;
String stringLiteral;
Think the problem over once more: if there is a chance to shorten a number of the fields to 3 (an extreme bound according to good practices), the constructor can be used instead of the builder.
I believe it's possible with arrays or varargs (more readable).
Please, don't do that - it's an error-prone approach. You are coupling an index of the array with a corresponding field. Such code, hard to maintain and document, causes an API user
to read a documentation if any provided,
to poke around in the sources if no provided,
to follow to any API change believing that nothing is changed.

Java CLI arguments syntax for object initialization

I'm looking for a tool which will allow me use command-line-style (preferably POSIX) strings to initialize an object' properties and attributes.
For example, you'd provide it with String input formatted like so:
String input = "--firstName=John --MiddleName=\"Louis Victor\" --lastName=Smith";
... and it would setFirstName("John"), setMiddleName("Louis Victor") and setLastName("Smith") on a given object. (which could be a JavaBean)
Please note that the input is a single String, not an array String[] as is the case with many popular CLI argument "parsers".
This is all similar to args4j but I couldn't get that to work... and I'm hoping to avoid using #annotations.
Does anyone have code/libraries/tools which could accomplish this?
For your use case, forget regular CLI parsers, you need a custom-tailored solution. If you really have such a simple argument syntax (parameters always begin with --, no occurrences of -- in the parameter values), you can use a simple Guava-based solution like this class:
Parse the String Arguments
public class ArgParser{
// split on (optional whitespace) + "--"
private final Splitter paramSplitter = Splitter.on(
Pattern.compile("\\s*\\-{2}")).omitEmptyStrings();
// find key=value (with optional double quotes around value)
private final Pattern keyValuePattern = Pattern
.compile("(.+?)=\"?(.*?)\"?$");
public Map<String, String> getParamValues(final String posixString){
final Map<String, String> paramValues = Maps.newLinkedHashMap();
Matcher matcher;
for(final String param : paramSplitter.split(posixString)){
matcher = keyValuePattern.matcher(param);
if(!matcher.find()){
throw new IllegalArgumentException("Bad parameter: " + param);
}
paramValues.put(matcher.group(1), matcher.group(2));
}
return paramValues;
}
}
Usage
final String input =
"--firstName=John --middleName=\"Louis Victor\" --lastName=Smith";
System.out.println(new ArgParser().getParamValues(input));
Output
{firstName=John, middleName=Louis Victor, lastName=Smith}
Now you can take the map and use it with a Bean library like commons-beanutils (I prefer the Spring BeanWrapper personally, but that only makes sense if you use Spring anyway)
Define the Bean Class
Any way, I'll use this value holder class:
public class Name{
private String firstName;
private String middleName;
private String lastName;
#Override
public String toString(){
return Objects
.toStringHelper(this)
.add("first name", firstName)
.add("middle name", middleName)
.add("last name", lastName)
.toString();
}
// + getters & setters
}
Set the Bean Properties
Now we'll use BeanUtils.populate(Object, Map) to apply the parameter values, like this:
final String input =
"--firstName=John --middleName=\"Louis Victor\" --lastName=Smith";
final Map<String, String> paramValues =
new ArgParser().getParamValues(input);
final Name name = new Name();
BeanUtils.populate(name, paramValues);
System.out.println(name);
Output:
Name{first name=John, middle name=Louis Victor, last name=Smith}
Caveat: Supported Property Types
BeanUtils.populate() supports setting the following property types:
... String, boolean, int, long, float, and double.
In addition, array setters for these
types (or the corresponding primitive
types) can also be identified.
Source: BeanUtilsBean.populate(Object, Map)
If you need parameter conversion beyond that, you should probably look into using the Spring BeanWrapper after all, it's extremely powerful, has many built-in property editors and you can add custom property editors. Just change the code like this:
final Name name = new Name();
final BeanWrapper wrapper = new BeanWrapperImpl(name);
wrapper.setPropertyValues(paramValues);
Reference:
BeanWrapper
PropertyAccessor.setPropertyValues(Map)
If I understand correctly, you are looking for a Java library to parse POSIX-style command line parameters. I used JSAP some time ago and it was really cool (it was using XML configuration back then).
This
-firstName John -lastName Smith
is no POSIX, you mean
--firstName John --lastName Smith
This may be the reason, why you can't get it working.
Update:
As I look at the example, it doesn't look like it could be the reason.

Categories

Resources