Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Adds fluent getters detection for immutables
  • Loading branch information
thmasker committed Aug 10, 2025
commit e53da5054c674edb2de4fc913c26e9cd876f244b
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
package org.mapstruct.ap.spi;

import java.util.regex.Pattern;

import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
Expand All @@ -26,7 +25,7 @@
*/
public class DefaultAccessorNamingStrategy implements AccessorNamingStrategy {

private static final Pattern JAVA_JAVAX_PACKAGE = Pattern.compile( "^javax?\\..*" );
protected static final Pattern JAVA_JAVAX_PACKAGE = Pattern.compile( "^javax?\\..*" );

protected Elements elementUtils;
protected Types typeUtils;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package org.mapstruct.ap.spi;

import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.TypeKind;

import org.mapstruct.util.Experimental;

Expand All @@ -19,6 +20,17 @@
@Experimental("The Immutables accessor naming strategy might change in a subsequent release")
public class ImmutablesAccessorNamingStrategy extends DefaultAccessorNamingStrategy {

@Override
public boolean isGetterMethod(ExecutableElement method) {
return super.isGetterMethod( method ) || isFluentGetter( method );
}

private boolean isFluentGetter(ExecutableElement method) {
return method.getParameters().isEmpty() &&
method.getReturnType().getKind() != TypeKind.VOID &&
!JAVA_JAVAX_PACKAGE.matcher( method.getEnclosingElement().asType().toString() ).matches();
}
Comment on lines +28 to +32
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that we should just make this like this. We should look at the Immutables @Styles, see https://immutables.github.io/style.html and determine based on that.

Otherwise, this just applies to everything.

Additionally, I really do not understand how this even works, the getElementName is not overridden, so I don't think that this works properly


@Override
protected boolean isFluentSetter(ExecutableElement method) {
return super.isFluentSetter( method ) &&
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.fluent.getters;

import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;

import org.mapstruct.ap.spi.BuilderInfo;
import org.mapstruct.ap.spi.BuilderProvider;
import org.mapstruct.ap.spi.ImmutablesBuilderProvider;

public class Issue1601BuilderProvider extends ImmutablesBuilderProvider implements BuilderProvider {

@Override
protected BuilderInfo findBuilderInfo(TypeElement typeElement) {
Name name = typeElement.getQualifiedName();
if ( name.toString().endsWith( ".Item" ) ) {
BuilderInfo info = findBuilderInfoForImmutables( typeElement );
if ( info != null ) {
return info;
}
}

return super.findBuilderInfo( typeElement );
}

@Override
protected BuilderInfo findBuilderInfoForImmutables(TypeElement typeElement) {
TypeElement immutableElement = asImmutableElement( typeElement );
if ( immutableElement != null ) {
return super.findBuilderInfo( immutableElement );
}
return null;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.fluent.getters;

import org.mapstruct.ap.spi.AccessorNamingStrategy;
import org.mapstruct.ap.spi.BuilderProvider;
import org.mapstruct.ap.spi.ImmutablesAccessorNamingStrategy;
import org.mapstruct.ap.test.fluent.getters.domain.ImmutableItem;
import org.mapstruct.ap.test.fluent.getters.domain.Item;
import org.mapstruct.ap.test.fluent.getters.dto.ImmutableItemDto;
import org.mapstruct.ap.test.fluent.getters.dto.ItemDto;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.ProcessorTest;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.WithServiceImplementation;
import org.mapstruct.ap.testutil.WithServiceImplementations;

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

@WithClasses({
ItemMapper.class,
Item.class,
ItemDto.class,
ImmutableItem.class,
ImmutableItemDto.class
})
@IssueKey("1601")
@WithServiceImplementations({
@WithServiceImplementation(provides = BuilderProvider.class, value = Issue1601BuilderProvider.class),
@WithServiceImplementation(provides = AccessorNamingStrategy.class, value = ImmutablesAccessorNamingStrategy.class)
})
public class Issue1601Test {

@ProcessorTest
public void shouldIncludeBuildType() {
var item = ImmutableItemDto.builder().id( "test" ).build();

var target = ItemMapper.INSTANCE.map( item );
Comment on lines +39 to +41
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not use var. We don't have a check for this, but I am not a fan of it and we are not using it anywhere else in our codebase.


assertThat( target ).isNotNull();
assertThat( target.id() ).isEqualTo( "test" );
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.fluent.getters;

import org.mapstruct.Builder;
import org.mapstruct.Mapper;
import org.mapstruct.ap.test.fluent.getters.domain.Item;
import org.mapstruct.ap.test.fluent.getters.dto.ItemDto;
import org.mapstruct.factory.Mappers;

@Mapper(builder = @Builder)
public abstract class ItemMapper {

public static final ItemMapper INSTANCE = Mappers.getMapper( ItemMapper.class );

public abstract Item map(ItemDto itemDTO);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.fluent.getters.domain;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public final class ImmutableItem extends Item {

private final String id;

private ImmutableItem(String id) {
this.id = id;
}

@Override
public String id() {
return id;
}

public ImmutableItem withId(String value) {
if ( Objects.equals( this.id, value ) ) {
return this;
}
return new ImmutableItem( value );
}

@Override
public boolean equals(Object another) {
if ( this == another ) {
return true;
}
return another instanceof ImmutableItem && id.equals( ( (ImmutableItem) another ).id );
}

@Override
public int hashCode() {
int h = 5381;
h += ( h << 5 ) + id.hashCode();
return h;
}

@Override
public String toString() {
return "Item{id=" + id + "}";
}

public static ImmutableItem copyOf(Item instance) {
if ( instance instanceof ImmutableItem ) {
return (ImmutableItem) instance;
}
return ImmutableItem.builder().from( instance ).build();
}

public static Builder builder() {
return new Builder();
}

public static final class Builder {

private static final long INIT_BIT_ID = 0x1L;
private long initBits = 0x1L;

private String id;

private Builder() {
}

public Builder from(Item instance) {
id( instance.id() );
return this;
}

public Builder id(String id) {
this.id = id;
initBits &= ~INIT_BIT_ID;
return this;
}

public ImmutableItem build() {
if ( initBits != 0 ) {
throw new IllegalStateException( formatRequiredAttributesMessage() );
}
return new ImmutableItem( id );
}

private String formatRequiredAttributesMessage() {
List<String> attributes = new ArrayList<>();
if ( ( initBits & INIT_BIT_ID ) != 0 ) {
attributes.add( "id" );
}
return "Cannot build Item, some of required attributes are not set " + attributes;
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.mapstruct.ap.test.fluent.getters.domain;

public abstract class Item {

public abstract String id();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.fluent.getters.dto;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public final class ImmutableItemDto extends ItemDto {

private final String id;

private ImmutableItemDto(String id) {
this.id = id;
}

@Override
public String id() {
return id;
}

public ImmutableItemDto withId(String value) {
if ( Objects.equals( this.id, value ) ) {
return this;
}
return new ImmutableItemDto( value );
}

@Override
public boolean equals(Object another) {
if ( this == another ) {
return true;
}
return another instanceof ImmutableItemDto && id.equals( ( (ImmutableItemDto) another ).id );
}

@Override
public int hashCode() {
int h = 5381;
h += ( h << 5 ) + id.hashCode();
return h;
}

@Override
public String toString() {
return "ItemDTO{id=" + id + "}";
}

public static ImmutableItemDto copyOf(ItemDto instance) {
if ( instance instanceof ImmutableItemDto ) {
return (ImmutableItemDto) instance;
}
return ImmutableItemDto.builder().from( instance ).build();
}

public static Builder builder() {
return new Builder();
}

public static final class Builder {

private static final long INIT_BIT_ID = 0x1L;
private long initBits = 0x1L;

private String id;

private Builder() {
}

public Builder from(ItemDto instance) {
id( instance.id() );
return this;
}

public Builder id(String id) {
this.id = id;
initBits &= ~INIT_BIT_ID;
return this;
}

public ImmutableItemDto build() {
if ( initBits != 0 ) {
throw new IllegalStateException( formatRequiredAttributesMessage() );
}
return new ImmutableItemDto( id );
}

private String formatRequiredAttributesMessage() {
List<String> attributes = new ArrayList<>();
if ( ( initBits & INIT_BIT_ID ) != 0 ) {
attributes.add( "id" );
}
return "Cannot build ItemDTO, some of required attributes are not set " + attributes;
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.mapstruct.ap.test.fluent.getters.dto;

public abstract class ItemDto {

public abstract String id();

}
Loading