From 4eb96e87caf34a348156617cd2b3b35460c463c8 Mon Sep 17 00:00:00 2001 From: thunderhook <8238759+thunderhook@users.noreply.github.com> Date: Sun, 1 Jun 2025 01:14:40 +0200 Subject: [PATCH 1/3] #226: Reference only accessible qualified methods --- ...structMappingQualifiedByNameReference.java | 32 +++++++++++- ...pingQualifiedByNameCompletionTestCase.java | 51 ++++++++++++++++++- ...fiedByNameWithAllPossibleVisibilities.java | 42 +++++++++++++++ .../mapper/ExternalMapper.java | 34 +++++++++++++ .../mapper/SamePackageMapper.java | 34 +++++++++++++ 5 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 testData/completion/qualifiedbyname/MappingQualifiedByNameWithAllPossibleVisibilities.java create mode 100644 testData/completion/qualifiedbyname/mapper/ExternalMapper.java create mode 100644 testData/completion/qualifiedbyname/mapper/SamePackageMapper.java diff --git a/src/main/java/org/mapstruct/intellij/codeinsight/references/MapstructMappingQualifiedByNameReference.java b/src/main/java/org/mapstruct/intellij/codeinsight/references/MapstructMappingQualifiedByNameReference.java index dbe6ff64..3c614e66 100644 --- a/src/main/java/org/mapstruct/intellij/codeinsight/references/MapstructMappingQualifiedByNameReference.java +++ b/src/main/java/org/mapstruct/intellij/codeinsight/references/MapstructMappingQualifiedByNameReference.java @@ -18,6 +18,7 @@ import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiMethod; +import com.intellij.psi.PsiModifier; import com.intellij.psi.PsiParameter; import com.intellij.psi.PsiReference; import com.intellij.psi.PsiType; @@ -113,12 +114,41 @@ private Stream findAllNamedMethodsFromThisAndReferencedMappers(@NotNu Stream internalMethods = Stream.of( containingClass.getMethods() ) .filter( MapstructUtil::isNamedMethod ); - Stream externalMethods = findNamedMethodsInUsedMappers( containingClass ); + Stream externalMethods = findNamedMethodsInUsedMappers( containingClass ) + .filter( method -> methodIsAccessibleFrom( method, containingClass ) ); return Stream.concat( internalMethods, externalMethods ) .filter( this::methodHasReturnType ); } + private boolean methodIsAccessibleFrom(PsiMethod method, PsiClass containingClass) { + PsiClass methodClass = method.getContainingClass(); + if ( methodClass == null ) { + return false; + } + + if ( method.hasModifierProperty( PsiModifier.PRIVATE ) ) { + return false; + } + + if ( method.hasModifierProperty( PsiModifier.PUBLIC ) ) { + return true; + } + + if ( method.hasModifierProperty( PsiModifier.PROTECTED ) ) { + return methodClass.equals( containingClass ); + } + + return haveSamePackage( containingClass, methodClass ); + } + + private boolean haveSamePackage(PsiClass firstClass, PsiClass secondClass) { + return Objects.equals( + StringUtil.getPackageName( Objects.requireNonNull( firstClass.getQualifiedName() ) ), + StringUtil.getPackageName( Objects.requireNonNull( secondClass.getQualifiedName() ) ) + ); + } + @NotNull private Stream findNamedMethodsInUsedMappers(@Nullable PsiClass containingClass) { diff --git a/src/test/java/org/mapstruct/intellij/completion/MappingQualifiedByNameCompletionTestCase.java b/src/test/java/org/mapstruct/intellij/completion/MappingQualifiedByNameCompletionTestCase.java index 5c8de1f0..b519caa1 100644 --- a/src/test/java/org/mapstruct/intellij/completion/MappingQualifiedByNameCompletionTestCase.java +++ b/src/test/java/org/mapstruct/intellij/completion/MappingQualifiedByNameCompletionTestCase.java @@ -83,10 +83,59 @@ private void assertQualifiedByNameInsideMapperConfigAutoComplete() { assertThat( myItems ) .extracting( LookupElementPresentation::renderElement ) .usingRecursiveFieldByFieldElementComparator() + .describedAs( "methods of mappers from @MapperConfig(uses = ...)" ) .containsOnly( - // methods of mappers from @MapperConfig(uses = ...) createMethod( "unwrapOptional", "T", " OptionalMapper#unwrapOptional(Optional)" ) ); } + public void testMappingQualifiedByNameWithAllPossibleVisibilities() { + configureByTestName(); + assertAutoCompleteOfValidVisibilities(); + } + + private void assertAutoCompleteOfValidVisibilities() { + + assertThat( myItems ) + .extracting( LookupElement::getLookupString ) + .containsExactlyInAnyOrder( + "internalModifierPackagePrivate", + "internalModifierPrivate", + "internalModifierProtected", + "internalModifierPublic", + "samePackageModifierPackagePrivate", + "samePackageModifierPublic", + "externalPackageModifierPublic" + ); + + assertThat( myItems ) + .extracting( LookupElementPresentation::renderElement ) + .usingRecursiveFieldByFieldElementComparator() + .containsExactlyInAnyOrder( + createMethod( + "internalModifierPackagePrivate", + "String", + " CarMapper#internalModifierPackagePrivate(String)" + ), + createMethod( "internalModifierPrivate", "String", " CarMapper#internalModifierPrivate(String)" ), + createMethod( "internalModifierProtected", "String", " CarMapper#internalModifierProtected(String)" ), + createMethod( "internalModifierPublic", "String", " CarMapper#internalModifierPublic(String)" ), + createMethod( + "samePackageModifierPackagePrivate", + "String", + " SamePackageMapper#samePackageModifierPackagePrivate(String)" + ), + createMethod( + "samePackageModifierPublic", + "String", + " SamePackageMapper#samePackageModifierPublic(String)" + ), + createMethod( + "externalPackageModifierPublic", + "String", + " ExternalMapper#externalPackageModifierPublic(String)" + ) + ); + } + } diff --git a/testData/completion/qualifiedbyname/MappingQualifiedByNameWithAllPossibleVisibilities.java b/testData/completion/qualifiedbyname/MappingQualifiedByNameWithAllPossibleVisibilities.java new file mode 100644 index 00000000..57544538 --- /dev/null +++ b/testData/completion/qualifiedbyname/MappingQualifiedByNameWithAllPossibleVisibilities.java @@ -0,0 +1,42 @@ +/* + * 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 org.example.dto.Car; +import org.example.dto.CarDto; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +import org.mapstruct.ap.test.complex.SamePackageMapper; +import org.mapstruct.helper.qualifiedbyname.external.ExternalMapper; + +@Mapper(uses = { ExternalMapper.class, SamePackageMapper.class }) +public interface CarMapper { + + @Mapping(target = "make", qualifiedByName = "") + CarDto carToCarDto(Car car); + + @Named("internalModifierPackagePrivate") + String internalModifierPackagePrivate(String value) { + return ""; + } + + @Named("internalModifierPrivate") + private String internalModifierPrivate(String value) { + return ""; + } + + @Named("internalModifierProtected") + protected String internalModifierProtected(String value) { + return ""; + } + + @Named("internalModifierPublic") + public String internalModifierPublic(String value) { + return ""; + } + +} diff --git a/testData/completion/qualifiedbyname/mapper/ExternalMapper.java b/testData/completion/qualifiedbyname/mapper/ExternalMapper.java new file mode 100644 index 00000000..3dae73d4 --- /dev/null +++ b/testData/completion/qualifiedbyname/mapper/ExternalMapper.java @@ -0,0 +1,34 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.helper.qualifiedbyname.external; + +import org.mapstruct.Mapper; +import org.mapstruct.Named; + +@Mapper +public abstract class ExternalMapper { + + @Named("externalPackageModifierPackagePrivate") + String externalPackageModifierPackagePrivate(String value) { + return ""; + } + + @Named("externalPackageModifierPrivate") + private String externalPackageModifierPrivate(String value) { + return ""; + } + + @Named("externalPackageModifierProtected") + protected String externalPackageModifierProtected(String value) { + return ""; + } + + @Named("externalPackageModifierPublic") + public String externalPackageModifierPublic(String value) { + return ""; + } + +} diff --git a/testData/completion/qualifiedbyname/mapper/SamePackageMapper.java b/testData/completion/qualifiedbyname/mapper/SamePackageMapper.java new file mode 100644 index 00000000..a9cf6d5f --- /dev/null +++ b/testData/completion/qualifiedbyname/mapper/SamePackageMapper.java @@ -0,0 +1,34 @@ +/* + * 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 org.mapstruct.Mapper; +import org.mapstruct.Named; + +@Mapper +public abstract class SamePackageMapper { + + @Named("samePackageModifierPackagePrivate") + String samePackageModifierPackagePrivate(String value) { + return ""; + } + + @Named("samePackageModifierPrivate") + private String samePackageModifierPrivate(String value) { + return ""; + } + + @Named("samePackageModifierProtected") + protected String samePackageModifierProtected(String value) { + return ""; + } + + @Named("samePackageModifierPublic") + public String samePackageModifierPublic(String value) { + return ""; + } + +} From be40513c57a0c76d317234f786904639f7bf2686 Mon Sep 17 00:00:00 2001 From: thunderhook <8238759+thunderhook@users.noreply.github.com> Date: Sun, 1 Jun 2025 19:42:13 +0200 Subject: [PATCH 2/3] #226: Filter internal methods and support methods from super class --- ...structMappingQualifiedByNameReference.java | 5 +-- ...pingQualifiedByNameCompletionTestCase.java | 16 +++++++-- ...fiedByNameWithAllPossibleVisibilities.java | 5 +-- .../qualifiedbyname/mapper/BaseMapper.java | 34 +++++++++++++++++++ 4 files changed, 54 insertions(+), 6 deletions(-) create mode 100644 testData/completion/qualifiedbyname/mapper/BaseMapper.java diff --git a/src/main/java/org/mapstruct/intellij/codeinsight/references/MapstructMappingQualifiedByNameReference.java b/src/main/java/org/mapstruct/intellij/codeinsight/references/MapstructMappingQualifiedByNameReference.java index 3c614e66..f3642457 100644 --- a/src/main/java/org/mapstruct/intellij/codeinsight/references/MapstructMappingQualifiedByNameReference.java +++ b/src/main/java/org/mapstruct/intellij/codeinsight/references/MapstructMappingQualifiedByNameReference.java @@ -111,8 +111,9 @@ private Stream findAllNamedMethodsFromThisAndReferencedMappers(@NotNu return Stream.empty(); } - Stream internalMethods = Stream.of( containingClass.getMethods() ) - .filter( MapstructUtil::isNamedMethod ); + Stream internalMethods = Stream.of( containingClass.getAllMethods() ) + .filter( MapstructUtil::isNamedMethod ) + .filter( m -> !m.hasModifierProperty( PsiModifier.PRIVATE ) ); Stream externalMethods = findNamedMethodsInUsedMappers( containingClass ) .filter( method -> methodIsAccessibleFrom( method, containingClass ) ); diff --git a/src/test/java/org/mapstruct/intellij/completion/MappingQualifiedByNameCompletionTestCase.java b/src/test/java/org/mapstruct/intellij/completion/MappingQualifiedByNameCompletionTestCase.java index b519caa1..de53e6c8 100644 --- a/src/test/java/org/mapstruct/intellij/completion/MappingQualifiedByNameCompletionTestCase.java +++ b/src/test/java/org/mapstruct/intellij/completion/MappingQualifiedByNameCompletionTestCase.java @@ -100,9 +100,11 @@ private void assertAutoCompleteOfValidVisibilities() { .extracting( LookupElement::getLookupString ) .containsExactlyInAnyOrder( "internalModifierPackagePrivate", - "internalModifierPrivate", "internalModifierProtected", "internalModifierPublic", + "superClassModifierPackagePrivate", + "superClassModifierProtected", + "superClassModifierPublic", "samePackageModifierPackagePrivate", "samePackageModifierPublic", "externalPackageModifierPublic" @@ -117,9 +119,19 @@ private void assertAutoCompleteOfValidVisibilities() { "String", " CarMapper#internalModifierPackagePrivate(String)" ), - createMethod( "internalModifierPrivate", "String", " CarMapper#internalModifierPrivate(String)" ), createMethod( "internalModifierProtected", "String", " CarMapper#internalModifierProtected(String)" ), createMethod( "internalModifierPublic", "String", " CarMapper#internalModifierPublic(String)" ), + createMethod( + "superClassModifierPackagePrivate", + "String", + " BaseMapper#superClassModifierPackagePrivate(String)" + ), + createMethod( + "superClassModifierProtected", + "String", + " BaseMapper#superClassModifierProtected(String)" + ), + createMethod( "superClassModifierPublic", "String", " BaseMapper#superClassModifierPublic(String)" ), createMethod( "samePackageModifierPackagePrivate", "String", diff --git a/testData/completion/qualifiedbyname/MappingQualifiedByNameWithAllPossibleVisibilities.java b/testData/completion/qualifiedbyname/MappingQualifiedByNameWithAllPossibleVisibilities.java index 57544538..298f6cf8 100644 --- a/testData/completion/qualifiedbyname/MappingQualifiedByNameWithAllPossibleVisibilities.java +++ b/testData/completion/qualifiedbyname/MappingQualifiedByNameWithAllPossibleVisibilities.java @@ -10,14 +10,15 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Named; +import org.mapstruct.ap.test.complex.BaseMapper; import org.mapstruct.ap.test.complex.SamePackageMapper; import org.mapstruct.helper.qualifiedbyname.external.ExternalMapper; @Mapper(uses = { ExternalMapper.class, SamePackageMapper.class }) -public interface CarMapper { +public abstract class CarMapper extends BaseMapper { @Mapping(target = "make", qualifiedByName = "") - CarDto carToCarDto(Car car); + public abstract CarDto carToCarDto(Car car); @Named("internalModifierPackagePrivate") String internalModifierPackagePrivate(String value) { diff --git a/testData/completion/qualifiedbyname/mapper/BaseMapper.java b/testData/completion/qualifiedbyname/mapper/BaseMapper.java new file mode 100644 index 00000000..e5424a62 --- /dev/null +++ b/testData/completion/qualifiedbyname/mapper/BaseMapper.java @@ -0,0 +1,34 @@ +/* + * 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 org.mapstruct.Mapper; +import org.mapstruct.Named; + +@Mapper +public abstract class BaseMapper { + + @Named("superClassModifierPackagePrivate") + String superClassModifierPackagePrivate(String value) { + return ""; + } + + @Named("superClassModifierPrivate") + private String superClassModifierPrivate(String value) { + return ""; + } + + @Named("superClassModifierProtected") + protected String superClassModifierProtected(String value) { + return ""; + } + + @Named("superClassModifierPublic") + public String superClassModifierPublic(String value) { + return ""; + } + +} From 531da0df67d6f0daf65661ea1bdaad285c762ef7 Mon Sep 17 00:00:00 2001 From: thunderhook <8238759+thunderhook@users.noreply.github.com> Date: Sat, 14 Jun 2025 23:42:38 +0200 Subject: [PATCH 3/3] #226: Fix reference for package private methods in the same package --- .../MapstructMappingQualifiedByNameReference.java | 4 ---- .../MappingQualifiedByNameCompletionTestCase.java | 6 ++++++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/mapstruct/intellij/codeinsight/references/MapstructMappingQualifiedByNameReference.java b/src/main/java/org/mapstruct/intellij/codeinsight/references/MapstructMappingQualifiedByNameReference.java index f3642457..d002f2a2 100644 --- a/src/main/java/org/mapstruct/intellij/codeinsight/references/MapstructMappingQualifiedByNameReference.java +++ b/src/main/java/org/mapstruct/intellij/codeinsight/references/MapstructMappingQualifiedByNameReference.java @@ -136,10 +136,6 @@ private boolean methodIsAccessibleFrom(PsiMethod method, PsiClass containingClas return true; } - if ( method.hasModifierProperty( PsiModifier.PROTECTED ) ) { - return methodClass.equals( containingClass ); - } - return haveSamePackage( containingClass, methodClass ); } diff --git a/src/test/java/org/mapstruct/intellij/completion/MappingQualifiedByNameCompletionTestCase.java b/src/test/java/org/mapstruct/intellij/completion/MappingQualifiedByNameCompletionTestCase.java index de53e6c8..c4704084 100644 --- a/src/test/java/org/mapstruct/intellij/completion/MappingQualifiedByNameCompletionTestCase.java +++ b/src/test/java/org/mapstruct/intellij/completion/MappingQualifiedByNameCompletionTestCase.java @@ -106,6 +106,7 @@ private void assertAutoCompleteOfValidVisibilities() { "superClassModifierProtected", "superClassModifierPublic", "samePackageModifierPackagePrivate", + "samePackageModifierProtected", "samePackageModifierPublic", "externalPackageModifierPublic" ); @@ -137,6 +138,11 @@ private void assertAutoCompleteOfValidVisibilities() { "String", " SamePackageMapper#samePackageModifierPackagePrivate(String)" ), + createMethod( + "samePackageModifierProtected", + "String", + " SamePackageMapper#samePackageModifierProtected(String)" + ), createMethod( "samePackageModifierPublic", "String",