Skip to content
Merged
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 @@ -590,6 +590,21 @@ public Type withoutBounds() {
);
}

private Type replaceGeneric(Type oldGenericType, Type newType) {
if ( !typeParameters.contains( oldGenericType ) || newType == null ) {
return this;
}
newType = newType.getBoxedEquivalent();
TypeMirror[] replacedTypeMirrors = new TypeMirror[typeParameters.size()];
for ( int i = 0; i < typeParameters.size(); i++ ) {
Type typeParameter = typeParameters.get( i );
replacedTypeMirrors[i] =
typeParameter.equals( oldGenericType ) ? newType.typeMirror : typeParameter.typeMirror;
}

return typeFactory.getType( typeUtils.getDeclaredType( typeElement, replacedTypeMirrors ) );
}

/**
* Whether this type is assignable to the given other type, considering the "extends / upper bounds"
* as well.
Expand Down Expand Up @@ -1364,9 +1379,9 @@ public boolean isLiteral() {

/**
* Steps through the declaredType in order to find a match for this typeVar Type. It aligns with
* the provided parameterized type where this typeVar type is used.
*
* For example:
* the provided parameterized type where this typeVar type is used.<br>
* <br>
* For example:<pre>
* {@code
* this: T
* declaredType: JAXBElement<String>
Expand All @@ -1379,12 +1394,13 @@ public boolean isLiteral() {
* parameterizedType: Callable<BigDecimal>
* return: BigDecimal
* }
* </pre>
*
* @param declared the type
* @param parameterized the parameterized type
*
* @return - the same type when this is not a type var in the broadest sense (T, T[], or ? extends T)
* - the matching parameter in the parameterized type when this is a type var when found
* @return - the same type when this is not a type var in the broadest sense (T, T[], or ? extends T)<br>
* - the matching parameter in the parameterized type when this is a type var when found<br>
* - null in all other cases
*/
public ResolvedPair resolveParameterToType(Type declared, Type parameterized) {
Expand All @@ -1395,6 +1411,96 @@ public ResolvedPair resolveParameterToType(Type declared, Type parameterized) {
return new ResolvedPair( this, this );
}

/**
* Resolves generic types using the declared and parameterized types as input.<br>
* <br>
* For example:
* <pre>
* {@code
* this: T
* declaredType: JAXBElement<T>
* parameterizedType: JAXBElement<Integer>
* result: Integer
*
* this: List<T>
* declaredType: JAXBElement<T>
* parameterizedType: JAXBElement<Integer>
* result: List<Integer>
*
* this: List<? extends T>
* declaredType: JAXBElement<? extends T>
* parameterizedType: JAXBElement<BigDecimal>
* result: List<BigDecimal>
*
* this: List<Optional<T>>
* declaredType: JAXBElement<T>
* parameterizedType: JAXBElement<BigDecimal>
* result: List<Optional<BigDecimal>>
* }
* </pre>
* It also works for partial matching.<br>
* <br>
* For example:
* <pre>
* {@code
* this: Map<K, V>
* declaredType: JAXBElement<K>
* parameterizedType: JAXBElement<BigDecimal>
* result: Map<BigDecimal, V>
* }
* </pre>
* It also works with multiple parameters at both sides.<br>
* <br>
* For example when reversing Key/Value for a Map:
* <pre>
* {@code
* this: Map<KEY, VALUE>
* declaredType: HashMap<VALUE, KEY>
* parameterizedType: HashMap<BigDecimal, String>
* result: Map<String, BigDecimal>
* }
* </pre>
*
* Mismatch result examples:
* <pre>
* {@code
* this: T
* declaredType: JAXBElement<Y>
* parameterizedType: JAXBElement<Integer>
* result: null
*
* this: List<T>
* declaredType: JAXBElement<Y>
* parameterizedType: JAXBElement<Integer>
* result: List<T>
* }
* </pre>
*
* @param declared the type
* @param parameterized the parameterized type
*
* @return - the result of {@link #resolveParameterToType(Type, Type)} when this type itself is a type var.<br>
* - the type but then with the matching type parameters replaced.<br>
* - the same type when this type does not contain matching type parameters.
*/
public Type resolveGenericTypeParameters(Type declared, Type parameterized) {
if ( isTypeVar() || isArrayTypeVar() || isWildCardBoundByTypeVar() ) {
return resolveParameterToType( declared, parameterized ).getMatch();
}
Type resultType = this;
for ( Type generic : getTypeParameters() ) {
if ( generic.isTypeVar() || generic.isArrayTypeVar() || generic.isWildCardBoundByTypeVar() ) {
ResolvedPair resolveParameterToType = generic.resolveParameterToType( declared, parameterized );
resultType = resultType.replaceGeneric( generic, resolveParameterToType.getMatch() );
}
else {
Type replacementType = generic.resolveParameterToType( declared, parameterized ).getMatch();
resultType = resultType.replaceGeneric( generic, replacementType );
}
}
return resultType;
}

public boolean isWildCardBoundByTypeVar() {
return ( hasExtendsBound() || hasSuperBound() ) && getTypeBound().isTypeVar();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -821,7 +821,7 @@ private MethodMethod<T1, T2> getBestMatch(Type sourceType, Type targetType, Best

for ( T2 yCandidate : yMethods ) {
Type ySourceType = yCandidate.getMappingSourceType();
ySourceType = ySourceType.resolveParameterToType( targetType, yCandidate.getResultType() ).getMatch();
ySourceType = ySourceType.resolveGenericTypeParameters( targetType, yCandidate.getResultType() );
Type yTargetType = yCandidate.getResultType();
if ( ySourceType == null
|| !yTargetType.isRawAssignableTo( targetType )
Expand Down
Original file line number Diff line number Diff line change
@@ -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._2663;

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

/**
* @author Filip Hrisafov
*/
@Mapper( uses = NullableHelper.class )
public interface Issue2663Mapper {

Issue2663Mapper INSTANCE = Mappers.getMapper( Issue2663Mapper.class );

Request map(RequestDto dto);

default JsonNullable<Request.ChildRequest> mapJsonNullableChildren(JsonNullable<RequestDto.ChildRequestDto> dtos) {
if ( dtos.isPresent() ) {
return JsonNullable.of( mapChild( dtos.get() ) );
}
else {
return JsonNullable.undefined();
}
}

Request.ChildRequest mapChild(RequestDto.ChildRequestDto dto);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* 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._2663;

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

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

/**
* @author Filip Hrisafov
*/
@IssueKey( "2663" )
@WithClasses({
Issue2663Mapper.class,
JsonNullable.class,
Nullable.class,
NullableHelper.class,
Request.class,
RequestDto.class
})
public class Issue2663Test {

@ProcessorTest
public void shouldUnpackGenericsCorrectly() {
RequestDto dto = new RequestDto();
dto.setName( JsonNullable.of( "Tester" ) );

Request request = Issue2663Mapper.INSTANCE.map( dto );

assertThat( request.getName() )
.extracting( Nullable::get )
.isEqualTo( "Tester" );

dto.setName( JsonNullable.undefined() );

request = Issue2663Mapper.INSTANCE.map( dto );

assertThat( request.getName() )
.extracting( Nullable::isPresent )
.isEqualTo( false );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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._2663;

import java.util.NoSuchElementException;

/**
* @author Filip Hrisafov
*/
public class JsonNullable<T> {

private static final JsonNullable<?> UNDEFINED = new JsonNullable<>( null, false );

private final T value;
private final boolean present;

private JsonNullable(T value, boolean present) {
this.value = value;
this.present = present;
}

public T get() {
if (!present) {
throw new NoSuchElementException("Value is undefined");
}
return value;
}

public boolean isPresent() {
return present;
}

@SuppressWarnings("unchecked")
public static <T> JsonNullable<T> undefined() {
return (JsonNullable<T>) UNDEFINED;
}

public static <T> JsonNullable<T> of(T value) {
return new JsonNullable<>( value, true );
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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._2663;

import java.util.NoSuchElementException;

/**
* @author Filip Hrisafov
*/
public class Nullable<T> {

@SuppressWarnings("rawtypes")
private static final Nullable UNDEFINED = new Nullable<>( null, false );

private final T value;
private final boolean present;

private Nullable(T value, boolean present) {
this.value = value;
this.present = present;
}

public T get() {
if (!present) {
throw new NoSuchElementException("Value is undefined");
}
return value;
}

public boolean isPresent() {
return present;
}

public static <T> Nullable<T> of(T value) {
return new Nullable<>( value, true );
}

@SuppressWarnings("unchecked")
public static <T> Nullable<T> undefined() {
return (Nullable<T>) UNDEFINED;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* 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._2663;

/**
* @author Filip Hrisafov
*/
public class NullableHelper {

private NullableHelper() {
// Helper class
}

public static <T> Nullable<T> jsonNullableToNullable(JsonNullable<T> jsonNullable) {
if ( jsonNullable.isPresent() ) {
return Nullable.of( jsonNullable.get() );
}
return Nullable.undefined();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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._2663;

/**
* @author Filip Hrisafov
*/
public class Request {

private Nullable<String> name = Nullable.undefined();
private Nullable<ChildRequest> child = Nullable.undefined();

public Nullable<String> getName() {
return name;
}

public void setName(Nullable<String> name) {
this.name = name;
}

public Nullable<ChildRequest> getChild() {
return child;
}

public void setChild(Nullable<ChildRequest> child) {
this.child = child;
}

public static class ChildRequest {

private Nullable<String> name = Nullable.undefined();

public Nullable<String> getName() {
return name;
}

public void setName(Nullable<String> name) {
this.name = name;
}
}
}
Loading