#2233 Allow generic of generics in types when matching

e.g. method signature such as
<T> T fromOptional(Optional<T> optional)

when T resolves to another generic class such as Collection<String>
This commit is contained in:
Filip Hrisafov 2020-10-19 23:34:59 +02:00
parent 6102d0cc8e
commit 74d06fea5d
7 changed files with 201 additions and 1 deletions

View File

@ -296,8 +296,17 @@ public class MethodMatcher {
public Boolean visitTypeVariable(TypeVariable t, TypeMirror p) {
if ( genericTypesMap.containsKey( t ) ) {
// when already found, the same mapping should apply
// Then we should visit the resolved generic type.
// Which can potentially be another generic type
// e.g.
// <T> T fromOptional(Optional<T> optional)
// T resolves to Collection<Integer>
// We know what T resolves to, so we should treat it as if the method signature was
// Collection<Integer> fromOptional(Optional<Collection<Integer> optional)
TypeMirror p1 = genericTypesMap.get( t );
return typeUtils.isSubtype( p, p1 );
// p (Integer) should be a subType of p1 (Number)
// i.e. you can assign p (Integer) to p1 (Number)
return visit( p, p1 );
}
else {
// check if types are in bound

View File

@ -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.bugs._2233;
import java.util.Collections;
import java.util.Optional;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
/**
* @author Filip Hrisafov
*/
@IssueKey("2233")
@RunWith(AnnotationProcessorTestRunner.class)
@WithClasses( {
Program.class,
ProgramAggregate.class,
ProgramDto.class,
ProgramMapper.class,
ProgramResponseDto.class,
} )
public class Issue2233Test {
@Test
public void shouldCorrectlyMapFromOptionalToCollection() {
ProgramResponseDto response = ProgramMapper.INSTANCE.map( new ProgramAggregate( Collections.singleton(
new Program(
"Optional Mapping",
"123"
) ) ) );
assertThat( response ).isNotNull();
assertThat( response.getPrograms() ).isPresent();
assertThat( response.getPrograms().get() )
.extracting( ProgramDto::getName, ProgramDto::getNumber )
.containsExactly(
tuple( Optional.of( "Optional Mapping" ), Optional.of( "123" ) )
);
}
}

View File

@ -0,0 +1,30 @@
/*
* 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.bugs._2233;
import java.util.Optional;
/**
* @author Filip Hrisafov
*/
public class Program {
private final String name;
private final String number;
public Program(String name, String number) {
this.name = name;
this.number = number;
}
public Optional<String> getName() {
return Optional.ofNullable( name );
}
public Optional<String> getNumber() {
return Optional.ofNullable( number );
}
}

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
*/
package org.mapstruct.ap.test.bugs._2233;
import java.util.Collection;
import java.util.Optional;
/**
* @author Filip Hrisafov
*/
public class ProgramAggregate {
private final Collection<Program> programs;
public ProgramAggregate(Collection<Program> programs) {
this.programs = programs;
}
public Optional<Collection<Program>> getPrograms() {
return Optional.ofNullable( programs );
}
}

View File

@ -0,0 +1,30 @@
/*
* 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.bugs._2233;
import java.util.Optional;
/**
* @author Filip Hrisafov
*/
public class ProgramDto {
private final String name;
private final String number;
public ProgramDto(String name, String number) {
this.name = name;
this.number = number;
}
public Optional<String> getName() {
return Optional.ofNullable( name );
}
public Optional<String> getNumber() {
return Optional.ofNullable( number );
}
}

View File

@ -0,0 +1,31 @@
/*
* 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.bugs._2233;
import java.util.Collection;
import java.util.Optional;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
*/
@Mapper
public interface ProgramMapper {
ProgramMapper INSTANCE = Mappers.getMapper( ProgramMapper.class );
ProgramResponseDto map(ProgramAggregate programAggregate);
ProgramDto map(Program sourceProgramDto);
Collection<ProgramDto> mapPrograms(Collection<Program> sourcePrograms);
default <T> T fromOptional(Optional<T> optional) {
return optional.orElse( null );
}
}

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
*/
package org.mapstruct.ap.test.bugs._2233;
import java.util.Collection;
import java.util.Optional;
/**
* @author Filip Hrisafov
*/
public class ProgramResponseDto {
private final Collection<ProgramDto> programs;
public ProgramResponseDto(Collection<ProgramDto> programs) {
this.programs = programs;
}
public Optional<Collection<ProgramDto>> getPrograms() {
return Optional.ofNullable( programs );
}
}