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
4 changes: 4 additions & 0 deletions change-notes.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
<html lang="en">
<h2>1.9.1</h2>
<ul>
<li>Fix collection / map getter write accessor not properly resolved</li>
</ul>
<h2>1.9.0</h2>
<ul>
<li>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiParameterList;
import com.intellij.psi.PsiRecordComponent;
import com.intellij.psi.PsiReference;
import com.intellij.psi.PsiSubstitutor;
Expand Down Expand Up @@ -98,11 +99,18 @@ builderSupportPresent && isBuilderEnabled( getMappingMethod() )
}
}

PsiMethod[] methods = psiClass.findMethodsByName( "set" + MapstructUtil.capitalize( value ), true );
String capitalizedName = MapstructUtil.capitalize( value );
PsiMethod[] methods = psiClass.findMethodsByName( "set" + capitalizedName, true );
if ( methods.length != 0 && isPublicNonStatic( methods[0] ) ) {
return methods[0];
}

// If there is no such setter we need to check if there is a collection getter
methods = psiClass.findMethodsByName( "get" + capitalizedName, true );
if ( methods.length != 0 && isCollectionGetterWriteAccessor( methods[0] ) ) {
return methods[0];
}

if ( builderSupportPresent ) {
for ( PsiMethod method : psiClass.findMethodsByName( value, true ) ) {
if ( method.getParameterList().getParametersCount() == 1 &&
Expand Down Expand Up @@ -219,7 +227,7 @@ static PsiReference[] create(PsiElement psiElement) {

private static PsiType memberPsiType(PsiElement psiMember) {
if ( psiMember instanceof PsiMethod psiMemberMethod ) {
return firstParameterPsiType( psiMemberMethod );
return resolveMethodType( psiMemberMethod );
}
else if ( psiMember instanceof PsiVariable psiMemberVariable ) {
return psiMemberVariable.getType();
Expand All @@ -229,4 +237,23 @@ else if ( psiMember instanceof PsiVariable psiMemberVariable ) {
}

}

private static boolean isCollectionGetterWriteAccessor(@NotNull PsiMethod method) {
if ( !isPublicNonStatic( method ) ) {
return false;
}
PsiParameterList parameterList = method.getParameterList();
if ( parameterList.getParametersCount() > 0 ) {
return false;
}
return TargetUtils.isMethodReturnTypeAssignableToCollectionOrMap( method );
}

private static PsiType resolveMethodType(PsiMethod psiMethod) {
PsiParameter[] psiParameters = psiMethod.getParameterList().getParameters();
if ( psiParameters.length == 0 ) {
return psiMethod.getReturnType();
}
return psiParameters[0].getType();
}
}
28 changes: 24 additions & 4 deletions src/main/java/org/mapstruct/intellij/util/TargetUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiAnnotationMemberValue;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiClassType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiJavaCodeReferenceElement;
import com.intellij.psi.PsiMember;
Expand Down Expand Up @@ -295,6 +296,25 @@ private static Map<String, Pair<? extends PsiMember, PsiSubstitutor>> publicSett
return publicSetters;
}

public static boolean isMethodReturnTypeAssignableToCollectionOrMap(@NotNull PsiMethod method) {
PsiType returnType = method.getReturnType();
if ( returnType == null ) {
return false;
}
if ( getTypeByName( "java.util.Collection", method ).isAssignableFrom( returnType ) ) {
return true;
}
return getTypeByName( "java.util.Map", method ).isAssignableFrom( returnType );
}

private static PsiClassType getTypeByName(@NotNull String qName, @NotNull PsiMethod method) {
return PsiType.getTypeByName(
qName,
method.getProject(),
method.getResolveScope()
);
}

@Nullable
private static String extractPublicSetterPropertyName(PsiMethod method, @NotNull PsiType typeToUse,
MapstructUtil mapstructUtil, boolean builderSupportPresent) {
Expand All @@ -304,10 +324,10 @@ private static String extractPublicSetterPropertyName(PsiMethod method, @NotNull
}
String methodName = method.getName();
int parametersCount = method.getParameterList().getParametersCount();
PsiType returnType = method.getReturnType();
if (parametersCount == 0 && methodName.startsWith( "get" ) && returnType != null &&
returnType.isConvertibleFrom( PsiType.getTypeByName( "java.util.Collection",
method.getProject(), method.getResolveScope() ) )) {

if ( parametersCount == 0 && methodName.startsWith( "get" ) &&
isMethodReturnTypeAssignableToCollectionOrMap( method ) ) {

// If the methode returns a collection
return Introspector.decapitalize( methodName.substring( 3 ) );
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,66 @@ public void testTargetPropertyReferencesTargetParameter() {
} );
}

public void testTargetWithCollectionGetterMapper() {
configureByTestName();
assertThat( myItems )
.extracting( LookupElement::getLookupString )
.containsExactlyInAnyOrder(
"myStringList",
"myStringSet"
);

assertThat( myItems )
.extracting( LookupElementPresentation::renderElement )
.usingRecursiveFieldByFieldElementComparator()
.containsExactlyInAnyOrder(
createVariable( "myStringList", "List<String>" ),
createVariable( "myStringSet", "Set<String>" )
);

PsiElement reference = myFixture.getElementAtCaret();
assertThat( reference )
.isInstanceOfSatisfying(
PsiMethod.class, method -> {
assertThat( method.getName() ).isEqualTo( "getMyStringList" );
assertThat( method.getPresentation() ).isNotNull();
assertThat( method.getPresentation().getPresentableText() ).isEqualTo( "getMyStringList()" );
assertThat( method.getParameterList().getParametersCount() ).isEqualTo( 0 );
assertThat( method.getReturnType() ).isNotNull();
assertThat( method.getReturnType().getPresentableText() ).isEqualTo( "List<String>" );
}
);
}

public void testTargetWithMapGetterMapper() {
configureByTestName();
assertThat( myItems )
.extracting( LookupElement::getLookupString )
.containsExactlyInAnyOrder(
"myMap"
);

assertThat( myItems )
.extracting( LookupElementPresentation::renderElement )
.usingRecursiveFieldByFieldElementComparator()
.containsExactlyInAnyOrder(
createVariable( "myMap", "Map<String, String>" )
);

PsiElement reference = myFixture.getElementAtCaret();
assertThat( reference )
.isInstanceOfSatisfying(
PsiMethod.class, method -> {
assertThat( method.getName() ).isEqualTo( "getMyMap" );
assertThat( method.getPresentation() ).isNotNull();
assertThat( method.getPresentation().getPresentableText() ).isEqualTo( "getMyMap()" );
assertThat( method.getParameterList().getParametersCount() ).isEqualTo( 0 );
assertThat( method.getReturnType() ).isNotNull();
assertThat( method.getReturnType().getPresentableText() ).isEqualTo( "Map<String, String>" );
}
);
}

public void testCarMapperReferenceBooleanSourceCar() {
myFixture.configureByFile( "CarMapperReferenceBooleanSourceCar.java" );
PsiElement reference = myFixture.getElementAtCaret();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public List<String> getListTarget() {
return listTarget;
}

public Set<Strinf> getSetTarget() {
public Set<String> getSetTarget() {
return setTarget;
}

Expand All @@ -51,6 +51,10 @@ public Map<String, String> getMapTarget() {
public String getStringTarget() {
return stringTarget;
}

public Object getObjectTarget() {
return null;
}
}

}
36 changes: 36 additions & 0 deletions testData/mapping/TargetWithCollectionGetterMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.complex;

import java.util.List;
import java.util.Set;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

@Mapper
public interface TestMapper {

@Mapping(target = "<caret>myStringList")
Target carToCarDto(Object value);

public class Target {
public List<String> getMyStringList() {
return null;
}

public Set<String> getMyStringSet() {
return null;
}

public String getName() {
return null;
}

public Object getValue() {
return null;
}
}
}
31 changes: 31 additions & 0 deletions testData/mapping/TargetWithMapGetterMapper.java
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 https://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.complex;

import java.util.Map;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

@Mapper
public interface TestMapper {

@Mapping(target = "<caret>myMap")
Target carToCarDto(Object value);

public class Target {
public Map<String, String> getMyMap() {
return null;
}

public String getName() {
return null;
}

public Object getValue() {
return null;
}
}
}
Loading