diff --git a/change-notes.html b/change-notes.html
index 017c76c1..8cd76882 100644
--- a/change-notes.html
+++ b/change-notes.html
@@ -1,4 +1,8 @@
+
1.9.1
+
+ - Fix collection / map getter write accessor not properly resolved
+
1.9.0
-
diff --git a/src/main/java/org/mapstruct/intellij/codeinsight/references/MapstructTargetReference.java b/src/main/java/org/mapstruct/intellij/codeinsight/references/MapstructTargetReference.java
index a46f82d6..9e995e1d 100644
--- a/src/main/java/org/mapstruct/intellij/codeinsight/references/MapstructTargetReference.java
+++ b/src/main/java/org/mapstruct/intellij/codeinsight/references/MapstructTargetReference.java
@@ -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;
@@ -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 &&
@@ -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();
@@ -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();
+ }
}
diff --git a/src/main/java/org/mapstruct/intellij/util/TargetUtils.java b/src/main/java/org/mapstruct/intellij/util/TargetUtils.java
index 56a8787a..0df3fc35 100644
--- a/src/main/java/org/mapstruct/intellij/util/TargetUtils.java
+++ b/src/main/java/org/mapstruct/intellij/util/TargetUtils.java
@@ -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;
@@ -295,6 +296,25 @@ private static Map> 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) {
@@ -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 ) );
}
diff --git a/src/test/java/org/mapstruct/intellij/MapstructCompletionTestCase.java b/src/test/java/org/mapstruct/intellij/MapstructCompletionTestCase.java
index 0aad938d..3bdd858f 100644
--- a/src/test/java/org/mapstruct/intellij/MapstructCompletionTestCase.java
+++ b/src/test/java/org/mapstruct/intellij/MapstructCompletionTestCase.java
@@ -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" ),
+ createVariable( "myStringSet", "Set" )
+ );
+
+ 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" );
+ }
+ );
+ }
+
+ public void testTargetWithMapGetterMapper() {
+ configureByTestName();
+ assertThat( myItems )
+ .extracting( LookupElement::getLookupString )
+ .containsExactlyInAnyOrder(
+ "myMap"
+ );
+
+ assertThat( myItems )
+ .extracting( LookupElementPresentation::renderElement )
+ .usingRecursiveFieldByFieldElementComparator()
+ .containsExactlyInAnyOrder(
+ createVariable( "myMap", "Map" )
+ );
+
+ 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" );
+ }
+ );
+ }
+
public void testCarMapperReferenceBooleanSourceCar() {
myFixture.configureByFile( "CarMapperReferenceBooleanSourceCar.java" );
PsiElement reference = myFixture.getElementAtCaret();
diff --git a/testData/inspection/UnmappedCollectionGetterPropertiesData.java b/testData/inspection/UnmappedCollectionGetterPropertiesData.java
index ad54b368..2cc8cb2d 100644
--- a/testData/inspection/UnmappedCollectionGetterPropertiesData.java
+++ b/testData/inspection/UnmappedCollectionGetterPropertiesData.java
@@ -40,7 +40,7 @@ public List getListTarget() {
return listTarget;
}
- public Set getSetTarget() {
+ public Set getSetTarget() {
return setTarget;
}
@@ -51,6 +51,10 @@ public Map getMapTarget() {
public String getStringTarget() {
return stringTarget;
}
+
+ public Object getObjectTarget() {
+ return null;
+ }
}
}
diff --git a/testData/mapping/TargetWithCollectionGetterMapper.java b/testData/mapping/TargetWithCollectionGetterMapper.java
new file mode 100644
index 00000000..fd8ad95b
--- /dev/null
+++ b/testData/mapping/TargetWithCollectionGetterMapper.java
@@ -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 = "myStringList")
+ Target carToCarDto(Object value);
+
+ public class Target {
+ public List getMyStringList() {
+ return null;
+ }
+
+ public Set getMyStringSet() {
+ return null;
+ }
+
+ public String getName() {
+ return null;
+ }
+
+ public Object getValue() {
+ return null;
+ }
+ }
+}
diff --git a/testData/mapping/TargetWithMapGetterMapper.java b/testData/mapping/TargetWithMapGetterMapper.java
new file mode 100644
index 00000000..8119b14c
--- /dev/null
+++ b/testData/mapping/TargetWithMapGetterMapper.java
@@ -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 = "myMap")
+ Target carToCarDto(Object value);
+
+ public class Target {
+ public Map getMyMap() {
+ return null;
+ }
+
+ public String getName() {
+ return null;
+ }
+
+ public Object getValue() {
+ return null;
+ }
+ }
+}