Skip to content

Commit

Permalink
#962 Add support for mapping Java 8 Streams
Browse files Browse the repository at this point in the history
  • Loading branch information
filiphr committed Jan 6, 2017
1 parent 48d7963 commit 6b07dda
Show file tree
Hide file tree
Showing 82 changed files with 4,691 additions and 71 deletions.
13 changes: 12 additions & 1 deletion core-common/src/main/java/org/mapstruct/IterableMapping.java
Expand Up @@ -28,11 +28,22 @@
import java.util.Date;

/**
* Configures the mapping between two iterable types, e.g. {@code List<String>} and {@code List<Date>}.
* Configures the mapping between two iterable like types, e.g. {@code List<String>} and {@code List<Date>}.
*
*
* <p>Note: either @IterableMapping#dateFormat, @IterableMapping#resultType or @IterableMapping#qualifiedBy
* must be specified</p>
*
* Supported mappings are:
* <ul>
* <li>{@code Iterable<A>} to/from {@code Iterable<B>}/{@code Iterable<A>}</li>
* <li>{@code Iterable<A>} to/from {@code B[]}/{@code A[]}</li>
* <li>{@code Iterable<A>} to/from {@code Stream<B>}/{@code Stream<A>}</li>
* <li>{@code A[]} to/from {@code Stream<B>}/{@code Stream<A>}</li>
* <li>{@code A[]} to/from {@code B[]}</li>
* <li>{@code Stream<A>} to/from {@code Stream<B>}</li>
* </ul>
*
* @author Gunnar Morling
*/
@Target(ElementType.METHOD)
Expand Down
1 change: 1 addition & 0 deletions documentation/pom.xml
Expand Up @@ -66,6 +66,7 @@
</dependencies>
<configuration>
<sourceHighlighter>coderay</sourceHighlighter>
<sourceDocumentName>mapstruct-reference-guide.asciidoc</sourceDocumentName>
<attributes>
<mapstructVersion>${project.version}</mapstructVersion>
<icons>font</icons>
Expand Down
67 changes: 67 additions & 0 deletions documentation/src/main/asciidoc/mapping-streams.asciidoc
@@ -0,0 +1,67 @@
[[mapping-streams]]
== Mapping Streams

The mapping of `java.util.Stream` is done in a similar way as the mapping of collection types, i.e. by defining mapping
methods with the required source and target types in a mapper interface.

The generated code will contain the creation of a `Stream` from the provided `Iterable`/array or will collect the
provided `Stream` into an `Iterable`/array. If a mapping method or an implicit conversion for the source and target
element types exists, then this conversion will be done in `Stream#map()`. The following shows an example:

.Mapper with stream mapping methods
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface CarMapper {
Set<String> integerStreamToStringSet(Stream<Integer> integers);
List<CarDto> carsToCarDtos(Stream<Car> cars);
CarDto carToCarDto(Car car);
}
----
====

The generated implementation of the `integerStreamToStringSet()` performs the conversion from `Integer` to `String` for
each element, while the generated `carsToCarDtos()` method invokes the `carToCarDto()` method for each contained
element as shown in the following:

.Generated stream mapping methods
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
//GENERATED CODE
@Override
public Set<String> integerStreamToStringSet(Stream<Integer> integers) {
if ( integers == null ) {
return null;
}
return integers.stream().map( integer -> String.valueOf( integer ) )
.collect( Collectors.toCollection( HashSet<String>::new ) );
}
@Override
public List<CarDto> carsToCarDtos(Stream<Car> cars) {
if ( cars == null ) {
return null;
}
return integers.stream().map( car -> carToCarDto( car ) )
.collect( Collectors.toCollection( ArrayList<CarDto>::new ) );
}
----
====

[WARNING]
====
If a mapping from a `Stream` to an `Iterable` or an array is performed, then the passed `Stream` will be consumed
and it will no longer be possible to consume it.
====

The same implementation types as in <<implementation-types-for-collection-mappings>> are used for the creation of the
collection when doing `Stream` to `Iterable` mapping.
Expand Up @@ -1338,6 +1338,8 @@ When an iterable or map mapping method declares an interface type as return type
|`ConcurrentNavigableMap`|`ConcurrentSkipListMap`
|===

include::mapping-streams.asciidoc[]

[[mapping-enum-types]]
== Mapping Values

Expand Down
Expand Up @@ -18,17 +18,11 @@
*/
package org.mapstruct.ap.internal.model;

import static org.mapstruct.ap.internal.model.assignment.Assignment.AssignmentType.DIRECT;
import static org.mapstruct.ap.internal.prism.NullValueCheckStrategyPrism.ALWAYS;
import static org.mapstruct.ap.internal.util.Collections.first;
import static org.mapstruct.ap.internal.util.Collections.last;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.DeclaredType;

Expand Down Expand Up @@ -59,6 +53,11 @@
import org.mapstruct.ap.internal.util.ValueProvider;
import org.mapstruct.ap.internal.util.accessor.Accessor;

import static org.mapstruct.ap.internal.model.assignment.Assignment.AssignmentType.DIRECT;
import static org.mapstruct.ap.internal.prism.NullValueCheckStrategyPrism.ALWAYS;
import static org.mapstruct.ap.internal.util.Collections.first;
import static org.mapstruct.ap.internal.util.Collections.last;

