Skip to content

Commit 8c3d9ee

Browse files
author
Stephan Leicht Vogt (C803964)
committed
#124 Add custom Immutables and FreeBuilder fluent setter MapstructUtils
1 parent c27f853 commit 8c3d9ee

14 files changed

+386
-22
lines changed

build.gradle

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ dependencies {
100100
testRuntimeOnly('org.junit.vintage:junit-vintage-engine')
101101
testImplementation('org.assertj:assertj-core:3.11.1')
102102
testImplementation('org.apache.commons:commons-text:1.10.0')
103+
testRuntimeOnly('org.immutables:value:2.5.6')
103104
}
104105

105106
task libs(type: Sync) {
@@ -111,6 +112,12 @@ task libs(type: Sync) {
111112
rename 'mapstruct-1.5.3.Final.jar', 'mapstruct.jar'
112113
}
113114

115+
task testLibs(type: Sync) {
116+
from configurations.testRuntimeClasspath
117+
into "$buildDir/test-libs"
118+
rename 'value-2.5.6.jar', 'immutables.jar'
119+
}
120+
114121
def mockJdkLocation = "https://github.com/JetBrains/intellij-community/raw/master/java/mock"
115122
def mockJdkDest = "$buildDir/mock"
116123
def downloadMockJdk(mockJdkLocation, mockJdkDest, mockJdkVersion) {
@@ -150,8 +157,8 @@ task downloadMockJdk11() {
150157
downloadMockJdk(mockJdkLocation, mockJdkDest, "JDK-11")
151158
}
152159

153-
test.dependsOn( libs, downloadMockJdk7, downloadMockJdk8, downloadMockJdk11 )
154-
prepareTestingSandbox.dependsOn( libs )
160+
test.dependsOn( libs, testLibs, downloadMockJdk7, downloadMockJdk8, downloadMockJdk11 )
161+
prepareTestingSandbox.dependsOn( libs, testLibs )
155162
prepareSandbox.dependsOn( libs )
156163

157164
test {

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 & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ builderSupportPresent && isBuilderEnabled( getMappingMethod() )
100100
if ( builderSupportPresent ) {
101101
for ( PsiMethod method : psiClass.findMethodsByName( value, true ) ) {
102102
if ( method.getParameterList().getParametersCount() == 1 &&
103-
MapstructUtil.isFluentSetter( method, typeToUse ) ) {
103+
mapstructUtil.isFluentSetter( method, typeToUse ) ) {
104104
return method;
105105
}
106106
}
@@ -140,7 +140,7 @@ PsiElement resolveInternal(@NotNull String value, @NotNull PsiMethod mappingMeth
140140
@Override
141141
Object[] getVariantsInternal(@NotNull PsiType psiType) {
142142
return asLookup(
143-
publicWriteAccessors( psiType, mapStructVersion, getMappingMethod() ),
143+
publicWriteAccessors( psiType, mapStructVersion, mapstructUtil, getMappingMethod() ),
144144
MapstructTargetReference::memberPsiType
145145
);
146146
}

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,17 +62,19 @@ public class UnmappedTargetPropertiesInspection extends InspectionBase {
6262
@NotNull
6363
@Override
6464
PsiElementVisitor buildVisitorInternal(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
65-
return new MyJavaElementVisitor( holder, MapstructUtil.resolveMapStructProjectVersion( holder.getFile() ) );
65+
return new MyJavaElementVisitor( holder, MapstructUtil.resolveMapStructProjectVersion( holder.getFile() ), MapstructUtil.getInstance( holder.getFile() ) );
6666
}
6767

6868
private static class MyJavaElementVisitor extends JavaElementVisitor {
6969
private final ProblemsHolder holder;
7070
private final MapStructVersion mapStructVersion;
71+
private final MapstructUtil mapstructUtil;
7172

72-
private MyJavaElementVisitor(ProblemsHolder holder, MapStructVersion mapStructVersion) {
73+
private MyJavaElementVisitor(ProblemsHolder holder, MapStructVersion mapStructVersion, MapstructUtil mapstructUtil) {
7374
this.holder = holder;
7475
this.mapStructVersion = mapStructVersion;
75-
}
76+
this.mapstructUtil = mapstructUtil;
77+
}
7678

7779
@Override
7880
public void visitMethod(PsiMethod method) {
@@ -92,7 +94,7 @@ public void visitMethod(PsiMethod method) {
9294
}
9395

9496

95-
Set<String> allTargetProperties = findAllTargetProperties( targetType, mapStructVersion, method );
97+
Set<String> allTargetProperties = findAllTargetProperties( targetType, mapStructVersion, mapstructUtil, method );
9698

9799
// find and remove all defined mapping targets
98100
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 http://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 http://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 http://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 & 4 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.
@@ -100,11 +100,26 @@ public final class MapstructUtil {
100100
private static final String INHERIT_INVERSE_CONFIGURATION_FQN = InheritInverseConfiguration.class.getName();
101101
private static final String BUILDER_ANNOTATION_FQN = Builder.class.getName();
102102
private static final String ENUM_MAPPING_ANNOTATION_FQN = EnumMapping.class.getName();
103+
private static final String IMMUTABLE_FQN = "org.immutables.value.Value.Immutable";
104+
private static final String FREE_BUILDER_FQN = "org.inferred.freebuilder.FreeBuilder";
103105

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

110125
public static LookupElement[] asLookup(Map<String, Pair<? extends PsiElement, PsiSubstitutor>> accessors,
@@ -140,7 +155,7 @@ public static LookupElement asLookup(PsiEnumConstant enumConstant) {
140155
}
141156

142157
public static LookupElement asLookupWithRepresentableText(PsiMethod method, String lookupString,
143-
String representableText, String tailText) {
158+
String representableText, String tailText) {
144159
LookupElementBuilder builder = LookupElementBuilder.create( method, lookupString )
145160
.withIcon( PlatformIcons.METHOD_ICON )
146161
.withPresentableText( representableText )
@@ -203,7 +218,7 @@ public static boolean isPublicModifiable(@NotNull PsiField field) {
203218
!field.hasModifierProperty( PsiModifier.FINAL );
204219
}
205220

206-
public static boolean isFluentSetter(@NotNull PsiMethod method, PsiType psiType) {
221+
public boolean isFluentSetter(@NotNull PsiMethod method, PsiType psiType) {
207222
return !psiType.getCanonicalText().startsWith( "java.lang" ) &&
208223
method.getReturnType() != null &&
209224
!isAdderWithUpperCase4thCharacter( method ) &&
@@ -541,6 +556,36 @@ else if ( JavaPsiFacade.getInstance( module.getProject() )
541556
} );
542557
}
543558

559+
public static boolean immutablesOnClassPath(@NotNull PsiFile psiFile) {
560+
Module module = ModuleUtilCore.findModuleForFile(psiFile.getVirtualFile(), psiFile.getProject());
561+
if (module == null) {
562+
return false;
563+
}
564+
return CachedValuesManager.getManager(module.getProject()).getCachedValue(module, () -> {
565+
boolean immutablesOnClassPath = JavaPsiFacade.getInstance(module.getProject())
566+
.findClass(IMMUTABLE_FQN, module.getModuleRuntimeScope(false)) != null;
567+
return CachedValueProvider.Result.createSingleDependency(
568+
immutablesOnClassPath,
569+
ProjectRootManager.getInstance(module.getProject())
570+
);
571+
});
572+
}
573+
574+
public static boolean freeBuilderOnClassPath(@NotNull PsiFile psiFile) {
575+
Module module = ModuleUtilCore.findModuleForFile(psiFile.getVirtualFile(), psiFile.getProject());
576+
if (module == null) {
577+
return false;
578+
}
579+
return CachedValuesManager.getManager(module.getProject()).getCachedValue(module, () -> {
580+
boolean freeBuilderOnClassPath = JavaPsiFacade.getInstance(module.getProject())
581+
.findClass(FREE_BUILDER_FQN, module.getModuleRuntimeScope(false)) != null;
582+
return CachedValueProvider.Result.createSingleDependency(
583+
freeBuilderOnClassPath,
584+
ProjectRootManager.getInstance(module.getProject())
585+
);
586+
});
587+
}
588+
544589
/**
545590
* Checks if MapStruct jdk8 is within the provided module. The MapStruct JDK 8 module is present when the
546591
* {@link Mapping} annotation is annotated with {@link java.lang.annotation.Repeatable}

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

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
import static org.mapstruct.intellij.util.MapstructUtil.INHERIT_CONFIGURATION_FQN;
4848
import static org.mapstruct.intellij.util.MapstructUtil.MAPPER_ANNOTATION_FQN;
4949
import static org.mapstruct.intellij.util.MapstructUtil.canDescendIntoType;
50-
import static org.mapstruct.intellij.util.MapstructUtil.isFluentSetter;
5150
import static org.mapstruct.intellij.util.MapstructUtil.publicFields;
5251

5352
/**
@@ -98,7 +97,7 @@ public static PsiType getRelevantType(@NotNull PsiMethod mappingMethod) {
9897
* @return a stream that holds all public write accessors for the given {@code psiType}
9998
*/
10099
public static Map<String, Pair<? extends PsiElement, PsiSubstitutor>> publicWriteAccessors(@NotNull PsiType psiType,
101-
MapStructVersion mapStructVersion, PsiMethod mappingMethod) {
100+
MapStructVersion mapStructVersion, MapstructUtil mapstructUtil, PsiMethod mappingMethod) {
102101
boolean builderSupportPresent = mapStructVersion.isBuilderSupported();
103102
Pair<PsiClass, TargetType> classAndType = resolveBuilderOrSelfClass(
104103
psiType,
@@ -114,7 +113,7 @@ builderSupportPresent && isBuilderEnabled( mappingMethod )
114113
TargetType targetType = classAndType.getSecond();
115114
PsiType typeToUse = targetType.type();
116115

117-
publicWriteAccessors.putAll( publicSetters( psiClass, typeToUse, builderSupportPresent ) );
116+
publicWriteAccessors.putAll( publicSetters( psiClass, typeToUse, mapstructUtil, builderSupportPresent && isBuilderEnabled( mappingMethod ) ) );
118117
publicWriteAccessors.putAll( publicFields( psiClass ) );
119118

120119
if ( mapStructVersion.isConstructorSupported() && !targetType.builder() ) {
@@ -266,7 +265,7 @@ public static PsiMethod resolveMappingConstructor(@NotNull PsiClass psiClass) {
266265
* @return a stream that holds all public setters for the given {@code psiType}
267266
*/
268267
private static Map<String, Pair<? extends PsiMember, PsiSubstitutor>> publicSetters(@NotNull PsiClass psiClass,
269-
@NotNull PsiType typeToUse,
268+
@NotNull PsiType typeToUse, MapstructUtil mapstructUtil,
270269
boolean builderSupportPresent) {
271270
Set<PsiMethod> overriddenMethods = new HashSet<>();
272271
Map<String, Pair<? extends PsiMember, PsiSubstitutor>> publicSetters = new LinkedHashMap<>();
@@ -275,7 +274,7 @@ private static Map<String, Pair<? extends PsiMember, PsiSubstitutor>> publicSett
275274
if ( method.isConstructor() ) {
276275
continue;
277276
}
278-
String propertyName = extractPublicSetterPropertyName( method, typeToUse, builderSupportPresent );
277+
String propertyName = extractPublicSetterPropertyName( method, typeToUse, mapstructUtil, builderSupportPresent );
279278

280279
if ( propertyName != null &&
281280
!overriddenMethods.contains( method ) ) {
@@ -289,7 +288,7 @@ private static Map<String, Pair<? extends PsiMember, PsiSubstitutor>> publicSett
289288
}
290289

291290
@Nullable
292-
private static String extractPublicSetterPropertyName(PsiMethod method, @NotNull PsiType typeToUse,
291+
private static String extractPublicSetterPropertyName(PsiMethod method, @NotNull PsiType typeToUse, MapstructUtil mapstructUtil,
293292
boolean builderSupportPresent) {
294293
if ( method.getParameterList().getParametersCount() != 1 || !MapstructUtil.isPublicNonStatic( method ) ) {
295294
// If the method does not have 1 parameter or is not public then there is no property
@@ -299,7 +298,7 @@ private static String extractPublicSetterPropertyName(PsiMethod method, @NotNull
299298
// This logic is aligned with the DefaultAccessorNamingStrategy
300299
String methodName = method.getName();
301300
if ( builderSupportPresent ) {
302-
if ( isFluentSetter( method, typeToUse ) ) {
301+
if ( mapstructUtil.isFluentSetter( method, typeToUse ) ) {
303302
if ( methodName.startsWith( "set" )
304303
&& methodName.length() > 3
305304
&& Character.isUpperCase( methodName.charAt( 3 ) ) ) {
@@ -432,9 +431,9 @@ public static Stream<String> findAllSourcePropertiesForCurrentTarget(@NotNull Ps
432431
*
433432
* @return all target properties for the given {@code targetClass}
434433
*/
435-
public static Set<String> findAllTargetProperties(@NotNull PsiType targetType, MapStructVersion mapStructVersion,
434+
public static Set<String> findAllTargetProperties(@NotNull PsiType targetType, MapStructVersion mapStructVersion, MapstructUtil mapstructUtil,
436435
PsiMethod mappingMethod) {
437-
return publicWriteAccessors( targetType, mapStructVersion, mappingMethod ).keySet();
436+
return publicWriteAccessors( targetType, mapStructVersion, mapstructUtil, mappingMethod ).keySet();
438437
}
439438

440439
/**

src/test/java/org/mapstruct/intellij/MapstructBaseCompletionTestCase.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
*/
3232
public abstract class MapstructBaseCompletionTestCase extends LightFixtureCompletionTestCase {
3333

34-
private static final String BUILD_LIBS_DIRECTORY = "build/libs";
34+
protected static final String BUILD_LIBS_DIRECTORY = "build/libs";
35+
protected static final String BUILD_TEST_LIBS_DIRECTORY = "build/test-libs";
3536
private static final String BUILD_MOCK_JDK_DIRECTORY = "build/mockJDK-";
3637

3738
@Override

0 commit comments

Comments
 (0)