Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,36 @@ include::{processor-ap-test}/value/nametransformation/CustomEnumTransformationSt
----
====


[[custom-implementation-naming-strategy]]
=== Custom implementation naming Strategy

SPI name: `org.mapstruct.ap.spi.ImplementationNamingStrategy`

MapStruct offers the possibility to customize the implementation name of the generated mapper using the `ImplementationNamingStrategy` via the Service Provider Interface (SPI).


.CustomImplementationNamingStrategy example
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
public class CustomImplementationNamingStrategy implements ImplementationNamingStrategy {

@Override
public String generateMapperImplementationName(String className, String implementationName) {
return implementationName + "MapperImpl";
}

@Override
public String generateDecoratorImplementationName(String className, String implementationName) {
return implementationName + "DecoratorImpl";
}

}
----
====

[[additional-supported-options-provider]]
=== Additional Supported Options Provider

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.common.TypeFactory;
import org.mapstruct.ap.internal.option.Options;
import org.mapstruct.ap.internal.util.Services;
import org.mapstruct.ap.internal.version.VersionInformation;
import org.mapstruct.ap.spi.DefaultImplementationNamingStrategy;
import org.mapstruct.ap.spi.ImplementationNamingStrategy;

/**
* Represents a decorator applied to a generated mapper type.
Expand All @@ -25,6 +28,9 @@
*/
public class Decorator extends GeneratedType {

static final ImplementationNamingStrategy IMPLEMENTATION_NAMING_STRATEGY =
Services.get( ImplementationNamingStrategy.class, new DefaultImplementationNamingStrategy() );

public static class Builder extends GeneratedTypeBuilder<Builder> {

private TypeElement mapperElement;
Expand Down Expand Up @@ -76,13 +82,12 @@ public Builder additionalAnnotations(Set<Annotation> customAnnotations) {
}

public Decorator build() {
String implementationName = implName.replace( Mapper.CLASS_NAME_PLACEHOLDER,
Mapper.getFlatName( mapperElement ) );

String decoratorImplementationName = generateDecoratorImplementationName();
String mapperImplementationName = generateMapperImplementationName();
Type decoratorType = typeFactory.getType( decorator.value().get() );
DecoratorConstructor decoratorConstructor = new DecoratorConstructor(
implementationName,
implementationName + "_",
decoratorImplementationName,
mapperImplementationName,
hasDelegateConstructor );


Expand All @@ -93,7 +98,7 @@ public Decorator build() {
return new Decorator(
typeFactory,
packageName,
implementationName,
decoratorImplementationName,
decoratorType,
mapperType,
methods,
Expand All @@ -106,6 +111,23 @@ public Decorator build() {
customAnnotations
);
}

private String generateMapperImplementationName() {
String flatName = Mapper.getFlatName( mapperElement );
String implementationNameCandidate =
Mapper.generateDelegateImplementationNameFromExpression( implName, mapperElement );
return IMPLEMENTATION_NAMING_STRATEGY
.generateMapperImplementationName( flatName, implementationNameCandidate );
}

private String generateDecoratorImplementationName() {
String flatName = Mapper.getFlatName( mapperElement );
String implementationNameCandidate =
Mapper.generateImplementationNameFromExpression( implName, mapperElement );
return IMPLEMENTATION_NAMING_STRATEGY
.generateDecoratorImplementationName( flatName, implementationNameCandidate );
}

}

private final Type decoratorType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.common.TypeFactory;
import org.mapstruct.ap.internal.option.Options;
import org.mapstruct.ap.internal.util.Services;
import org.mapstruct.ap.internal.version.VersionInformation;
import org.mapstruct.ap.spi.DefaultImplementationNamingStrategy;
import org.mapstruct.ap.spi.ImplementationNamingStrategy;

/**
* Represents a type implementing a mapper interface (annotated with {@code @Mapper}). This is the root object of the
Expand All @@ -29,6 +32,8 @@ public class Mapper extends GeneratedType {
static final String PACKAGE_NAME_PLACEHOLDER = "<PACKAGE_NAME>";
static final String DEFAULT_IMPLEMENTATION_CLASS = CLASS_NAME_PLACEHOLDER + "Impl";
static final String DEFAULT_IMPLEMENTATION_PACKAGE = PACKAGE_NAME_PLACEHOLDER;
static final ImplementationNamingStrategy IMPLEMENTATION_NAMING_STRATEGY =
Services.get( ImplementationNamingStrategy.class, new DefaultImplementationNamingStrategy() );

public static class Builder extends GeneratedTypeBuilder<Builder> {

Expand All @@ -38,7 +43,6 @@ public static class Builder extends GeneratedTypeBuilder<Builder> {

private Decorator decorator;
private String implName;
private boolean customName;
private String implPackage;
private boolean customPackage;
private boolean suppressGeneratorTimestamp;
Expand Down Expand Up @@ -76,7 +80,6 @@ public Builder decorator(Decorator decorator) {

public Builder implName(String implName) {
this.implName = implName;
this.customName = !DEFAULT_IMPLEMENTATION_CLASS.equals( this.implName );
return this;
}

Expand All @@ -97,9 +100,7 @@ public Builder javadoc(Javadoc javadoc) {
}

public Mapper build() {
String implementationName = implName.replace( CLASS_NAME_PLACEHOLDER, getFlatName( element ) ) +
( decorator == null ? "" : "_" );

String implementationName = generateImplementationName( );
String elementPackage = elementUtils.getPackageOf( element ).getQualifiedName().toString();
String packageName = implPackage.replace( PACKAGE_NAME_PLACEHOLDER, elementPackage );
Constructor constructor = null;
Expand All @@ -115,7 +116,7 @@ public Mapper build() {
implementationName,
definitionType,
customPackage,
customName,
hasCustomName( implementationName ),
customAnnotations,
methods,
options,
Expand All @@ -130,6 +131,21 @@ public Mapper build() {
);
}

private boolean hasCustomName( String implementationName ) {
String defaultImplementationName = decorator != null ?
generateDelegateImplementationNameFromExpression( DEFAULT_IMPLEMENTATION_CLASS, element ) :
generateImplementationNameFromExpression( DEFAULT_IMPLEMENTATION_CLASS, element );
return !implementationName.equals( defaultImplementationName );
}

private String generateImplementationName( ) {
String implementationNameCandidate = decorator != null ?
generateDelegateImplementationNameFromExpression( this.implName, element ) :
generateImplementationNameFromExpression( this.implName, element );
return IMPLEMENTATION_NAMING_STRATEGY.generateMapperImplementationName( getFlatName( element ),
implementationNameCandidate );
}

}

private final boolean customPackage;
Expand Down Expand Up @@ -210,4 +226,52 @@ public static String getFlatName(TypeElement element) {
}
return nameBuilder.toString();
}

/**
* Generates the name of a delegate implementation class by evaluating
* the given implementation name expression against the specified element.
* <p>
* This method appends an underscore (<code>_</code>) at the end of the
* generated implementation name to clearly distinguish delegate
* implementations from regular ones.
* </p>
*
* Important Note: the retuned name can still be overridden afterward by the {@link ImplementationNamingStrategy}
*
* @param implementationNameExpression the expression template for generating
* the implementation name; must contain
* {@link #CLASS_NAME_PLACEHOLDER} as a placeholder
* @param element the type element (e.g., class or interface) whose name will
* be substituted into the expression
* @return the generated delegate implementation name, ending with an underscore
*/
static String generateDelegateImplementationNameFromExpression(
String implementationNameExpression,
TypeElement element) {
return generateImplementationNameFromExpression( implementationNameExpression, element ) + "_";
}

/**
* Generates an implementation class name by substituting the given
* element's flattened name into the provided expression.
* <p>
* The placeholder {@link #CLASS_NAME_PLACEHOLDER} in the expression will
* be replaced with the result of {@link #getFlatName(TypeElement)}.
* </p>
*
* Important Note: this name can still be overridden afterward by the {@link ImplementationNamingStrategy}
*
* @param implementationNameExpression the expression template for generating
* the implementation name; must contain
* {@link #CLASS_NAME_PLACEHOLDER}
* @param element the type element (e.g., class or interface) whose flattened
* name will be inserted into the expression
* @return the generated implementation name with the placeholder replaced
*/
static String generateImplementationNameFromExpression(
String implementationNameExpression,
TypeElement element) {
return implementationNameExpression.replace( CLASS_NAME_PLACEHOLDER, getFlatName( element ) );
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* 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.spi;

public class DefaultImplementationNamingStrategy implements ImplementationNamingStrategy {
@Override
public String generateMapperImplementationName(String className, String implementationName) {
return implementationName;
}

@Override
public String generateDecoratorImplementationName(String className, String implementationName) {
return implementationName;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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.spi;

/**
* Naming strategy for mappers' and delegates' implementation names
*/
public interface ImplementationNamingStrategy {
/**
* @param className the name of the interface/abstract class name
* @param implementationName the implementation name according to
* {@link org.mapstruct.MapperConfig#implementationName()}
* @return the name of the Mapper implementation class
*/
String generateMapperImplementationName(String className, String implementationName);

/**
* @param className the name of the interface/abstract class name
* @param implementationName the implementation name according to
* {@link org.mapstruct.MapperConfig#implementationName()}
* @return the name of the Decorator implementation class
*/
String generateDecoratorImplementationName(String className, String implementationName);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* 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.naming.spi.ImplementationNamingStrategy;

import org.mapstruct.ap.spi.ImplementationNamingStrategy;

public class CustomImplementationNamingStrategy implements ImplementationNamingStrategy {

@Override
public String generateMapperImplementationName(String className, String implementationName) {
return implementationName + "MapperImpl";
}

@Override
public String generateDecoratorImplementationName(String className, String implementationName) {
return implementationName + "DecoratorImpl";
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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.naming.spi.ImplementationNamingStrategy;

import org.mapstruct.ap.testutil.ProcessorTest;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.WithServiceImplementation;

import static org.assertj.core.api.Assertions.assertThat;

public class CustomImplementationNamingTest {

@ProcessorTest
@WithClasses( { MyObject.class, MyObjectDto.class, SimpleObjectMapper.class } )
@WithServiceImplementation( CustomImplementationNamingStrategy.class )
public void shouldGenerateCustomNameForImplementation() {
SimpleObjectMapper instance = SimpleObjectMapper.INSTANCE;
MyObject myObject = new MyObject( "Gavriil" );
MyObjectDto myObjectDto = instance.toDto( myObject );

assertThat( instance ).isInstanceOf( SimpleObjectMapper.class );
String expectedName = new CustomImplementationNamingStrategy()
.generateMapperImplementationName( SimpleObjectMapper.class.getSimpleName(),
SimpleObjectMapper.class.getSimpleName() + "Impl" );
assertThat( instance.getClass().getSimpleName() ).isEqualTo( expectedName );
assertThat( myObjectDto.getName() ).isEqualTo( myObject.getName() );

}

@WithClasses( { MapperDecorator.class, MyObject.class, MyObjectDto.class, DecoratedMapper.class } )
@ProcessorTest
@WithServiceImplementation( CustomImplementationNamingStrategy.class )
public void shouldGenerateCustomNameForDecoratorImplementation() {
DecoratedMapper instance = DecoratedMapper.INSTANCE;
MyObject myObject = new MyObject( "Gavriil" );
MyObjectDto myObjectDto = instance.toDto( myObject );

assertThat( instance ).isInstanceOf( DecoratedMapper.class );
String simpleName = DecoratedMapper.class.getSimpleName();
String expectedName = new CustomImplementationNamingStrategy()
.generateDecoratorImplementationName( simpleName, simpleName + "Impl" );
assertThat( instance.getClass().getSimpleName() ).isEqualTo( expectedName );
assertThat( myObjectDto.getName() ).isEqualTo( myObject.getName() );

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* 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.naming.spi.ImplementationNamingStrategy;

import org.mapstruct.DecoratedWith;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

@Mapper
@DecoratedWith( MapperDecorator.class )
public interface DecoratedMapper {
DecoratedMapper INSTANCE =
Mappers.getMapper( DecoratedMapper.class );
MyObjectDto toDto(MyObject myObject);
}
Loading