Skip to content

Commit

Permalink
#159 Support fluent setters with super types (e.g. lomboks @SuperBuilder
Browse files Browse the repository at this point in the history
)
  • Loading branch information
thunderhook authored Mar 24, 2024
1 parent 8e25048 commit 27c6cfc
Show file tree
Hide file tree
Showing 5 changed files with 359 additions and 7 deletions.
32 changes: 25 additions & 7 deletions src/main/java/org/mapstruct/intellij/util/MapstructUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ private MapstructUtil() {
}

public static LookupElement[] asLookup(Map<String, Pair<? extends PsiElement, PsiSubstitutor>> accessors,
Function<PsiElement, PsiType> typeMapper) {
Function<PsiElement, PsiType> typeMapper) {
if ( !accessors.isEmpty() ) {
LookupElement[] lookupElements = new LookupElement[accessors.size()];
int index = 0;
Expand Down Expand Up @@ -163,7 +163,7 @@ public static <T extends PsiElement> LookupElement asLookup(String propertyName,
}

public static LookupElement asLookup(String propertyName, @NotNull Pair<? extends PsiElement, PsiSubstitutor> pair,
Function<PsiElement, PsiType> typeMapper, Icon icon) {
Function<PsiElement, PsiType> typeMapper, Icon icon) {
PsiElement member = pair.getFirst();
PsiSubstitutor substitutor = pair.getSecond();

Expand Down Expand Up @@ -200,17 +200,35 @@ private static boolean isPublic(@NotNull PsiField field) {

public static boolean isPublicModifiable(@NotNull PsiField field) {
return isPublicNonStatic( field ) &&
!field.hasModifierProperty( PsiModifier.FINAL );
!field.hasModifierProperty( PsiModifier.FINAL );
}

public static boolean isFluentSetter(@NotNull PsiMethod method, PsiType psiType) {
return !psiType.getCanonicalText().startsWith( "java.lang" ) &&
method.getReturnType() != null &&
!isAdderWithUpperCase4thCharacter( method ) &&
TypeConversionUtil.isAssignable(
psiType,
PsiUtil.resolveGenericsClassInType( psiType ).getSubstitutor().substitute( method.getReturnType() )
);
isAssignableFromReturnTypeOrSuperTypes( psiType, method.getReturnType() );
}

private static boolean isAssignableFromReturnTypeOrSuperTypes(PsiType psiType, PsiType returnType) {

if ( isAssignableFrom( psiType, returnType ) ) {
return true;
}

for ( PsiType superType : returnType.getSuperTypes() ) {
if ( isAssignableFrom( psiType, superType ) ) {
return true;
}
}
return false;
}

private static boolean isAssignableFrom(PsiType psiType, @Nullable PsiType returnType) {
return TypeConversionUtil.isAssignable(
psiType,
PsiUtil.resolveGenericsClassInType( psiType ).getSubstitutor().substitute( returnType )
);
}

private static boolean isAdderWithUpperCase4thCharacter(@NotNull PsiMethod method) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.intellij.inspection;

import java.util.List;

import com.intellij.codeInsight.intention.IntentionAction;
import org.jetbrains.annotations.NotNull;

import static org.assertj.core.api.Assertions.assertThat;

/**
* @author Oliver Erhart
*/
public class UnmappedSuperBuilderTargetPropertiesInspectionTest extends BaseInspectionTest {

@NotNull
@Override
protected Class<UnmappedTargetPropertiesInspection> getInspection() {
return UnmappedTargetPropertiesInspection.class;
}

@Override
protected void setUp() throws Exception {
super.setUp();
myFixture.copyFileToProject(
"UnmappedSuperBuilderTargetPropertiesData.java",
"org/example/data/UnmappedSuperBuilderTargetPropertiesData.java"
);
}

public void testUnmappedSuperBuilderTargetProperties() {
doTest();
String testName = getTestName( false );
List<IntentionAction> allQuickFixes = myFixture.getAllQuickFixes();

assertThat( allQuickFixes )
.extracting( IntentionAction::getText )
.as( "Intent Text" )
.containsExactly(
"Ignore unmapped target property: 'testName'",
"Add unmapped target property: 'testName'",
"Ignore unmapped target property: 'moreTarget'",
"Add unmapped target property: 'moreTarget'",
"Ignore unmapped target property: 'moreTarget'",
"Add unmapped target property: 'moreTarget'",
"Ignore unmapped target property: 'testName'",
"Add unmapped target property: 'testName'",
"Ignore all unmapped target properties",
"Ignore unmapped target property: 'testName'",
"Add unmapped target property: 'testName'",
"Ignore unmapped target property: 'moreTarget'",
"Add unmapped target property: 'moreTarget'"
);

allQuickFixes.forEach( myFixture::launchAction );
myFixture.checkResultByFile( testName + "_after.java" );
}
}
65 changes: 65 additions & 0 deletions testData/inspection/UnmappedSuperBuilderTargetProperties.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0
*/

import org.mapstruct.Context;
import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.Mappings;
import org.example.data.UnmappedSuperBuilderTargetPropertiesData.Target;
import org.example.data.UnmappedSuperBuilderTargetPropertiesData.Source;

interface NotMapStructMapper {

Target map(Source source);
}

@Mapper
interface SingleMappingsMapper {

@Mappings({
@Mapping(target = "moreTarget", source = "moreSource")
})
Target <warning descr="Unmapped target property: testName">map</warning>(Source source);
}

@Mapper
interface SingleMappingMapper {

@Mapping(target = "testName", source = "name")
Target <warning descr="Unmapped target property: moreTarget">map</warning>(Source source);
}

@Mapper
interface NoMappingMapper {

Target <warning descr="Unmapped target properties: moreTarget, testName">map</warning>(Source source);

@InheritInverseConfiguration
Source reverse(Target target);
}

@Mapper
interface AllMappingMapper {

@Mapping(target = "testName", source = "name")
@Mapping(target = "moreTarget", source = "moreSource")
Target mapWithAllMapping(Source source);
}

@Mapper
interface UpdateMapper {

@Mapping(target = "moreTarget", source = "moreSource")
void <warning descr="Unmapped target property: testName">update</warning>(@MappingTarget Target target, Source source);
}

@Mapper
interface MultiSourceUpdateMapper {

void <warning descr="Unmapped target property: moreTarget">update</warning>(@MappingTarget Target moreTarget, Source source, String testName, @Context String matching);
}
128 changes: 128 additions & 0 deletions testData/inspection/UnmappedSuperBuilderTargetPropertiesData.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0
*/
package org.example.data;

public class UnmappedSuperBuilderTargetPropertiesData {
public static class Source {

private String name;
private String matching;
private String moreSource;
private String onlyInSource;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getMatching() {
return matching;
}

public void setMatching(String matching) {
this.matching = matching;
}

public String getMoreSource() {
return moreSource;
}

public void setMoreSource(String moreSource) {
this.moreSource = moreSource;
}

public String getOnlyInSource() {
return onlyInSource;
}

public void setOnlyInSource(String onlyInSource) {
this.onlyInSource = onlyInSource;
}
}

public static class Target {

private String testName;
private String matching;
private String moreTarget;

protected Target(TargetBuilder<?, ?> b) {
this.testName = b.testName;
this.matching = b.matching;
this.moreTarget = b.moreTarget;
}

public static TargetBuilder<?, ?> builder() {
return new TargetBuilderImpl();
}

public String getTestName() {
return this.testName;
}

public String getMatching() {
return this.matching;
}

public String getMoreTarget() {
return this.moreTarget;
}

public void setTestName(String testName) {
this.testName = testName;
}

public void setMatching(String matching) {
this.matching = matching;
}

public void setMoreTarget(String moreTarget) {
this.moreTarget = moreTarget;
}

public static abstract class TargetBuilder<C extends Target, B extends TargetBuilder<C, B>> {
private String testName;
private String matching;
private String moreTarget;

public B testName(String testName) {
this.testName = testName;
return self();
}

public B matching(String matching) {
this.matching = matching;
return self();
}

public B moreTarget(String moreTarget) {
this.moreTarget = moreTarget;
return self();
}

protected abstract B self();

public abstract C build();
}

private static final class TargetBuilderImpl extends TargetBuilder<Target, TargetBuilderImpl> {
private TargetBuilderImpl() {
}

protected TargetBuilderImpl self() {
return this;
}

public Target build() {
return new Target( this );
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0
*/

import org.mapstruct.Context;
import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.Mappings;
import org.example.data.UnmappedSuperBuilderTargetPropertiesData.Target;
import org.example.data.UnmappedSuperBuilderTargetPropertiesData.Source;

interface NotMapStructMapper {

Target map(Source source);
}

@Mapper
interface SingleMappingsMapper {

@Mappings({
@Mapping(target = "moreTarget", source = "moreSource"),
@Mapping(target = "testName", ignore = true),
@Mapping(target = "testName", source = "")
})
Target map(Source source);
}

@Mapper
interface SingleMappingMapper {

@Mapping(target = "moreTarget", source = "")
@Mapping(target = "moreTarget", ignore = true)
@Mapping(target = "testName", source = "name")
Target map(Source source);
}

@Mapper
interface NoMappingMapper {

@Mapping(target = "testName", ignore = true)
@Mapping(target = "moreTarget", ignore = true)
@Mapping(target = "testName", source = "")
@Mapping(target = "testName", ignore = true)
@Mapping(target = "moreTarget", source = "")
@Mapping(target = "moreTarget", ignore = true)
Target map(Source source);

@InheritInverseConfiguration
Source reverse(Target target);
}

@Mapper
interface AllMappingMapper {

@Mapping(target = "testName", source = "name")
@Mapping(target = "moreTarget", source = "moreSource")
Target mapWithAllMapping(Source source);
}

@Mapper
interface UpdateMapper {

@Mapping(target = "testName", source = "")
@Mapping(target = "testName", ignore = true)
@Mapping(target = "moreTarget", source = "moreSource")
void update(@MappingTarget Target target, Source source);
}

@Mapper
interface MultiSourceUpdateMapper {

@Mapping(target = "moreTarget", source = "")
@Mapping(target = "moreTarget", ignore = true)
void update(@MappingTarget Target moreTarget, Source source, String testName, @Context String matching);
}

0 comments on commit 27c6cfc

Please sign in to comment.