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 committed Mar 14, 2024
1 parent c461730 commit efff2a7
Show file tree
Hide file tree
Showing 5 changed files with 363 additions and 7 deletions.
33 changes: 26 additions & 7 deletions src/main/java/org/mapstruct/intellij/util/MapstructUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package org.mapstruct.intellij.util;

import java.beans.Introspector;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -108,7 +109,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 +164,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 +201,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, @Nullable PsiType returnType) {

if ( returnType == null ) {
return false;
}

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

return Arrays.stream( returnType.getSuperTypes() )
.anyMatch( superType -> isAssignableFrom( psiType, superType ) );
}

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" );
}
}
64 changes: 64 additions & 0 deletions testData/inspection/UnmappedSuperBuilderTargetProperties.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* 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.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.Mappings;
import org.example.data.UnmappedFluentTargetPropertiesData.Target;
import org.example.data.UnmappedFluentTargetPropertiesData.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);

@org.mapstruct.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);
}
133 changes: 133 additions & 0 deletions testData/inspection/UnmappedSuperBuilderTargetPropertiesData.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*
* 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 UnmappedFluentTargetPropertiesData {
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();

public String toString() {
return "Target.TargetBuilder(testName=" + this.testName + ", matching=" + this.matching + ", moreTarget=" +
this.moreTarget + ")";
}
}

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

protected TargetBuilderImpl self() {
return this;
}

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

}
Loading

0 comments on commit efff2a7

Please sign in to comment.