#2987 Support for defining Javadoc in the generated mapper implementation

This commit is contained in:
José Carlos Campanero Ortiz 2023-05-01 09:22:59 +02:00 committed by GitHub
parent 970984785d
commit a8df94cc20
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 558 additions and 2 deletions

View File

@ -0,0 +1,115 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Allows the definition of Javadoc comments in the MapStruct <code>Mapper</code> generated class.
*
* <p>The annotation provides support for the usual Javadoc comments elements by defining analogous attributes.</p>
*
*
* <p>Please, note that at least one of these attributes must be specified.</p>
*
* <p>
* For instance, the following definition;
* </p>
* <pre><code class='java'>
* &#64;Javadoc(
* value = "This is the description",
* authors = { "author1", "author2" },
* deprecated = "Use {&#64;link OtherMapper} instead",
* since = "0.1"
* )
* </code></pre>
*
* <p>
* will generate:
* </p>
*
* <pre><code class='java'>
* /**
* * This is the description
* *
* * &#64;author author1
* * &#64;author author2
* *
* * &#64;deprecated Use {&#64;link OtherMapper} instead
* * &#64;since 0.1
* *&#47;
* </code></pre>
*
* <p>
* The entire Javadoc comment block can be passed directly:
* </p>
* <pre><code class='java'>
* &#64;Javadoc("This is the description\n"
* + "\n"
* + "&#64;author author1\n"
* + "&#64;author author2\n"
* + "\n"
* + "&#64;deprecated Use {&#64;link OtherMapper} instead\n"
* + "&#64;since 0.1\n"
* )
* </code></pre>
*
* <pre><code class='java'>
* // or using Text Blocks
* &#64;Javadoc(
* """
* This is the description
*
* &#64;author author1
* &#64;author author2
*
* &#64;deprecated Use {&#64;link OtherMapper} instead
* &#64;since 0.1
* """
* )
* </code></pre>
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Javadoc {
/**
* Main Javadoc comment text block.
*
* @return Main Javadoc comment text block.
*/
String value() default "";
/**
* List of authors of the code that it is being documented.
* <p>
* It will generate a list of the Javadoc tool comment element <code>&#64;author</code>
* with the different values and in the order provided.
*
* @return array of javadoc authors.
*/
String[] authors() default { };
/**
* Specifies that the functionality that is being documented is deprecated.
* <p>
* Corresponds to the <code>&#64;deprecated</code> Javadoc tool comment element.
*
* @return Deprecation message about the documented functionality
*/
String deprecated() default "";
/**
* Specifies the version since the functionality that is being documented is available.
* <p>
* Corresponds to the <code>&#64;since</code> Javadoc tool comment element.
*
* @return Version since the functionality is available
*/
String since() default "";
}

View File

@ -74,6 +74,7 @@ import static org.mapstruct.SubclassExhaustiveStrategy.COMPILE_ERROR;
* </code></pre>
*
* @author Gunnar Morling
* @see Javadoc
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)

View File

@ -761,3 +761,103 @@ public class MyConverterImpl implements MyConverter {
}
----
====
[[javadoc]]
=== Adding Javadoc comments
MapStruct provides support for defining Javadoc comments in the generated mapper implementation using the
`org.mapstruct.Javadoc` annotation.
This functionality could be relevant especially in situations where certain Javadoc standards need to be met or
to deal with Javadoc validation constraints.
The `@Javadoc` annotation defines attributes for the different Javadoc elements.
Consider the following example:
.Javadoc annotation example
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
@Javadoc(
value = "This is the description",
authors = { "author1", "author2" },
deprecated = "Use {@link OtherMapper} instead",
since = "0.1"
)
public interface MyAnnotatedWithJavadocMapper {
//...
}
----
====
.Javadoc annotated generated mapper
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
/**
* This is the description
*
* @author author1
* @author author2
*
* @deprecated Use {@link OtherMapper} instead
* @since 0.1
*/
public class MyAnnotatedWithJavadocMapperImpl implements MyAnnotatedWithJavadocMapper {
//...
}
----
====
The entire Javadoc comment block can be provided directly as well.
.Javadoc annotation example with the entire Javadoc comment block provided directly
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
@Javadoc(
"This is the description\n"
+ "\n"
+ "@author author1\n"
+ "@author author2\n"
+ "\n"
+ "@deprecated Use {@link OtherMapper} instead\n"
+ "@since 0.1\n"
)
public interface MyAnnotatedWithJavadocMapper {
//...
}
----
====
Or using Text blocks:
.Javadoc annotation example with the entire Javadoc comment block provided directly using Text blocks
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
@Javadoc(
"""
This is the description
@author author1
@author author2
@deprecated Use {@link OtherMapper} instead
@since 0.1
"""
)
public interface MyAnnotatedWithJavadocMapper {
//...
}
----
====