/**
* Represents the mapping between a source and target property, e.g. from {@code String Source#foo} to
* {@code int Target#bar}. Name and type of source and target property can differ. If they have different types, the
Expand Down Expand Up @@ -262,6 +261,11 @@ public PropertyMapping build() {
else if ( sourceType.isMapType() && targetType.isMapType() ) {
assignment = forgeMapMapping( sourceType, targetType, rightHandSide, method.getExecutable() );
}
else if ( ( sourceType.isIterableType() && targetType.isStreamType() ) ||
( sourceType.isStreamType() && targetType.isStreamType() ) ||
( sourceType.isStreamType() && targetType.isIterableType() ) ) {
assignment = forgeStreamMapping( sourceType, targetType, rightHandSide, method.getExecutable() );
}
else {
assignment = forgeMapping( rightHandSide );
}
Expand Down Expand Up @@ -533,29 +537,26 @@ private String getSourcePresenceCheckerRef( SourceReference sourceReference ) {
return sourcePresenceChecker;
}

private Assignment forgeStreamMapping(Type sourceType, Type targetType, SourceRHS source,
ExecutableElement element) {

ForgedMethod methodRef = prepareForgedMethod( sourceType, targetType, source, element );

StreamMappingMethod.Builder builder = new StreamMappingMethod.Builder();
StreamMappingMethod streamMappingMethod = builder
.mappingContext( ctx )
.method( methodRef )
.selectionParameters( selectionParameters )
.callingContextTargetPropertyName( targetPropertyName )
.build();

return getForgedAssignment( source, methodRef, streamMappingMethod );
}

private Assignment forgeIterableMapping(Type sourceType, Type targetType, SourceRHS source,
ExecutableElement element) {

Assignment assignment = null;
String name = getName( sourceType, targetType );
name = Strings.getSaveVariableName( name, ctx.getNamesOfMappingsToGenerate() );

// copy mapper configuration from the source method, its the same mapper
MapperConfiguration config = method.getMapperConfiguration();
ForgedMethod methodRef = new ForgedMethod(
name,
sourceType,
targetType,
config,
element,
method.getContextParameters(),
new ForgedMethodHistory( getForgedMethodHistory( source ),
source.getSourceErrorMessagePart(),
targetPropertyName,
source.getSourceType(),
targetType
)
);
ForgedMethod methodRef = prepareForgedMethod( sourceType, targetType, source, element );
IterableMappingMethod.Builder builder = new IterableMappingMethod.Builder();

IterableMappingMethod iterableMappingMethod = builder
Expand All @@ -565,78 +566,68 @@ private Assignment forgeIterableMapping(Type sourceType, Type targetType, Source
.callingContextTargetPropertyName( targetPropertyName )
.build();

if ( iterableMappingMethod != null ) {
if ( !ctx.getMappingsToGenerate().contains( iterableMappingMethod ) ) {
ctx.getMappingsToGenerate().add( iterableMappingMethod );
return getForgedAssignment( source, methodRef, iterableMappingMethod );
}

private Assignment getForgedAssignment(SourceRHS source, ForgedMethod methodRef,
MappingMethod mappingMethod) {
Assignment assignment = null;
if ( mappingMethod != null ) {
if ( !ctx.getMappingsToGenerate().contains( mappingMethod ) ) {
ctx.getMappingsToGenerate().add( mappingMethod );
}
else {
String existingName = ctx.getExistingMappingMethod( iterableMappingMethod ).getName();
String existingName = ctx.getExistingMappingMethod( mappingMethod ).getName();
methodRef = new ForgedMethod( existingName, methodRef );
}

assignment = new MethodReference(
methodRef,
null,
ParameterBinding.fromParameters( methodRef.getParameters() ) );

ParameterBinding.fromParameters( methodRef.getParameters() )
);
assignment.setAssignment( source );

forgedMethods.addAll( iterableMappingMethod.getForgedMethods() );
forgedMethods.addAll( mappingMethod.getForgedMethods() );
}

return assignment;
}

private Assignment forgeMapMapping(Type sourceType, Type targetType, SourceRHS source,
ExecutableElement element) {

Assignment assignment = null;

private ForgedMethod prepareForgedMethod(Type sourceType, Type targetType, SourceRHS source,
ExecutableElement element) {
String name = getName( sourceType, targetType );
name = Strings.getSaveVariableName( name, ctx.getNamesOfMappingsToGenerate() );

// copy mapper configuration from the source method, its the same mapper
MapperConfiguration config = method.getMapperConfiguration();
ForgedMethod methodRef =
new ForgedMethod(
name,
sourceType,
targetType,
config,
element,
method.getContextParameters(),
return new ForgedMethod(
name,
sourceType,
targetType,
config,
element,
method.getContextParameters(),
new ForgedMethodHistory( getForgedMethodHistory( source ),
source.getSourceErrorMessagePart(),
targetPropertyName,
source.getSourceType(),
targetType
)
);
}

private Assignment forgeMapMapping(Type sourceType, Type targetType, SourceRHS source,
ExecutableElement element) {

ForgedMethod methodRef = prepareForgedMethod( sourceType, targetType, source, element );

MapMappingMethod.Builder builder = new MapMappingMethod.Builder();
MapMappingMethod mapMappingMethod = builder
.mappingContext( ctx )
.method( methodRef )
.build();

if ( mapMappingMethod != null ) {
if ( !ctx.getMappingsToGenerate().contains( mapMappingMethod ) ) {
ctx.getMappingsToGenerate().add( mapMappingMethod );
}
else {
String existingName = ctx.getExistingMappingMethod( mapMappingMethod ).getName();
methodRef = new ForgedMethod( existingName, methodRef );
}
assignment = new MethodReference(
methodRef,
null,
ParameterBinding.fromParameters( methodRef.getParameters() ) );
assignment.setAssignment( source );

forgedMethods.addAll( mapMappingMethod.getForgedMethods() );
}

return assignment;
return getForgedAssignment( source, methodRef, mapMappingMethod );
}

private Assignment forgeMapping(SourceRHS sourceRHS) {
Expand Down

0 comments on commit 6b07dda

Please sign in to comment.