Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/src/main/java/org/mapstruct/ValueMapping.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
*/
@Repeatable(ValueMappings.class)
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
public @interface ValueMapping {
/**
* The source value constant to use for this mapping.
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/org/mapstruct/ValueMappings.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
*
* @author Sjaak Derksen
*/
@Target(ElementType.METHOD)
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface ValueMappings {

Expand Down
39 changes: 39 additions & 0 deletions documentation/src/main/asciidoc/chapter-8-mapping-values.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -266,3 +266,42 @@ MapStruct provides the following out of the box enum name transformation strateg

It is also possible to register custom strategies.
For more information on how to do that have a look at <<custom-enum-transformation-strategy>>

[[value-mapping-composition]]
=== ValueMapping Composition

The `@ValueMapping` annotation supports now `@Target` with `ElementType#ANNOTATION_TYPE` in addition to `ElementType#METHOD`.
This allows `@ValueMapping` to be used on other (user defined) annotations for re-use purposes.
For example:

.Custom value mapping annotations
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Retention( RetentionPolicy.CLASS )
@ValueMapping(source = "EXTRA", target = "SPECIAL")
@ValueMapping(source = MappingConstants.ANY_REMAINING, target = "DEFAULT")
public @interface CustomValueAnnotation {
}
----
====
It can be used to describe some common value mapping relationships to avoid duplicate declarations, as in the following example:

.Using custom combination annotations
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface ValueMappingCompositionMapper {

@CustomValueAnnotation
ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);

@CustomValueAnnotation
@ValueMapping(source = "STANDARD", target = "SPECIAL")
ExternalOrderType duplicateAnnotation(OrderType orderType);
}
----
====
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
*/
package org.mapstruct.ap.internal.model.source;

import java.util.List;
import java.util.Objects;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.ExecutableElement;
Expand Down Expand Up @@ -34,7 +34,7 @@ public class ValueMappingOptions {
private final AnnotationValue targetAnnotationValue;

public static void fromMappingsGem(ValueMappingsGem mappingsGem, ExecutableElement method,
FormattingMessager messager, List<ValueMappingOptions> mappings) {
FormattingMessager messager, Set<ValueMappingOptions> mappings) {

boolean anyFound = false;
for ( ValueMappingGem mappingGem : mappingsGem.value().get() ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
private static final String MAPPINGS_FQN = "org.mapstruct.Mappings";
private static final String SUB_CLASS_MAPPING_FQN = "org.mapstruct.SubclassMapping";
private static final String SUB_CLASS_MAPPINGS_FQN = "org.mapstruct.SubclassMappings";

private static final String VALUE_MAPPING_FQN = "org.mapstruct.ValueMapping";
private static final String VALUE_MAPPINGS_FQN = "org.mapstruct.ValueMappings";
private FormattingMessager messager;
private TypeFactory typeFactory;
private AccessorNamingUtils accessorNaming;
Expand Down Expand Up @@ -714,22 +715,36 @@ protected void addInstances(SubclassMappingsGem gem,
* @return The mappings for the given method, keyed by target property name
*/
private List<ValueMappingOptions> getValueMappings(ExecutableElement method) {
List<ValueMappingOptions> valueMappings = new ArrayList<>();
Set<ValueMappingOptions> processedAnnotations = new RepeatValueMappings().getProcessedAnnotations( method );
return new ArrayList<>(processedAnnotations);
}

ValueMappingGem mappingAnnotation = ValueMappingGem.instanceOn( method );
ValueMappingsGem mappingsAnnotation = ValueMappingsGem.instanceOn( method );
private class RepeatValueMappings
extends RepeatableAnnotations<ValueMappingGem, ValueMappingsGem, ValueMappingOptions> {

if ( mappingAnnotation != null ) {
ValueMappingOptions valueMapping = ValueMappingOptions.fromMappingGem( mappingAnnotation );
if ( valueMapping != null ) {
valueMappings.add( valueMapping );
}
protected RepeatValueMappings() {
super( elementUtils, VALUE_MAPPING_FQN, VALUE_MAPPINGS_FQN );
}

@Override
protected ValueMappingGem singularInstanceOn(Element element) {
return ValueMappingGem.instanceOn( element );
}

if ( mappingsAnnotation != null ) {
ValueMappingOptions.fromMappingsGem( mappingsAnnotation, method, messager, valueMappings );
@Override
protected ValueMappingsGem multipleInstanceOn(Element element) {
return ValueMappingsGem.instanceOn( element );
}

@Override
protected void addInstance(ValueMappingGem gem, Element source, Set<ValueMappingOptions> mappings) {
ValueMappingOptions valueMappingOptions = ValueMappingOptions.fromMappingGem( gem );
mappings.add( valueMappingOptions );
}

return valueMappings;
@Override
protected void addInstances(ValueMappingsGem gems, Element source, Set<ValueMappingOptions> mappings) {
ValueMappingOptions.fromMappingsGem( gems, (ExecutableElement) source, messager, mappings );
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.value.composition;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import org.mapstruct.MappingConstants;
import org.mapstruct.ValueMapping;

/**
* @author orange add
*/
@Retention( RetentionPolicy.CLASS )
@ValueMapping(source = "EXTRA", target = "SPECIAL")
@ValueMapping(source = MappingConstants.ANY_REMAINING, target = "DEFAULT")
public @interface CustomValueAnnotation {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.value.composition;

import org.mapstruct.ap.test.value.ExternalOrderType;
import org.mapstruct.ap.test.value.OrderType;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.ProcessorTest;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.factory.Mappers;

import static org.assertj.core.api.Assertions.assertThat;
/**
* @author orange add
*/
@IssueKey("3037")
@WithClasses({
ValueMappingCompositionMapper.class,
ExternalOrderType.class,
OrderType.class,
CustomValueAnnotation.class
})
public class ValueCompositionTest {

@ProcessorTest
public void shouldValueCompositionSuccess() {
ValueMappingCompositionMapper compositionMapper = Mappers.getMapper( ValueMappingCompositionMapper.class );
assertThat( compositionMapper.orderTypeToExternalOrderType( OrderType.EXTRA ) )
.isEqualTo( ExternalOrderType.SPECIAL );
assertThat( compositionMapper.orderTypeToExternalOrderType( OrderType.NORMAL ) )
.isEqualTo( ExternalOrderType.DEFAULT );
}

@ProcessorTest
public void duplicateValueMappingAnnotation() {
ValueMappingCompositionMapper compositionMapper = Mappers.getMapper( ValueMappingCompositionMapper.class );
assertThat( compositionMapper.duplicateAnnotation( OrderType.EXTRA ) )
.isEqualTo( ExternalOrderType.SPECIAL );
assertThat( compositionMapper.duplicateAnnotation( OrderType.STANDARD ) )
.isEqualTo( ExternalOrderType.SPECIAL );
assertThat( compositionMapper.duplicateAnnotation( OrderType.NORMAL ) )
.isEqualTo( ExternalOrderType.DEFAULT );

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.value.composition;

import org.mapstruct.Mapper;
import org.mapstruct.ValueMapping;
import org.mapstruct.ap.test.value.ExternalOrderType;
import org.mapstruct.ap.test.value.OrderType;

/**
* @author orange add
*/
@Mapper
public interface ValueMappingCompositionMapper {

@CustomValueAnnotation
ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);

@CustomValueAnnotation
@ValueMapping(source = "STANDARD", target = "SPECIAL")
ExternalOrderType duplicateAnnotation(OrderType orderType);
}