diff --git a/build.gradle b/build.gradle index d21b9d81..a07efd4f 100644 --- a/build.gradle +++ b/build.gradle @@ -111,7 +111,7 @@ dependencies { testFramework( TestFrameworkType.Platform.INSTANCE ) testFramework( TestFrameworkType.Bundled.INSTANCE ) } - implementation('org.mapstruct:mapstruct:1.5.3.Final') + implementation('org.mapstruct:mapstruct:1.7.0-SNAPSHOT') testImplementation(platform('org.junit:junit-bom:5.11.0')) testImplementation('org.junit.platform:junit-platform-launcher') testImplementation('org.junit.jupiter:junit-jupiter-api') @@ -130,7 +130,7 @@ tasks.register('libs', Sync) { include('mapstruct-intellij-*.jar') include('MapStruct-Intellij-*.jar') } - rename('mapstruct-1.5.3.Final.jar', 'mapstruct.jar') + rename('mapstruct-1.7.0-SNAPSHOT.jar', 'mapstruct.jar') } tasks.register('testLibs', Sync) { diff --git a/src/main/java/org/mapstruct/intellij/util/MapStructVersion.java b/src/main/java/org/mapstruct/intellij/util/MapStructVersion.java index 77e69b5b..2e00d8d3 100644 --- a/src/main/java/org/mapstruct/intellij/util/MapStructVersion.java +++ b/src/main/java/org/mapstruct/intellij/util/MapStructVersion.java @@ -10,16 +10,19 @@ */ public enum MapStructVersion { - V1_2_O( false, false ), - V1_3_O( true, false ), - V1_4_O( true, true ); + V1_2_O( false, false, false ), + V1_3_O( true, false, false ), + V1_4_O( true, true, false ), + V1_7_O( true, true, true ),; private final boolean builderSupported; private final boolean constructorSupported; + private final boolean ignoringRemovers; - MapStructVersion(boolean builderSupported, boolean constructorSupported) { + MapStructVersion(boolean builderSupported, boolean constructorSupported, boolean ignoringRemovers) { this.builderSupported = builderSupported; this.constructorSupported = constructorSupported; + this.ignoringRemovers = ignoringRemovers; } public boolean isBuilderSupported() { @@ -29,4 +32,8 @@ public boolean isBuilderSupported() { public boolean isConstructorSupported() { return constructorSupported; } + + public boolean isIgnoringRemovers() { + return ignoringRemovers; + } } diff --git a/src/main/java/org/mapstruct/intellij/util/MapstructUtil.java b/src/main/java/org/mapstruct/intellij/util/MapstructUtil.java index b0ac84c8..38302a65 100644 --- a/src/main/java/org/mapstruct/intellij/util/MapstructUtil.java +++ b/src/main/java/org/mapstruct/intellij/util/MapstructUtil.java @@ -6,11 +6,15 @@ package org.mapstruct.intellij.util; import java.beans.Introspector; +import java.io.IOException; +import java.io.InputStream; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Function; +import java.util.jar.Manifest; import java.util.stream.Stream; import javax.swing.Icon; @@ -21,6 +25,8 @@ import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.Version; +import com.intellij.openapi.vfs.JarFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.CommonClassNames; import com.intellij.psi.EmptySubstitutor; @@ -61,6 +67,7 @@ import org.mapstruct.MappingTarget; import org.mapstruct.Mappings; import org.mapstruct.Named; +//import org.mapstruct.PrimaryMappingSource; import org.mapstruct.ValueMapping; import org.mapstruct.ValueMappings; import org.mapstruct.factory.Mappers; @@ -229,9 +236,15 @@ public boolean isFluentSetter(@NotNull PsiMethod method, PsiType psiType, @NotNu return !psiType.getCanonicalText().startsWith( "java.lang" ) && method.getReturnType() != null && !isAdderWithUpperCase4thCharacter( method ) && + !isIgnoredRemover( method ) && isAssignableFromReturnTypeOrSuperTypes( psiType, substitutor.substitute( method.getReturnType() ) ); } + private static boolean isIgnoredRemover(@NotNull PsiMethod method) { + return isRemoverWithUpperCase7thCharacter( method ) + && resolveMapStructProjectVersion( method.getContainingFile() ).isIgnoringRemovers(); + } + private static boolean isAssignableFromReturnTypeOrSuperTypes(PsiType psiType, @NotNull PsiType returnType) { if ( isAssignableFrom( psiType, returnType ) ) { @@ -260,6 +273,13 @@ private static boolean isAdderWithUpperCase4thCharacter(@NotNull PsiMethod metho Character.isUpperCase( methodName.charAt( 3 ) ); } + private static boolean isRemoverWithUpperCase7thCharacter(@NotNull PsiMethod method) { + String methodName = method.getName(); + return methodName.startsWith( "remove" ) && + methodName.length() > 6 && + Character.isUpperCase( methodName.charAt( 6 ) ); + } + /** * Checks if the {@code method} is a possible builder creation method. *

@@ -567,7 +587,12 @@ public static MapStructVersion resolveMapStructProjectVersion(@NotNull PsiFile p } return CachedValuesManager.getManager( module.getProject() ).getCachedValue( module, () -> { MapStructVersion mapStructVersion; - if ( JavaPsiFacade.getInstance( module.getProject() ) + + Version version = resolveImplementationVersion( module ); + if ( version != null && version.isOrGreaterThan( 1, 7 ) ) { + mapStructVersion = MapStructVersion.V1_7_O; + } + else if ( JavaPsiFacade.getInstance( module.getProject() ) .findClass( ENUM_MAPPING_ANNOTATION_FQN, module.getModuleRuntimeScope( false ) ) != null ) { mapStructVersion = MapStructVersion.V1_4_O; } @@ -659,4 +684,39 @@ public static boolean isInheritInverseConfiguration(PsiMethod method) { return isAnnotated( method, INHERIT_INVERSE_CONFIGURATION_FQN, AnnotationUtil.CHECK_TYPE ); } + @Nullable + private static Version resolveImplementationVersion(Module module) { + JarFileSystem jarFileSystem = JarFileSystem.getInstance(); + return Optional.ofNullable( JavaPsiFacade + .getInstance( module.getProject() ) + .findClass( MAPPER_ANNOTATION_FQN, module.getModuleRuntimeScope( false ) ) ) + .map( PsiClass::getContainingFile ) + .map( PsiFile::getVirtualFile ) + .map( jarFileSystem::getVirtualFileForJar ) + .map( jarFileSystem::getJarRootForLocalFile ) + .map( jarRoot -> jarRoot.findFileByRelativePath( "META-INF/MANIFEST.MF" ) ) + .map( MapstructUtil::resolveManifest ) + .map( MapstructUtil::resolveVersionString ) + .map( Version::parseVersion ) + .orElse( null ); + } + + private static @Nullable Manifest resolveManifest(VirtualFile manifestFile) { + try ( InputStream is = manifestFile.getInputStream() ) { + return new Manifest(is); + } + catch ( IOException e ) { + return null; + } + } + + private static @Nullable String resolveVersionString(Manifest manifest) { + if ( manifest.getMainAttributes().containsKey( "Implementation-Version" ) ) { + return manifest.getMainAttributes().getValue( "Implementation-Version" ); + } + else { + return manifest.getMainAttributes().getValue( "Bundle-Version" ); + } + } + } diff --git a/testData/mapping/dto/CarDto.java b/testData/mapping/dto/CarDto.java index d31feee4..45ac5447 100644 --- a/testData/mapping/dto/CarDto.java +++ b/testData/mapping/dto/CarDto.java @@ -83,6 +83,12 @@ public void addPassenger(PersonDto passenger) { this.passengers.add( passenger ); } + public void removePassenger(PersonDTO passenger) { + if ( this.passengers != null ) { + this.passengers.remove( passenger ); + } + } + public Long getPrice() { return price; } diff --git a/testData/mapping/dto/FluentCarDto.java b/testData/mapping/dto/FluentCarDto.java index 7b50b86e..c16cb02a 100644 --- a/testData/mapping/dto/FluentCarDto.java +++ b/testData/mapping/dto/FluentCarDto.java @@ -75,6 +75,13 @@ public FluentCarDto addPassenger(PersonDto passenger) { return this; } + public FluentCarDto removePassenger(PersonDto passenger) { + if ( this.passengers != null ) { + this.passengers.remove( passenger ); + } + return this; + } + public Long getPrice() { return price; }