From 43a9419c33002f1fd49bbeacd5c48492c4ee30f3 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 15 Apr 2018 10:31:45 +0200 Subject: [PATCH] #1417 Add documentation about the builder support --- .../mapstruct-reference-guide.asciidoc | 140 ++++++++++++++++++ .../extras/ImmutablesBuilderProvider.java | 7 + 2 files changed, 147 insertions(+) diff --git a/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc b/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc index d5e0f84d9..8f359969d 100644 --- a/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc +++ b/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc @@ -5,6 +5,8 @@ :Author: Gunnar Morling, Andreas Gudian, Sjaak Derksen, Filip Hrisafov and the MapStruct community :processor-dir: ../../../../processor :processor-ap-test: {processor-dir}/src/test/java/org/mapstruct/ap/test +:integration-tests-dir: ../../../../integrationtest +:immutables-builder-test: {integration-tests-dir}/src/test/resources/immutablesBuilderTest [[Preface]] == Preface @@ -310,6 +312,21 @@ In the generated method implementations all readable properties from the source The property name as defined in the http://www.oracle.com/technetwork/java/javase/documentation/spec-136004.html[JavaBeans specification] must be specified in the `@Mapping` annotation, e.g. _seatCount_ for a property with the accessor methods `getSeatCount()` and `setSeatCount()`. ==== +[TIP] +==== +Fluent setters are also supported. +Fluent setters are setters that return the same type as the type being modified. + +E.g. + +``` +public Builder seatCount(int seatCount) { + this.seatCount = seatCount; + return this; +} +``` +==== + [TIP] ==== When using Java 8 or later, you can omit the `@Mappings` wrapper annotation and directly specify several `@Mapping` annotations on one method. @@ -594,6 +611,113 @@ You can find the complete example in the https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-field-mapping[mapstruct-examples-field-mapping] project on GitHub. +[[mapping-with-builders]] +=== Using builders + +MapStruct also supports mapping of immutable types via builders. +When performing a mapping MapStruct checks if there is a builder for the type being mapped. +This is done via the `BuilderProvider` SPI. +If a Builder exists for a certain type, than that builder will be used for the mappings. + +The default implementation of the `BuilderProvider` assumes the following: + +* The type has a parameterless public static builder creation method that returns a builder. +So for example `Person` has a public static method that returns `PersonBuilder`. +* The builder type has a parameterless public method (build method) that returns the type being build +In our example `PersonBuilder` has a method returning `Person`. + +If such type is found then MapStruct will use that type to perform the mapping to (i.e. it will look for setters into that type). +To finish the mapping MapStruct generates code that will invoke the build method of the builder. + +[NOTE] +====== +The <> are also considered for the builder type. +E.g. If an object factory exists for our `PersonBuilder` than this factory would be used instead of the builder creation method. +====== + +.Person with Builder example +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public class Person { + + private final String name; + + protected Person(Person.Builder builder) { + this.name = builder.name; + } + + public static Person.Builder builder() { + return new Person.Builder(); + } + + public static class Builder { + + private String name; + + public Builder name(String name) { + this.name = name; + return this; + } + + public Person create() { + return new Person( this ); + } + } +} +---- +==== + +.Person Mapper definition +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +public interface PersonMapper { + + Person map(PersonDto dto); +} +---- +==== + +.Generated mapper with builder +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +// GENERATED CODE +public class PersonMapperImpl implements PersonMapper { + + public Person map(PersonDto dto) { + if (dto == null) { + return null; + } + + Person.Builder builder = Person.builder(); + + builder.name( dto.getName() ); + + return builder.create(); + } +} +---- +==== + +Supported builder frameworks: + +* https://projectlombok.org/[Lombok] - requires having the Lombok classes in a separate module. See for more information https://github.com/rzwitserloot/lombok/issues/1538[rzwitserloot/lombok#1538] +* https://github.com/google/auto/blob/master/value/userguide/index.md[AutoValue] +* https://immutables.github.io/[Immutables] - requires using a custom `AccessorNamingStrategy` and a custom `BuilderProvider` (see example in integration tests) +* https://github.com/google/FreeBuilder[FreeBuilder] +* It also works for custom builders (handwritten ones) if the implementation supports the defined rules for the default `BuilderProvider`. +Otherwise, you would need to write a custom `BuilderProvider` + +[TIP] +==== +In case you want to disable using builders then you can use the `NoOpBuilderProvider` by creating a `org.mapstruct.ap.spi.BuilderProvider` file in the `META-INF/services` directory with `org.mapstruct.ap.spi.NoOpBuilderProvider` as it's content. +==== + [[retrieving-mapper]] == Retrieving a mapper @@ -2671,3 +2795,19 @@ together with the file `META-INF/services/org.mapstruct.ap.spi.MappingExclusionP (e.g. `org.mapstruct.example.CustomMappingExclusionProvider`). This JAR file needs to be added to the annotation processor classpath (i.e. add it next to the place where you added the mapstruct-processor jar). + + +[[custom-builder-provider]] +=== Custom Builder Provider + +MapStruct offers the possibility to override the `DefaultProvider` via the Service Provider Interface (SPI). +A nice example is to provide support for a custom builder strategy. + +.Custom Builder Provider for Immutables +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +include::{immutables-builder-test}/extras/src/main/java/org/mapstruct/itest/immutables/extras/ImmutablesBuilderProvider.java[tag=documentation] +---- +==== diff --git a/integrationtest/src/test/resources/immutablesBuilderTest/extras/src/main/java/org/mapstruct/itest/immutables/extras/ImmutablesBuilderProvider.java b/integrationtest/src/test/resources/immutablesBuilderTest/extras/src/main/java/org/mapstruct/itest/immutables/extras/ImmutablesBuilderProvider.java index 63c6ae26a..96f1c1bd3 100644 --- a/integrationtest/src/test/resources/immutablesBuilderTest/extras/src/main/java/org/mapstruct/itest/immutables/extras/ImmutablesBuilderProvider.java +++ b/integrationtest/src/test/resources/immutablesBuilderTest/extras/src/main/java/org/mapstruct/itest/immutables/extras/ImmutablesBuilderProvider.java @@ -18,6 +18,8 @@ */ package org.mapstruct.itest.immutables.extras; +// tag::documentation[] + import java.util.regex.Pattern; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; @@ -32,9 +34,12 @@ import org.mapstruct.ap.spi.BuilderInfo; import org.mapstruct.ap.spi.DefaultBuilderProvider; import org.mapstruct.ap.spi.TypeHierarchyErroneousException; +// end::documentation[] + /** * @author Filip Hrisafov */ +// tag::documentation[] public class ImmutablesBuilderProvider extends DefaultBuilderProvider { private static final Pattern JAVA_JAVAX_PACKAGE = Pattern.compile( "^javax?\\..*" ); @@ -70,6 +75,7 @@ public class ImmutablesBuilderProvider extends DefaultBuilderProvider { return super.findBuilderInfo( immutableElement, elements, types ); } else { + // Immutables processor has not run yet. Trigger a postpone to the next round for MapStruct throw new TypeHierarchyErroneousException( typeElement ); } } @@ -95,3 +101,4 @@ public class ImmutablesBuilderProvider extends DefaultBuilderProvider { return elements.getTypeElement( builderQualifiedName ); } } +// end::documentation[]