View File

@ -21,6 +21,7 @@ import org.mapstruct.EnumMapping;
import org.mapstruct.InheritConfiguration;
import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.IterableMapping;
import org.mapstruct.Javadoc;
import org.mapstruct.MapMapping;
import org.mapstruct.Mapper;
import org.mapstruct.MapperConfig;
@ -75,6 +76,7 @@ import org.mapstruct.tools.gem.GemDefinition;
@GemDefinition(Context.class)
@GemDefinition(Builder.class)
@GemDefinition(Condition.class)
@GemDefinition(Javadoc.class)
@GemDefinition(MappingControl.class)
@GemDefinition(MappingControls.class)

View File

@ -253,6 +253,10 @@ public abstract class GeneratedType extends ModelElement {
constructor = null;
}
public Javadoc getJavadoc() {
return null;
}
protected void addIfImportRequired(Collection<Type> collection, Type typeToAdd) {
if ( typeToAdd == null ) {
return;

View File

@ -0,0 +1,92 @@
/*
* 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.internal.model;
import org.mapstruct.ap.internal.model.common.ModelElement;
import org.mapstruct.ap.internal.model.common.Type;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/**
* Represents the javadoc information that should be generated for a {@link Mapper}.
*
* @author Jose Carlos Campanero Ortiz
*/
public class Javadoc extends ModelElement {
public static class Builder {
private String value;
private List<String> authors;
private String deprecated;
private String since;
public Builder value(String value) {
this.value = value;
return this;
}
public Builder authors(List authors) {
this.authors = authors;
return this;
}
public Builder deprecated(String deprecated) {
this.deprecated = deprecated;
return this;
}
public Builder since(String since) {
this.since = since;
return this;
}
public Javadoc build() {
return new Javadoc(
value,
authors,
deprecated,
since
);
}
}
private final String value;
private final List<String> authors;
private final String deprecated;
private final String since;
private Javadoc(String value, List<String> authors, String deprecated, String since) {
this.value = value;
this.authors = authors != null ? Collections.unmodifiableList( authors ) : Collections.emptyList();
this.deprecated = deprecated;
this.since = since;
}
public String getValue() {
return value;
}
public List<String> getAuthors() {
return authors;
}
public String getDeprecated() {
return deprecated;
}
public String getSince() {
return since;
}
@Override
public Set<Type> getImportTypes() {
return Collections.emptySet();
}
}

View File

@ -43,6 +43,7 @@ public class Mapper extends GeneratedType {
private boolean customPackage;
private boolean suppressGeneratorTimestamp;
private Set<Annotation> customAnnotations;
private Javadoc javadoc;
public Builder() {
super( Builder.class );
@ -90,6 +91,11 @@ public class Mapper extends GeneratedType {
return this;
}
public Builder javadoc(Javadoc javadoc) {
this.javadoc = javadoc;
return this;
}
public Mapper build() {
String implementationName = implName.replace( CLASS_NAME_PLACEHOLDER, getFlatName( element ) ) +
( decorator == null ? "" : "_" );
@ -119,7 +125,8 @@ public class Mapper extends GeneratedType {
fields,
constructor,
decorator,
extraImportedTypes
extraImportedTypes,
javadoc
);
}
@ -128,6 +135,7 @@ public class Mapper extends GeneratedType {
private final boolean customPackage;
private final boolean customImplName;
private Decorator decorator;
private final Javadoc javadoc;
@SuppressWarnings( "checkstyle:parameternumber" )
private Mapper(TypeFactory typeFactory, String packageName, String name,
@ -136,7 +144,7 @@ public class Mapper extends GeneratedType {
List<MappingMethod> methods, Options options, VersionInformation versionInformation,
boolean suppressGeneratorTimestamp,
Accessibility accessibility, List<Field> fields, Constructor constructor,
Decorator decorator, SortedSet<Type> extraImportedTypes ) {
Decorator decorator, SortedSet<Type> extraImportedTypes, Javadoc javadoc ) {
super(
typeFactory,
@ -157,6 +165,8 @@ public class Mapper extends GeneratedType {
customAnnotations.forEach( this::addAnnotation );
this.decorator = decorator;
this.javadoc = javadoc;
}
public Decorator getDecorator() {
@ -171,6 +181,11 @@ public class Mapper extends GeneratedType {
return customImplName || customPackage;
}
@Override
public Javadoc getJavadoc() {
return javadoc;
}
@Override
protected String getTemplateName() {
return getTemplateNameForClass( GeneratedType.class );

View File

@ -28,6 +28,7 @@ import org.mapstruct.ap.internal.gem.BuilderGem;
import org.mapstruct.ap.internal.gem.DecoratedWithGem;
import org.mapstruct.ap.internal.gem.InheritConfigurationGem;
import org.mapstruct.ap.internal.gem.InheritInverseConfigurationGem;
import org.mapstruct.ap.internal.gem.JavadocGem;
import org.mapstruct.ap.internal.gem.MapperGem;
import org.mapstruct.ap.internal.gem.MappingInheritanceStrategyGem;
import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem;
@ -40,6 +41,7 @@ import org.mapstruct.ap.internal.model.DefaultMapperReference;
import org.mapstruct.ap.internal.model.DelegatingMethod;
import org.mapstruct.ap.internal.model.Field;
import org.mapstruct.ap.internal.model.IterableMappingMethod;
import org.mapstruct.ap.internal.model.Javadoc;
import org.mapstruct.ap.internal.model.MapMappingMethod;
import org.mapstruct.ap.internal.model.Mapper;
import org.mapstruct.ap.internal.model.MapperReference;
@ -212,6 +214,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
.implPackage( mapperOptions.implementationPackage() )
.suppressGeneratorTimestamp( mapperOptions.suppressTimestampInGenerated() )
.additionalAnnotations( additionalAnnotationsBuilder.getProcessedAnnotations( element ) )
.javadoc( getJavadoc( element ) )
.build();
if ( !mappingContext.getForgedMethodsUnderCreation().isEmpty() ) {
@ -441,6 +444,23 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
return mappingMethods;
}
private Javadoc getJavadoc(TypeElement element) {
JavadocGem javadocGem = JavadocGem.instanceOn( element );
if ( javadocGem == null || !isConsistent( javadocGem, element, messager ) ) {
return null;
}
Javadoc javadoc = new Javadoc.Builder()
.value( javadocGem.value().getValue() )
.authors( javadocGem.authors().getValue() )
.deprecated( javadocGem.deprecated().getValue() )
.since( javadocGem.since().getValue() )
.build();
return javadoc;
}
private Type getUserDesiredReturnType(SourceMethod method) {
SelectionParameters selectionParameters = method.getOptions().getBeanMapping().getSelectionParameters();
if ( selectionParameters != null && selectionParameters.getResultType() != null ) {
@ -810,4 +830,15 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
onlyCandidate.getName()
);
}
private boolean isConsistent( JavadocGem gem, TypeElement element, FormattingMessager messager ) {
if ( !gem.value().hasValue()
&& !gem.authors().hasValue()
&& !gem.deprecated().hasValue()
&& !gem.since().hasValue() ) {
messager.printMessage( element, gem.mirror(), Message.JAVADOC_NO_ELEMENTS );
return false;
}
return true;
}
}

View File

@ -130,6 +130,8 @@ public enum Message {
DECORATOR_NO_SUBTYPE( "Specified decorator type is no subtype of the annotated mapper type." ),
DECORATOR_CONSTRUCTOR( "Specified decorator type has no default constructor nor a constructor with a single parameter accepting the decorated mapper type." ),
JAVADOC_NO_ELEMENTS( "'value', 'authors', 'deprecated' and 'since' are undefined in @Javadoc, define at least one of them." ),
GENERAL_CANNOT_IMPLEMENT_PRIVATE_MAPPER("Cannot create an implementation for mapper %s, because it is a private %s."),
GENERAL_NO_IMPLEMENTATION( "No implementation type is registered for return type %s." ),
GENERAL_ABSTRACT_RETURN_TYPE( "The return type %s is an abstract class or interface. Provide a non abstract / non interface result type or a factory method." ),

View File

@ -14,6 +14,7 @@ package ${packageName};
import ${importedType};
</#list>
<#if javadoc??><#nt><@includeModel object=javadoc/></#if>
<#if !generatedTypeAvailable>/*</#if>
@Generated(
value = "org.mapstruct.ap.MappingProcessor"<#if suppressGeneratorTimestamp == false>,

View File

@ -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
-->
<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.Javadoc" -->
/**
<#list value?split("\n") as line><#nt>*<#if line?has_content> </#if>${line?trim}
</#list>
<#if !authors.isEmpty()>
*
<#list authors as author> <#nt>* @author ${author?trim}
</#list>
</#if>
<#if deprecated?has_content>
*
<#nt>* @deprecated ${deprecated?trim}
</#if>
<#if since?has_content>
*
<#nt>* @since ${since?trim}
</#if>
<#nt> */

View File

@ -0,0 +1,17 @@
/*
* 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.javadoc;
import org.mapstruct.Javadoc;
import org.mapstruct.Mapper;
/**
* @author Jose Carlos Campanero Ortiz
*/
@Mapper
@Javadoc
public interface ErroneousJavadocMapper {
}

View File

@ -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.javadoc;
import org.mapstruct.Javadoc;
import org.mapstruct.Mapper;
@Mapper
@Javadoc(
value = "This is the description",
authors = { "author1", "author2" },
deprecated = "Use {@link OtherMapper} instead",
since = "0.1"
)
@Deprecated
public interface JavadocAnnotatedWithAttributesMapper {
}

View File

@ -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.javadoc;
import org.mapstruct.Javadoc;
import org.mapstruct.Mapper;
@Mapper
@Javadoc("This is the description\n"
+ "\n"
+ "@author author1\n"
+ "@author author2\n"
+ "\n"
+ "@deprecated Use {@link OtherMapper} instead\n"
+ "@since 0.1\n")
@Deprecated
public interface JavadocAnnotatedWithValueMapper {
}

View File

@ -0,0 +1,54 @@
/*
* 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.javadoc;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.ProcessorTest;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;
import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic;
import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
import org.mapstruct.ap.testutil.runner.GeneratedSource;
/**
* @author Jose Carlos Campanero Ortiz
*/
@IssueKey("2987")
class JavadocTest {
@RegisterExtension
final GeneratedSource generatedSource = new GeneratedSource();
@ProcessorTest
@WithClasses( { JavadocAnnotatedWithValueMapper.class } )
void javadocAnnotatedWithValueMapper() {
generatedSource.addComparisonToFixtureFor( JavadocAnnotatedWithValueMapper.class );
}
@ProcessorTest
@WithClasses( { JavadocAnnotatedWithAttributesMapper.class } )
void javadocAnnotatedWithAttributesMapper() {
generatedSource.addComparisonToFixtureFor( JavadocAnnotatedWithAttributesMapper.class );
}
@ProcessorTest
@IssueKey("2987")
@WithClasses({ ErroneousJavadocMapper.class })
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(type = ErroneousJavadocMapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 15,
message = "'value', 'authors', 'deprecated' and 'since' are undefined in @Javadoc, "
+ "define at least one of them.")
}
)
void shouldFailOnEmptyJavadocAnnotation() {
}
}

View File

@ -0,0 +1,27 @@
/*
* 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.javadoc;
import javax.annotation.processing.Generated;
/**
* This is the description
*
* @author author1
* @author author2
*
* @deprecated Use {@link OtherMapper} instead
*
* @since 0.1
*/
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2023-04-30T17:36:38+0200",
comments = "version: , compiler: javac, environment: Java 11.0.18 (Ubuntu)"
)
@Deprecated
public class JavadocAnnotatedWithAttributesMapperImpl implements JavadocAnnotatedWithAttributesMapper {
}

View File

@ -0,0 +1,27 @@
/*
* 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.javadoc;
import javax.annotation.processing.Generated;
/**
* This is the description
*
* @author author1
* @author author2
*
* @deprecated Use {@link OtherMapper} instead
* @since 0.1
*
*/
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2023-04-30T17:38:45+0200",
comments = "version: , compiler: javac, environment: Java 11.0.18 (Ubuntu)"
)
@Deprecated
public class JavadocAnnotatedWithValueMapperImpl implements JavadocAnnotatedWithValueMapper {
}