Skip to content

Commit 236c1e5

Browse files
Stephan Leicht VogtLeicht Vogt Stephan
authored andcommitted
#124 Add custom Immutables and FreeBuilder fluent setter MapstructUtils
1 parent fe429d9 commit 236c1e5

15 files changed

+399
-22
lines changed

build.gradle

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ dependencies {
114114
testImplementation('org.assertj:assertj-core:3.26.3')
115115
testImplementation('org.apache.commons:commons-text:1.12.0')
116116
testImplementation( 'junit:junit:4.13.2' )
117+
testRuntimeOnly('org.immutables:value:2.10.1')
117118
}
118119

119120
task libs(type: Sync) {
@@ -126,6 +127,13 @@ task libs(type: Sync) {
126127
rename 'mapstruct-1.5.3.Final.jar', 'mapstruct.jar'
127128
}
128129

130+
task testLibs(type: Sync) {
131+
from configurations.testRuntimeClasspath
132+
into "$buildDir/test-libs"
133+
rename 'value-2.10.1.jar', 'immutables.jar'
134+
}
135+
136+
test.dependsOn( libs, testLibs )
129137
prepareSandbox.dependsOn( libs )
130138
composedJar.dependsOn( libs )
131139

src/main/java/org/mapstruct/intellij/codeinsight/references/MapstructBaseReference.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ abstract class MapstructBaseReference extends BaseReference {
3434

3535
private final MapstructBaseReference previous;
3636
private final String value;
37+
protected final MapstructUtil mapstructUtil;
3738

3839
/**
3940
* Create a reference.
@@ -47,6 +48,7 @@ abstract class MapstructBaseReference extends BaseReference {
4748
super( element, rangeInElement );
4849
this.previous = previous;
4950
this.value = value;
51+
this.mapstructUtil = MapstructUtil.getInstance( element.getContainingFile() );
5052
}
5153

5254
@Override

src/main/java/org/mapstruct/intellij/codeinsight/references/MapstructTargetReference.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ builderSupportPresent && isBuilderEnabled( getMappingMethod() )
106106
if ( builderSupportPresent ) {
107107
for ( PsiMethod method : psiClass.findMethodsByName( value, true ) ) {
108108
if ( method.getParameterList().getParametersCount() == 1 &&
109-
MapstructUtil.isFluentSetter( method, typeToUse ) ) {
109+
mapstructUtil.isFluentSetter( method, typeToUse ) ) {
110110
return method;
111111
}
112112
}
@@ -151,6 +151,7 @@ Object[] getVariantsInternal(@NotNull PsiType psiType) {
151151
Map<String, Pair<? extends PsiElement, PsiSubstitutor>> accessors = publicWriteAccessors(
152152
psiType,
153153
mapStructVersion,
154+
mapstructUtil,
154155
mappingMethod
155156
);
156157

src/main/java/org/mapstruct/intellij/inspection/FromMapMappingMapTypeInspection.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,21 @@ public class FromMapMappingMapTypeInspection extends InspectionBase {
4040
@NotNull
4141
@Override
4242
PsiElementVisitor buildVisitorInternal(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
43-
return new MyJavaElementVisitor( holder, MapstructUtil.resolveMapStructProjectVersion( holder.getFile() ) );
43+
return new MyJavaElementVisitor(
44+
holder, MapstructUtil.resolveMapStructProjectVersion( holder.getFile() ),
45+
MapstructUtil.getInstance( holder.getFile() ) );
4446
}
4547

4648
private static class MyJavaElementVisitor extends JavaElementVisitor {
4749
private final ProblemsHolder holder;
4850
private final MapStructVersion mapStructVersion;
51+
private final MapstructUtil mapstructUtil;
4952

50-
private MyJavaElementVisitor(ProblemsHolder holder, MapStructVersion mapStructVersion) {
51-
this.holder = holder;
52-
this.mapStructVersion = mapStructVersion;
53+
private MyJavaElementVisitor(
54+
ProblemsHolder holder, MapStructVersion mapStructVersion, MapstructUtil mapstructUtil) {
55+
this.holder = holder;
56+
this.mapStructVersion = mapStructVersion;
57+
this.mapstructUtil = mapstructUtil;
5358
}
5459

5560
@Override
@@ -73,7 +78,8 @@ public void visitMethod(@NotNull PsiMethod method) {
7378
if (parameters == null) {
7479
return;
7580
}
76-
Set<String> allTargetProperties = findAllTargetProperties( targetType, mapStructVersion, method );
81+
Set<String> allTargetProperties = findAllTargetProperties(
82+
targetType, mapStructVersion, mapstructUtil, method );
7783
if ( allTargetProperties.contains( fromMapMappingParameter.getName() ) ) {
7884
return;
7985
}

src/main/java/org/mapstruct/intellij/inspection/UnmappedTargetPropertiesInspection.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,16 +59,21 @@ public class UnmappedTargetPropertiesInspection extends InspectionBase {
5959
@NotNull
6060
@Override
6161
PsiElementVisitor buildVisitorInternal(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
62-
return new MyJavaElementVisitor( holder, MapstructUtil.resolveMapStructProjectVersion( holder.getFile() ) );
62+
return new MyJavaElementVisitor(
63+
holder, MapstructUtil.resolveMapStructProjectVersion( holder.getFile() ),
64+
MapstructUtil.getInstance( holder.getFile() ) );
6365
}
6466

6567
private static class MyJavaElementVisitor extends JavaElementVisitor {
6668
private final ProblemsHolder holder;
6769
private final MapStructVersion mapStructVersion;
70+
private final MapstructUtil mapstructUtil;
6871

69-
private MyJavaElementVisitor(ProblemsHolder holder, MapStructVersion mapStructVersion) {
72+
private MyJavaElementVisitor(
73+
ProblemsHolder holder, MapStructVersion mapStructVersion, MapstructUtil mapstructUtil) {
7074
this.holder = holder;
7175
this.mapStructVersion = mapStructVersion;
76+
this.mapstructUtil = mapstructUtil;
7277
}
7378

7479
@Override
@@ -97,7 +102,8 @@ public void visitMethod(PsiMethod method) {
97102
return;
98103
}
99104

100-
Set<String> allTargetProperties = findAllTargetProperties( targetType, mapStructVersion, method );
105+
Set<String> allTargetProperties = findAllTargetProperties(
106+
targetType, mapStructVersion, mapstructUtil, method );
101107

102108
// find and remove all defined mapping targets
103109
Set<String> definedTargets = findAllDefinedMappingTargets( method, mapStructVersion )
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
package org.mapstruct.intellij.util;
7+
8+
public class DefaultMapstructUtil extends MapstructUtil {
9+
/**
10+
* Hide constructor.
11+
*/
12+
protected DefaultMapstructUtil() {
13+
}
14+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
package org.mapstruct.intellij.util;
7+
8+
import com.intellij.psi.PsiMethod;
9+
import com.intellij.psi.PsiType;
10+
import org.jetbrains.annotations.NotNull;
11+
12+
/**
13+
* Mapstruct util for FreeBuilder.
14+
* FreeBuilder adds a lot of other methods that can be considered as fluent setters. Such as:
15+
* <ul>
16+
* <li>{@code from(Target)}</li>
17+
* <li>{@code mapXXX(UnaryOperator)}</li>
18+
* <li>{@code mutateXXX(Consumer)}</li>
19+
* <li>{@code mergeFrom(Target)}</li>
20+
* <li>{@code mergeFrom(Target.Builder)}</li>
21+
* </ul>
22+
* <p>
23+
* When the JavaBean convention is not used with FreeBuilder then the getters are non-standard and MapStruct
24+
* won't recognize them. Therefore, one needs to use the JavaBean convention in which the fluent setters
25+
* start with {@code set}.
26+
*/
27+
public class FreeBuildersMapstructUtil extends MapstructUtil {
28+
/**
29+
* Hide constructor.
30+
*/
31+
protected FreeBuildersMapstructUtil() {
32+
}
33+
34+
@Override
35+
public boolean isFluentSetter(@NotNull PsiMethod method, PsiType psiType) {
36+
// When using FreeBuilder one needs to use the JavaBean convention, which means that all setters will start
37+
// with set
38+
return false;
39+
}
40+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
package org.mapstruct.intellij.util;
7+
8+
import com.intellij.psi.PsiMethod;
9+
import com.intellij.psi.PsiType;
10+
import org.jetbrains.annotations.NotNull;
11+
12+
/**
13+
* Mapstruct util for Immutables.
14+
* The generated Immutables also have a from that works as a copy. Our default strategy considers this method
15+
* as a setter with a name {@code from}. Therefore, we are ignoring it.
16+
*/
17+
public class ImmutablesMapstructUtil extends MapstructUtil {
18+
/**
19+
* Hide constructor.
20+
*/
21+
protected ImmutablesMapstructUtil() {
22+
}
23+
24+
@Override
25+
public boolean isFluentSetter(@NotNull PsiMethod method, PsiType psiType) {
26+
return super.isFluentSetter( method, psiType ) && !method.getName().equals( "from" );
27+
}
28+
}

src/main/java/org/mapstruct/intellij/util/MapstructUtil.java

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
/**
7171
* @author Filip Hrisafov
7272
*/
73-
public final class MapstructUtil {
73+
public class MapstructUtil {
7474

7575
/**
7676
* The FQN of the {@link Mapper} annotation.
@@ -101,11 +101,27 @@ public final class MapstructUtil {
101101
private static final String CONTEXT_ANNOTATION_FQN = Context.class.getName();
102102
private static final String BUILDER_ANNOTATION_FQN = Builder.class.getName();
103103
private static final String ENUM_MAPPING_ANNOTATION_FQN = EnumMapping.class.getName();
104+
private static final String IMMUTABLE_FQN = "org.immutables.value.Value.Immutable";
105+
private static final String FREE_BUILDER_FQN = "org.inferred.freebuilder.FreeBuilder";
104106

105107
/**
106108
* Hide constructor.
107109
*/
108-
private MapstructUtil() {
110+
MapstructUtil() {
111+
}
112+
113+
public static MapstructUtil getInstance(@Nullable PsiFile psiFile) {
114+
MapstructUtil mapstructUtil = new DefaultMapstructUtil();
115+
if (psiFile == null) {
116+
return mapstructUtil;
117+
}
118+
if (MapstructUtil.immutablesOnClassPath( psiFile )) {
119+
mapstructUtil = new ImmutablesMapstructUtil();
120+
}
121+
else if (MapstructUtil.freeBuilderOnClassPath( psiFile )) {
122+
mapstructUtil = new FreeBuildersMapstructUtil();
123+
}
124+
return mapstructUtil;
109125
}
110126

111127
public static LookupElement[] asLookup(Map<String, Pair<? extends PsiElement, PsiSubstitutor>> accessors,
@@ -204,7 +220,7 @@ public static boolean isPublicModifiable(@NotNull PsiField field) {
204220
!field.hasModifierProperty( PsiModifier.FINAL );
205221
}
206222

207-
public static boolean isFluentSetter(@NotNull PsiMethod method, PsiType psiType) {
223+
public boolean isFluentSetter(@NotNull PsiMethod method, PsiType psiType) {
208224
return !psiType.getCanonicalText().startsWith( "java.lang" ) &&
209225
method.getReturnType() != null &&
210226
!isAdderWithUpperCase4thCharacter( method ) &&
@@ -560,6 +576,36 @@ else if ( JavaPsiFacade.getInstance( module.getProject() )
560576
} );
561577
}
562578

579+
public static boolean immutablesOnClassPath(@NotNull PsiFile psiFile) {
580+
Module module = ModuleUtilCore.findModuleForFile( psiFile.getVirtualFile(), psiFile.getProject() );
581+
if (module == null) {
582+
return false;
583+
}
584+
return CachedValuesManager.getManager( module.getProject() ).getCachedValue( module, () -> {
585+
boolean immutablesOnClassPath = JavaPsiFacade.getInstance( module.getProject() )
586+
.findClass( IMMUTABLE_FQN, module.getModuleRuntimeScope( false ) ) != null;
587+
return CachedValueProvider.Result.createSingleDependency(
588+
immutablesOnClassPath,
589+
ProjectRootManager.getInstance( module.getProject() )
590+
);
591+
} );
592+
}
593+
594+
public static boolean freeBuilderOnClassPath(@NotNull PsiFile psiFile) {
595+
Module module = ModuleUtilCore.findModuleForFile( psiFile.getVirtualFile(), psiFile.getProject() );
596+
if (module == null) {
597+
return false;
598+
}
599+
return CachedValuesManager.getManager( module.getProject() ).getCachedValue( module, () -> {
600+
boolean freeBuilderOnClassPath = JavaPsiFacade.getInstance( module.getProject() )
601+
.findClass( FREE_BUILDER_FQN, module.getModuleRuntimeScope( false ) ) != null;
602+
return CachedValueProvider.Result.createSingleDependency(
603+
freeBuilderOnClassPath,
604+
ProjectRootManager.getInstance( module.getProject() )
605+
);
606+
} );
607+
}
608+
563609
/**
564610
* Checks if MapStruct jdk8 is within the provided module. The MapStruct JDK 8 module is present when the
565611
* {@link Mapping} annotation is annotated with {@link java.lang.annotation.Repeatable}

src/main/java/org/mapstruct/intellij/util/TargetUtils.java

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@
4646
import static org.mapstruct.intellij.util.MapstructAnnotationUtils.findMapperConfigReference;
4747
import static org.mapstruct.intellij.util.MapstructUtil.MAPPER_ANNOTATION_FQN;
4848
import static org.mapstruct.intellij.util.MapstructUtil.canDescendIntoType;
49-
import static org.mapstruct.intellij.util.MapstructUtil.isFluentSetter;
5049
import static org.mapstruct.intellij.util.MapstructUtil.isInheritInverseConfiguration;
5150
import static org.mapstruct.intellij.util.MapstructUtil.isMapper;
5251
import static org.mapstruct.intellij.util.MapstructUtil.isMapperConfig;
@@ -100,7 +99,7 @@ public static PsiType getRelevantType(@NotNull PsiMethod mappingMethod) {
10099
* @return a stream that holds all public write accessors for the given {@code psiType}
101100
*/
102101
public static Map<String, Pair<? extends PsiElement, PsiSubstitutor>> publicWriteAccessors(@NotNull PsiType psiType,
103-
MapStructVersion mapStructVersion, PsiMethod mappingMethod) {
102+
MapStructVersion mapStructVersion, MapstructUtil mapstructUtil, PsiMethod mappingMethod) {
104103
boolean builderSupportPresent = mapStructVersion.isBuilderSupported();
105104
Pair<PsiClass, TargetType> classAndType = resolveBuilderOrSelfClass(
106105
psiType,
@@ -116,7 +115,8 @@ builderSupportPresent && isBuilderEnabled( mappingMethod )
116115
TargetType targetType = classAndType.getSecond();
117116
PsiType typeToUse = targetType.type();
118117

119-
publicWriteAccessors.putAll( publicSetters( psiClass, typeToUse, builderSupportPresent ) );
118+
publicWriteAccessors.putAll( publicSetters( psiClass, typeToUse, mapstructUtil,
119+
builderSupportPresent && isBuilderEnabled( mappingMethod ) ) );
120120
publicWriteAccessors.putAll( publicFields( psiClass ) );
121121

122122
if ( mapStructVersion.isConstructorSupported() && !targetType.builder() ) {
@@ -268,7 +268,7 @@ public static PsiMethod resolveMappingConstructor(@NotNull PsiClass psiClass) {
268268
* @return a stream that holds all public setters for the given {@code psiType}
269269
*/
270270
private static Map<String, Pair<? extends PsiMember, PsiSubstitutor>> publicSetters(@NotNull PsiClass psiClass,
271-
@NotNull PsiType typeToUse,
271+
@NotNull PsiType typeToUse, MapstructUtil mapstructUtil,
272272
boolean builderSupportPresent) {
273273
Set<PsiMethod> overriddenMethods = new HashSet<>();
274274
Map<String, Pair<? extends PsiMember, PsiSubstitutor>> publicSetters = new LinkedHashMap<>();
@@ -277,7 +277,8 @@ private static Map<String, Pair<? extends PsiMember, PsiSubstitutor>> publicSett
277277
if ( method.isConstructor() ) {
278278
continue;
279279
}
280-
String propertyName = extractPublicSetterPropertyName( method, typeToUse, builderSupportPresent );
280+
String propertyName = extractPublicSetterPropertyName(
281+
method, typeToUse, mapstructUtil, builderSupportPresent );
281282

282283
if ( propertyName != null &&
283284
!overriddenMethods.contains( method ) ) {
@@ -292,7 +293,7 @@ private static Map<String, Pair<? extends PsiMember, PsiSubstitutor>> publicSett
292293

293294
@Nullable
294295
private static String extractPublicSetterPropertyName(PsiMethod method, @NotNull PsiType typeToUse,
295-
boolean builderSupportPresent) {
296+
MapstructUtil mapstructUtil, boolean builderSupportPresent) {
296297
if (!MapstructUtil.isPublicNonStatic( method )) {
297298
// If the method is not public then there is no property
298299
return null;
@@ -312,7 +313,7 @@ private static String extractPublicSetterPropertyName(PsiMethod method, @NotNull
312313
}
313314

314315
// This logic is aligned with the DefaultAccessorNamingStrategy
315-
if ( builderSupportPresent && isFluentSetter( method, typeToUse )) {
316+
if ( builderSupportPresent && mapstructUtil.isFluentSetter( method, typeToUse )) {
316317
if ( methodName.startsWith( "set" )
317318
&& methodName.length() > 3
318319
&& Character.isUpperCase( methodName.charAt( 3 ) ) ) {
@@ -435,8 +436,8 @@ public static Stream<String> findAllSourcePropertiesForCurrentTarget(@NotNull Ps
435436
* @return all target properties for the given {@code targetClass}
436437
*/
437438
public static Set<String> findAllTargetProperties(@NotNull PsiType targetType, MapStructVersion mapStructVersion,
438-
PsiMethod mappingMethod) {
439-
return publicWriteAccessors( targetType, mapStructVersion, mappingMethod ).keySet();
439+
MapstructUtil mapstructUtil, PsiMethod mappingMethod) {
440+
return publicWriteAccessors( targetType, mapStructVersion, mapstructUtil, mappingMethod ).keySet();
440441
}
441442

442443
/**

0 commit comments

Comments
 (0)