From 38df79ff56758304791560fcd6ee9a7961b53ef6 Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Sat, 13 Jun 2026 07:42:38 +0100 Subject: [PATCH 1/6] Define new enum ClassAccessibility for controlling generated Mappers' declared accessibility. --- .../org/mapstruct/ClassAccessibility.java | 29 +++++++++++ core/src/main/java/org/mapstruct/Mapper.java | 10 ++++ .../main/java/org/mapstruct/MapperConfig.java | 10 ++++ .../internal/gem/ClassAccessibilityGem.java | 15 ++++++ .../ap/internal/model/GeneratedType.java | 6 +++ .../mapstruct/ap/internal/model/Mapper.java | 8 ++- .../internal/model/source/DefaultOptions.java | 6 +++ .../model/source/DelegatingOptions.java | 5 ++ .../model/source/MapperConfigOptions.java | 8 +++ .../internal/model/source/MapperOptions.java | 8 +++ .../processor/MapperCreationProcessor.java | 1 + .../ClassAccessibilityTest.java | 49 +++++++++++++++++++ .../ForcedDefaultMapper.java | 15 ++++++ .../ForcedPublicMapper.java | 15 ++++++ .../PackageAbstractionMapper.java | 14 ++++++ .../PublicAbstractionMapper.java | 14 ++++++ .../ap/test/classaccessibility/Source.java | 21 ++++++++ .../ap/test/classaccessibility/Target.java | 21 ++++++++ 18 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 core/src/main/java/org/mapstruct/ClassAccessibility.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/gem/ClassAccessibilityGem.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ClassAccessibilityTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedDefaultMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedPublicMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/classaccessibility/PackageAbstractionMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/classaccessibility/PublicAbstractionMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/classaccessibility/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/classaccessibility/Target.java diff --git a/core/src/main/java/org/mapstruct/ClassAccessibility.java b/core/src/main/java/org/mapstruct/ClassAccessibility.java new file mode 100644 index 0000000000..7783445e5b --- /dev/null +++ b/core/src/main/java/org/mapstruct/ClassAccessibility.java @@ -0,0 +1,29 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct; + +/** + * Indicates whether a generated Mapper implementation class should be declared {@code public} or not. + * + * @author Raimund Klein + * + * @since 1.7.0 + */ +public enum ClassAccessibility { + /** + * Indicates that the generated Mapper should have the same modifier ({@code public} or none) as the annotated + * class or interface. + */ + LIKE_ABSTRACTION, + /** + * Indicates that the generated Mapoer should be declared {@code public}. + */ + PUBLIC, + /** + * Indicates that the generated Mapper should have no visibility modifier ("package-private"). + */ + DEFAULT +} diff --git a/core/src/main/java/org/mapstruct/Mapper.java b/core/src/main/java/org/mapstruct/Mapper.java index 398dc1870a..71dd475923 100644 --- a/core/src/main/java/org/mapstruct/Mapper.java +++ b/core/src/main/java/org/mapstruct/Mapper.java @@ -14,6 +14,7 @@ import org.mapstruct.control.MappingControl; import org.mapstruct.factory.Mappers; +import static org.mapstruct.ClassAccessibility.LIKE_ABSTRACTION; import static org.mapstruct.NullValueCheckStrategy.ON_IMPLICIT_CONVERSION; import static org.mapstruct.SubclassExhaustiveStrategy.COMPILE_ERROR; @@ -388,4 +389,13 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() default * @since 1.5 */ boolean suppressTimestampInGenerated() default false; + + /** + * Determines the {@link ClassAccessibility} ({@code public} or package-private) for the generated Mapper + * implementation. Default is to mirror the interface or abstract class annotated by this {@code Mapper}. + * + * @return The {@link ClassAccessibility} ({@code public} or package-private) for the generated Mapper + * implementation + */ + ClassAccessibility accessibility() default LIKE_ABSTRACTION; } diff --git a/core/src/main/java/org/mapstruct/MapperConfig.java b/core/src/main/java/org/mapstruct/MapperConfig.java index 8631562a56..2704f77cdf 100644 --- a/core/src/main/java/org/mapstruct/MapperConfig.java +++ b/core/src/main/java/org/mapstruct/MapperConfig.java @@ -14,6 +14,7 @@ import org.mapstruct.control.MappingControl; import org.mapstruct.factory.Mappers; +import static org.mapstruct.ClassAccessibility.LIKE_ABSTRACTION; import static org.mapstruct.NullValueCheckStrategy.ON_IMPLICIT_CONVERSION; import static org.mapstruct.SubclassExhaustiveStrategy.COMPILE_ERROR; @@ -357,5 +358,14 @@ MappingInheritanceStrategy mappingInheritanceStrategy() * @since 1.5 */ boolean suppressTimestampInGenerated() default false; + + /** + * Determines the {@link ClassAccessibility} ({@code public} or package-private) for the generated Mapper + * implementation. Default is to mirror the interface or abstract class annotated by {@code Mapper}. + * + * @return The {@link ClassAccessibility} ({@code public} or package-private) for the generated Mapper + * implementation + */ + ClassAccessibility accessibility() default LIKE_ABSTRACTION; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/ClassAccessibilityGem.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/ClassAccessibilityGem.java new file mode 100644 index 0000000000..71aa6f4aa6 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/ClassAccessibilityGem.java @@ -0,0 +1,15 @@ +/* + * 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.internal.gem; + +/** + * Gem for the enum {@link org.mapstruct.ClassAccessibility} + * + * @author Raimund Klein + */ +public enum ClassAccessibilityGem { + LIKE_ABSTRACTION, PUBLIC, DEFAULT +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java index 4b39e92273..6eb8bbfef8 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java @@ -12,6 +12,7 @@ import java.util.TreeSet; import javax.lang.model.type.TypeKind; +import org.mapstruct.ap.internal.gem.ClassAccessibilityGem; import org.mapstruct.ap.internal.model.common.Accessibility; import org.mapstruct.ap.internal.model.common.ModelElement; import org.mapstruct.ap.internal.model.common.Type; @@ -40,6 +41,7 @@ protected abstract static class GeneratedTypeBuilder extraImportedTypes; protected List methods; + protected ClassAccessibilityGem classAccessibility; GeneratedTypeBuilder(Class selfType) { myself = selfType.cast( this ); @@ -75,6 +77,10 @@ public T methods(List methods) { return myself; } + public T classAccessibility(ClassAccessibilityGem classAccessibility) { + this.classAccessibility = classAccessibility; + return myself; + } } private final String packageName; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java index bcf7e840e5..5cb344d9cc 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java @@ -17,6 +17,8 @@ import org.mapstruct.ap.internal.option.Options; import org.mapstruct.ap.internal.version.VersionInformation; +import static org.mapstruct.ap.internal.gem.ClassAccessibilityGem.LIKE_ABSTRACTION; + /** * Represents a type implementing a mapper interface (annotated with {@code @Mapper}). This is the root object of the * mapper model. @@ -109,6 +111,10 @@ public Mapper build() { Type definitionType = typeFactory.getType( element ); + Accessibility accessibility = + classAccessibility == LIKE_ABSTRACTION ? Accessibility.fromModifiers( element.getModifiers() ) : + Accessibility.valueOf( classAccessibility.name() ); + return new Mapper( typeFactory, packageName, @@ -121,7 +127,7 @@ public Mapper build() { options, versionInformation, suppressGeneratorTimestamp, - Accessibility.fromModifiers( element.getModifiers() ), + accessibility, fields, constructor, decorator, diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java index 8d76e65c75..d958272f9b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java @@ -11,6 +11,7 @@ import javax.lang.model.type.TypeMirror; import org.mapstruct.ap.internal.gem.BuilderGem; +import org.mapstruct.ap.internal.gem.ClassAccessibilityGem; import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; import org.mapstruct.ap.internal.gem.InjectionStrategyGem; import org.mapstruct.ap.internal.gem.MapperGem; @@ -155,6 +156,11 @@ public NullValueMappingStrategyGem getNullValueMapMappingStrategy() { return NullValueMappingStrategyGem.valueOf( mapper.nullValueMapMappingStrategy().getDefaultValue() ); } + @Override + public ClassAccessibilityGem accessibility() { + return ClassAccessibilityGem.valueOf( mapper.accessibility().getDefaultValue() ); + } + public BuilderGem getBuilder() { // TODO: I realized this is not correct, however it needs to be null in order to keep downward compatibility // but assuming a default @Builder will make testcases fail. Not having a default means that you need to diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java index 34478969fa..e97f527745 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java @@ -12,6 +12,7 @@ import javax.lang.model.type.TypeMirror; import org.mapstruct.ap.internal.gem.BuilderGem; +import org.mapstruct.ap.internal.gem.ClassAccessibilityGem; import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; import org.mapstruct.ap.internal.gem.InjectionStrategyGem; import org.mapstruct.ap.internal.gem.MappingInheritanceStrategyGem; @@ -84,6 +85,10 @@ public Boolean isDisableSubMappingMethodsGeneration() { return next.isDisableSubMappingMethodsGeneration(); } + public ClassAccessibilityGem accessibility() { + return next.accessibility(); + } + // BeanMapping and Mapping public CollectionMappingStrategyGem getCollectionMappingStrategy() { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java index b656e2b5ea..b51008b713 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java @@ -10,6 +10,7 @@ import javax.lang.model.type.TypeMirror; import org.mapstruct.ap.internal.gem.BuilderGem; +import org.mapstruct.ap.internal.gem.ClassAccessibilityGem; import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; import org.mapstruct.ap.internal.gem.InjectionStrategyGem; import org.mapstruct.ap.internal.gem.MapperConfigGem; @@ -169,6 +170,13 @@ public NullValueMappingStrategyGem getNullValueMapMappingStrategy() { return next().getNullValueMapMappingStrategy(); } + @Override + public ClassAccessibilityGem accessibility() { + return mapperConfig.accessibility().hasValue() ? + ClassAccessibilityGem.valueOf( mapperConfig.accessibility().get() ) : + next().accessibility(); + } + @Override public BuilderGem getBuilder() { return mapperConfig.builder().hasValue() ? mapperConfig.builder().get() : next().getBuilder(); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java index fa3d527a30..b30b8c95fa 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java @@ -14,6 +14,7 @@ import javax.lang.model.type.TypeMirror; import org.mapstruct.ap.internal.gem.BuilderGem; +import org.mapstruct.ap.internal.gem.ClassAccessibilityGem; import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; import org.mapstruct.ap.internal.gem.InjectionStrategyGem; import org.mapstruct.ap.internal.gem.MapperConfigGem; @@ -170,6 +171,13 @@ public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() { next().getSubclassExhaustiveStrategy(); } + @Override + public ClassAccessibilityGem accessibility() { + return mapper.accessibility().hasValue() ? + ClassAccessibilityGem.valueOf( mapper.accessibility().get() ) : + next().accessibility(); + } + @Override public TypeMirror getSubclassExhaustiveException() { return mapper.subclassExhaustiveException().hasValue() ? diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java index 8bae8ee4c1..db16d52106 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java @@ -217,6 +217,7 @@ private Mapper getMapper(TypeElement element, MapperOptions mapperOptions, List< .suppressGeneratorTimestamp( mapperOptions.suppressTimestampInGenerated() ) .additionalAnnotations( additionalAnnotationsBuilder.getProcessedAnnotations( element ) ) .javadoc( getJavadoc( element ) ) + .classAccessibility( mapperOptions.accessibility() ) .build(); if ( !mappingContext.getForgedMethodsUnderCreation().isEmpty() ) { diff --git a/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ClassAccessibilityTest.java b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ClassAccessibilityTest.java new file mode 100644 index 0000000000..7ae082ec78 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ClassAccessibilityTest.java @@ -0,0 +1,49 @@ +/* + * 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.classaccessibility; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static java.lang.reflect.Modifier.isPrivate; +import static java.lang.reflect.Modifier.isProtected; +import static java.lang.reflect.Modifier.isPublic; +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + Source.class, + Target.class, + PublicAbstractionMapper.class, + PackageAbstractionMapper.class, + ForcedPublicMapper.class, + ForcedDefaultMapper.class +}) +public class ClassAccessibilityTest { + + @ProcessorTest + public void shouldCreateModifierAccordingToAnnotation() throws Exception { + Class publicLike = loadForMapper( PublicAbstractionMapper.class ); + assertThat( isPublic( publicLike.getModifiers() ) ).isTrue(); + + Class packageLike = loadForMapper( PackageAbstractionMapper.class ); + assertThat( isDefault( packageLike.getModifiers() ) ).isTrue(); + + Class forcedPublic = loadForMapper( ForcedPublicMapper.class ); + assertThat( isPublic( forcedPublic.getModifiers() ) ).isTrue(); + + Class forcedDefault = loadForMapper( ForcedDefaultMapper.class ); + assertThat( isDefault( forcedDefault.getModifiers() ) ).isTrue(); + } + + private static Class loadForMapper(Class mapper) throws ClassNotFoundException { + return Thread.currentThread().getContextClassLoader().loadClass( mapper.getName() + "Impl" ); + } + + private static boolean isDefault(int modifiers) { + return !isPublic( modifiers ) && !isProtected( modifiers ) && !isPrivate( modifiers ); + } +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedDefaultMapper.java b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedDefaultMapper.java new file mode 100644 index 0000000000..a3f8677cb9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedDefaultMapper.java @@ -0,0 +1,15 @@ +/* + * 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.classaccessibility; + +import org.mapstruct.ClassAccessibility; +import org.mapstruct.Mapper; + +@Mapper(accessibility = ClassAccessibility.DEFAULT) +public interface ForcedDefaultMapper { + Target map(Source value); +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedPublicMapper.java b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedPublicMapper.java new file mode 100644 index 0000000000..fd6ced910a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedPublicMapper.java @@ -0,0 +1,15 @@ +/* + * 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.classaccessibility; + +import org.mapstruct.ClassAccessibility; +import org.mapstruct.Mapper; + +@Mapper(accessibility = ClassAccessibility.PUBLIC) +interface ForcedPublicMapper { + Target map(Source value); +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/PackageAbstractionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/PackageAbstractionMapper.java new file mode 100644 index 0000000000..d9f426b382 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/PackageAbstractionMapper.java @@ -0,0 +1,14 @@ +/* + * 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.classaccessibility; + +import org.mapstruct.Mapper; + +@Mapper +interface PackageAbstractionMapper { + Target map(Source value); +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/PublicAbstractionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/PublicAbstractionMapper.java new file mode 100644 index 0000000000..7c1b5de10f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/PublicAbstractionMapper.java @@ -0,0 +1,14 @@ +/* + * 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.classaccessibility; + +import org.mapstruct.Mapper; + +@Mapper +public interface PublicAbstractionMapper { + Target map(Source value); +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/Source.java b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/Source.java new file mode 100644 index 0000000000..feff965719 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/Source.java @@ -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.classaccessibility; + +/** + * @author Raimund Klein + **/ +public class Source { + private String foo; + + public String getFoo() { + return foo; + } + + public void setFoo(String foo) { + this.foo = foo; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/Target.java b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/Target.java new file mode 100644 index 0000000000..e8c50a2531 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/Target.java @@ -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.classaccessibility; + +/** + * @author Raimund Klein + **/ +public class Target { + private String foo; + + public String getFoo() { + return foo; + } + + public void setFoo(String foo) { + this.foo = foo; + } +} From c0784ac9488407a6122530ef6e192b2cd4ad2003 Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Sat, 13 Jun 2026 07:42:38 +0100 Subject: [PATCH 2/6] Define new enum ClassAccessibility for controlling generated Mappers' declared accessibility. --- .../org/mapstruct/ClassAccessibility.java | 29 +++++++++++ core/src/main/java/org/mapstruct/Mapper.java | 10 ++++ .../main/java/org/mapstruct/MapperConfig.java | 10 ++++ .../chapter-4-retrieving-a-mapper.asciidoc | 22 ++++++++- .../internal/gem/ClassAccessibilityGem.java | 15 ++++++ .../ap/internal/model/GeneratedType.java | 6 +++ .../mapstruct/ap/internal/model/Mapper.java | 8 ++- .../internal/model/source/DefaultOptions.java | 6 +++ .../model/source/DelegatingOptions.java | 5 ++ .../model/source/MapperConfigOptions.java | 8 +++ .../internal/model/source/MapperOptions.java | 8 +++ .../processor/MapperCreationProcessor.java | 1 + .../ClassAccessibilityTest.java | 49 +++++++++++++++++++ .../ForcedDefaultMapper.java | 15 ++++++ .../ForcedPublicMapper.java | 15 ++++++ .../PackageAbstractionMapper.java | 14 ++++++ .../PublicAbstractionMapper.java | 14 ++++++ .../ap/test/classaccessibility/Source.java | 21 ++++++++ .../ap/test/classaccessibility/Target.java | 21 ++++++++ 19 files changed, 275 insertions(+), 2 deletions(-) create mode 100644 core/src/main/java/org/mapstruct/ClassAccessibility.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/gem/ClassAccessibilityGem.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ClassAccessibilityTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedDefaultMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedPublicMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/classaccessibility/PackageAbstractionMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/classaccessibility/PublicAbstractionMapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/classaccessibility/Source.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/classaccessibility/Target.java diff --git a/core/src/main/java/org/mapstruct/ClassAccessibility.java b/core/src/main/java/org/mapstruct/ClassAccessibility.java new file mode 100644 index 0000000000..7783445e5b --- /dev/null +++ b/core/src/main/java/org/mapstruct/ClassAccessibility.java @@ -0,0 +1,29 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct; + +/** + * Indicates whether a generated Mapper implementation class should be declared {@code public} or not. + * + * @author Raimund Klein + * + * @since 1.7.0 + */ +public enum ClassAccessibility { + /** + * Indicates that the generated Mapper should have the same modifier ({@code public} or none) as the annotated + * class or interface. + */ + LIKE_ABSTRACTION, + /** + * Indicates that the generated Mapoer should be declared {@code public}. + */ + PUBLIC, + /** + * Indicates that the generated Mapper should have no visibility modifier ("package-private"). + */ + DEFAULT +} diff --git a/core/src/main/java/org/mapstruct/Mapper.java b/core/src/main/java/org/mapstruct/Mapper.java index 398dc1870a..71dd475923 100644 --- a/core/src/main/java/org/mapstruct/Mapper.java +++ b/core/src/main/java/org/mapstruct/Mapper.java @@ -14,6 +14,7 @@ import org.mapstruct.control.MappingControl; import org.mapstruct.factory.Mappers; +import static org.mapstruct.ClassAccessibility.LIKE_ABSTRACTION; import static org.mapstruct.NullValueCheckStrategy.ON_IMPLICIT_CONVERSION; import static org.mapstruct.SubclassExhaustiveStrategy.COMPILE_ERROR; @@ -388,4 +389,13 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() default * @since 1.5 */ boolean suppressTimestampInGenerated() default false; + + /** + * Determines the {@link ClassAccessibility} ({@code public} or package-private) for the generated Mapper + * implementation. Default is to mirror the interface or abstract class annotated by this {@code Mapper}. + * + * @return The {@link ClassAccessibility} ({@code public} or package-private) for the generated Mapper + * implementation + */ + ClassAccessibility accessibility() default LIKE_ABSTRACTION; } diff --git a/core/src/main/java/org/mapstruct/MapperConfig.java b/core/src/main/java/org/mapstruct/MapperConfig.java index 8631562a56..2704f77cdf 100644 --- a/core/src/main/java/org/mapstruct/MapperConfig.java +++ b/core/src/main/java/org/mapstruct/MapperConfig.java @@ -14,6 +14,7 @@ import org.mapstruct.control.MappingControl; import org.mapstruct.factory.Mappers; +import static org.mapstruct.ClassAccessibility.LIKE_ABSTRACTION; import static org.mapstruct.NullValueCheckStrategy.ON_IMPLICIT_CONVERSION; import static org.mapstruct.SubclassExhaustiveStrategy.COMPILE_ERROR; @@ -357,5 +358,14 @@ MappingInheritanceStrategy mappingInheritanceStrategy() * @since 1.5 */ boolean suppressTimestampInGenerated() default false; + + /** + * Determines the {@link ClassAccessibility} ({@code public} or package-private) for the generated Mapper + * implementation. Default is to mirror the interface or abstract class annotated by {@code Mapper}. + * + * @return The {@link ClassAccessibility} ({@code public} or package-private) for the generated Mapper + * implementation + */ + ClassAccessibility accessibility() default LIKE_ABSTRACTION; } diff --git a/documentation/src/main/asciidoc/chapter-4-retrieving-a-mapper.asciidoc b/documentation/src/main/asciidoc/chapter-4-retrieving-a-mapper.asciidoc index 35afc93a51..29713df147 100644 --- a/documentation/src/main/asciidoc/chapter-4-retrieving-a-mapper.asciidoc +++ b/documentation/src/main/asciidoc/chapter-4-retrieving-a-mapper.asciidoc @@ -130,4 +130,24 @@ In that case utilize the `InjectionStrategy#SETTER` strategy. [TIP] ==== For abstract classes or decorators setter injection should be used. -==== \ No newline at end of file +==== + +[[class-accessibility]] +=== Class accessibility + +By default, the generated Mapper implementation will have the same accessibility (`public` or none) as the interface or abstract class carrying the `@Mapper` annotation. Especially in conjunction with a DI framework, this might not be desired. If you wish to reduce the visibility of the generated class, you can make use of the `accessibility` attribute like this: + +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, accessibility = ClassAccessibility.DEFAULT) +public interface CarMapper { + + CarDto carToCarDto(Car car); +} + +---- +==== + +In this case, the generated Mapper will have no modifier. Possible values are `ClassAccessibility.LIKE_ABSTRACTION` (the default behavior described above), `ClassAccessibility.PUBLIC`, and `ClassAccessibility.DEFAULT` (no modifier, i.e. package-private). diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/ClassAccessibilityGem.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/ClassAccessibilityGem.java new file mode 100644 index 0000000000..71aa6f4aa6 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/ClassAccessibilityGem.java @@ -0,0 +1,15 @@ +/* + * 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.internal.gem; + +/** + * Gem for the enum {@link org.mapstruct.ClassAccessibility} + * + * @author Raimund Klein + */ +public enum ClassAccessibilityGem { + LIKE_ABSTRACTION, PUBLIC, DEFAULT +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java index 4b39e92273..6eb8bbfef8 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java @@ -12,6 +12,7 @@ import java.util.TreeSet; import javax.lang.model.type.TypeKind; +import org.mapstruct.ap.internal.gem.ClassAccessibilityGem; import org.mapstruct.ap.internal.model.common.Accessibility; import org.mapstruct.ap.internal.model.common.ModelElement; import org.mapstruct.ap.internal.model.common.Type; @@ -40,6 +41,7 @@ protected abstract static class GeneratedTypeBuilder extraImportedTypes; protected List methods; + protected ClassAccessibilityGem classAccessibility; GeneratedTypeBuilder(Class selfType) { myself = selfType.cast( this ); @@ -75,6 +77,10 @@ public T methods(List methods) { return myself; } + public T classAccessibility(ClassAccessibilityGem classAccessibility) { + this.classAccessibility = classAccessibility; + return myself; + } } private final String packageName; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java index bcf7e840e5..5cb344d9cc 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java @@ -17,6 +17,8 @@ import org.mapstruct.ap.internal.option.Options; import org.mapstruct.ap.internal.version.VersionInformation; +import static org.mapstruct.ap.internal.gem.ClassAccessibilityGem.LIKE_ABSTRACTION; + /** * Represents a type implementing a mapper interface (annotated with {@code @Mapper}). This is the root object of the * mapper model. @@ -109,6 +111,10 @@ public Mapper build() { Type definitionType = typeFactory.getType( element ); + Accessibility accessibility = + classAccessibility == LIKE_ABSTRACTION ? Accessibility.fromModifiers( element.getModifiers() ) : + Accessibility.valueOf( classAccessibility.name() ); + return new Mapper( typeFactory, packageName, @@ -121,7 +127,7 @@ public Mapper build() { options, versionInformation, suppressGeneratorTimestamp, - Accessibility.fromModifiers( element.getModifiers() ), + accessibility, fields, constructor, decorator, diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java index 8d76e65c75..d958272f9b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java @@ -11,6 +11,7 @@ import javax.lang.model.type.TypeMirror; import org.mapstruct.ap.internal.gem.BuilderGem; +import org.mapstruct.ap.internal.gem.ClassAccessibilityGem; import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; import org.mapstruct.ap.internal.gem.InjectionStrategyGem; import org.mapstruct.ap.internal.gem.MapperGem; @@ -155,6 +156,11 @@ public NullValueMappingStrategyGem getNullValueMapMappingStrategy() { return NullValueMappingStrategyGem.valueOf( mapper.nullValueMapMappingStrategy().getDefaultValue() ); } + @Override + public ClassAccessibilityGem accessibility() { + return ClassAccessibilityGem.valueOf( mapper.accessibility().getDefaultValue() ); + } + public BuilderGem getBuilder() { // TODO: I realized this is not correct, however it needs to be null in order to keep downward compatibility // but assuming a default @Builder will make testcases fail. Not having a default means that you need to diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java index 34478969fa..e97f527745 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java @@ -12,6 +12,7 @@ import javax.lang.model.type.TypeMirror; import org.mapstruct.ap.internal.gem.BuilderGem; +import org.mapstruct.ap.internal.gem.ClassAccessibilityGem; import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; import org.mapstruct.ap.internal.gem.InjectionStrategyGem; import org.mapstruct.ap.internal.gem.MappingInheritanceStrategyGem; @@ -84,6 +85,10 @@ public Boolean isDisableSubMappingMethodsGeneration() { return next.isDisableSubMappingMethodsGeneration(); } + public ClassAccessibilityGem accessibility() { + return next.accessibility(); + } + // BeanMapping and Mapping public CollectionMappingStrategyGem getCollectionMappingStrategy() { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java index b656e2b5ea..b51008b713 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java @@ -10,6 +10,7 @@ import javax.lang.model.type.TypeMirror; import org.mapstruct.ap.internal.gem.BuilderGem; +import org.mapstruct.ap.internal.gem.ClassAccessibilityGem; import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; import org.mapstruct.ap.internal.gem.InjectionStrategyGem; import org.mapstruct.ap.internal.gem.MapperConfigGem; @@ -169,6 +170,13 @@ public NullValueMappingStrategyGem getNullValueMapMappingStrategy() { return next().getNullValueMapMappingStrategy(); } + @Override + public ClassAccessibilityGem accessibility() { + return mapperConfig.accessibility().hasValue() ? + ClassAccessibilityGem.valueOf( mapperConfig.accessibility().get() ) : + next().accessibility(); + } + @Override public BuilderGem getBuilder() { return mapperConfig.builder().hasValue() ? mapperConfig.builder().get() : next().getBuilder(); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java index fa3d527a30..b30b8c95fa 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java @@ -14,6 +14,7 @@ import javax.lang.model.type.TypeMirror; import org.mapstruct.ap.internal.gem.BuilderGem; +import org.mapstruct.ap.internal.gem.ClassAccessibilityGem; import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; import org.mapstruct.ap.internal.gem.InjectionStrategyGem; import org.mapstruct.ap.internal.gem.MapperConfigGem; @@ -170,6 +171,13 @@ public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() { next().getSubclassExhaustiveStrategy(); } + @Override + public ClassAccessibilityGem accessibility() { + return mapper.accessibility().hasValue() ? + ClassAccessibilityGem.valueOf( mapper.accessibility().get() ) : + next().accessibility(); + } + @Override public TypeMirror getSubclassExhaustiveException() { return mapper.subclassExhaustiveException().hasValue() ? diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java index 8bae8ee4c1..db16d52106 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java @@ -217,6 +217,7 @@ private Mapper getMapper(TypeElement element, MapperOptions mapperOptions, List< .suppressGeneratorTimestamp( mapperOptions.suppressTimestampInGenerated() ) .additionalAnnotations( additionalAnnotationsBuilder.getProcessedAnnotations( element ) ) .javadoc( getJavadoc( element ) ) + .classAccessibility( mapperOptions.accessibility() ) .build(); if ( !mappingContext.getForgedMethodsUnderCreation().isEmpty() ) { diff --git a/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ClassAccessibilityTest.java b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ClassAccessibilityTest.java new file mode 100644 index 0000000000..7ae082ec78 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ClassAccessibilityTest.java @@ -0,0 +1,49 @@ +/* + * 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.classaccessibility; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static java.lang.reflect.Modifier.isPrivate; +import static java.lang.reflect.Modifier.isProtected; +import static java.lang.reflect.Modifier.isPublic; +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + Source.class, + Target.class, + PublicAbstractionMapper.class, + PackageAbstractionMapper.class, + ForcedPublicMapper.class, + ForcedDefaultMapper.class +}) +public class ClassAccessibilityTest { + + @ProcessorTest + public void shouldCreateModifierAccordingToAnnotation() throws Exception { + Class publicLike = loadForMapper( PublicAbstractionMapper.class ); + assertThat( isPublic( publicLike.getModifiers() ) ).isTrue(); + + Class packageLike = loadForMapper( PackageAbstractionMapper.class ); + assertThat( isDefault( packageLike.getModifiers() ) ).isTrue(); + + Class forcedPublic = loadForMapper( ForcedPublicMapper.class ); + assertThat( isPublic( forcedPublic.getModifiers() ) ).isTrue(); + + Class forcedDefault = loadForMapper( ForcedDefaultMapper.class ); + assertThat( isDefault( forcedDefault.getModifiers() ) ).isTrue(); + } + + private static Class loadForMapper(Class mapper) throws ClassNotFoundException { + return Thread.currentThread().getContextClassLoader().loadClass( mapper.getName() + "Impl" ); + } + + private static boolean isDefault(int modifiers) { + return !isPublic( modifiers ) && !isProtected( modifiers ) && !isPrivate( modifiers ); + } +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedDefaultMapper.java b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedDefaultMapper.java new file mode 100644 index 0000000000..a3f8677cb9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedDefaultMapper.java @@ -0,0 +1,15 @@ +/* + * 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.classaccessibility; + +import org.mapstruct.ClassAccessibility; +import org.mapstruct.Mapper; + +@Mapper(accessibility = ClassAccessibility.DEFAULT) +public interface ForcedDefaultMapper { + Target map(Source value); +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedPublicMapper.java b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedPublicMapper.java new file mode 100644 index 0000000000..fd6ced910a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedPublicMapper.java @@ -0,0 +1,15 @@ +/* + * 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.classaccessibility; + +import org.mapstruct.ClassAccessibility; +import org.mapstruct.Mapper; + +@Mapper(accessibility = ClassAccessibility.PUBLIC) +interface ForcedPublicMapper { + Target map(Source value); +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/PackageAbstractionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/PackageAbstractionMapper.java new file mode 100644 index 0000000000..d9f426b382 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/PackageAbstractionMapper.java @@ -0,0 +1,14 @@ +/* + * 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.classaccessibility; + +import org.mapstruct.Mapper; + +@Mapper +interface PackageAbstractionMapper { + Target map(Source value); +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/PublicAbstractionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/PublicAbstractionMapper.java new file mode 100644 index 0000000000..7c1b5de10f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/PublicAbstractionMapper.java @@ -0,0 +1,14 @@ +/* + * 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.classaccessibility; + +import org.mapstruct.Mapper; + +@Mapper +public interface PublicAbstractionMapper { + Target map(Source value); +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/Source.java b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/Source.java new file mode 100644 index 0000000000..feff965719 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/Source.java @@ -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.classaccessibility; + +/** + * @author Raimund Klein + **/ +public class Source { + private String foo; + + public String getFoo() { + return foo; + } + + public void setFoo(String foo) { + this.foo = foo; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/Target.java b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/Target.java new file mode 100644 index 0000000000..e8c50a2531 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/Target.java @@ -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.classaccessibility; + +/** + * @author Raimund Klein + **/ +public class Target { + private String foo; + + public String getFoo() { + return foo; + } + + public void setFoo(String foo) { + this.foo = foo; + } +} From 5f9a0341dfa031db1846676fb58d48c1742ea61f Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Fri, 19 Jun 2026 21:28:37 +0100 Subject: [PATCH 3/6] Fix typos, improve wording, and add missing tests. --- .../java/org/mapstruct/ClassAccessibility.java | 9 ++++----- .../mapstruct/ap/test/gem/EnumGemsTest.java | 8 ++++++++ .../ap/test/mapperconfig/CentralConfig.java | 3 ++- .../ap/test/mapperconfig/ConfigTest.java | 18 ++++++++++++++++++ 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/mapstruct/ClassAccessibility.java b/core/src/main/java/org/mapstruct/ClassAccessibility.java index 7783445e5b..6f598b7f14 100644 --- a/core/src/main/java/org/mapstruct/ClassAccessibility.java +++ b/core/src/main/java/org/mapstruct/ClassAccessibility.java @@ -6,7 +6,7 @@ package org.mapstruct; /** - * Indicates whether a generated Mapper implementation class should be declared {@code public} or not. + * Determines whether a generated Mapper implementation class will be declared {@code public} or not. * * @author Raimund Klein * @@ -14,16 +14,15 @@ */ public enum ClassAccessibility { /** - * Indicates that the generated Mapper should have the same modifier ({@code public} or none) as the annotated - * class or interface. + * The generated Mapper will have the same modifier ({@code public} or none) as the annotated class or interface. */ LIKE_ABSTRACTION, /** - * Indicates that the generated Mapoer should be declared {@code public}. + * The generated Mapper will be declared {@code public}. */ PUBLIC, /** - * Indicates that the generated Mapper should have no visibility modifier ("package-private"). + * The generated Mapper will have no visibility modifier ("package-private"). */ DEFAULT } diff --git a/processor/src/test/java/org/mapstruct/ap/test/gem/EnumGemsTest.java b/processor/src/test/java/org/mapstruct/ap/test/gem/EnumGemsTest.java index f460180651..57c2099b8b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/gem/EnumGemsTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/gem/EnumGemsTest.java @@ -10,6 +10,7 @@ import java.util.stream.Stream; import org.junit.jupiter.api.Test; +import org.mapstruct.ClassAccessibility; import org.mapstruct.CollectionMappingStrategy; import org.mapstruct.ConditionStrategy; import org.mapstruct.InjectionStrategy; @@ -17,6 +18,7 @@ import org.mapstruct.NullValueCheckStrategy; import org.mapstruct.NullValueMappingStrategy; import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.internal.gem.ClassAccessibilityGem; import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; import org.mapstruct.ap.internal.gem.ConditionStrategyGem; import org.mapstruct.ap.internal.gem.InjectionStrategyGem; @@ -75,6 +77,12 @@ public void conditionStrategyGemIsCorrect() { namesOf( ConditionStrategyGem.values() ) ); } + @Test + public void classAccesibilityGemIsCorrect() { + assertThat( namesOf( ClassAccessibility.values() ) ).isEqualTo( + namesOf( ClassAccessibilityGem.values() ) ); + } + private static List namesOf(Enum[] values) { return Stream.of( values ) .map( Enum::name ) diff --git a/processor/src/test/java/org/mapstruct/ap/test/mapperconfig/CentralConfig.java b/processor/src/test/java/org/mapstruct/ap/test/mapperconfig/CentralConfig.java index 81dc9a452a..795e587ec3 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/mapperconfig/CentralConfig.java +++ b/processor/src/test/java/org/mapstruct/ap/test/mapperconfig/CentralConfig.java @@ -5,6 +5,7 @@ */ package org.mapstruct.ap.test.mapperconfig; +import org.mapstruct.ClassAccessibility; import org.mapstruct.MapperConfig; import org.mapstruct.ReportingPolicy; @@ -12,7 +13,7 @@ * * @author Sjaak Derksen */ -@MapperConfig(uses = { CustomMapperViaMapperConfig.class }, unmappedTargetPolicy = ReportingPolicy.ERROR ) +@MapperConfig(uses = { CustomMapperViaMapperConfig.class }, unmappedTargetPolicy = ReportingPolicy.ERROR, accessibility = ClassAccessibility.DEFAULT) public class CentralConfig { } diff --git a/processor/src/test/java/org/mapstruct/ap/test/mapperconfig/ConfigTest.java b/processor/src/test/java/org/mapstruct/ap/test/mapperconfig/ConfigTest.java index 1f7fe423f5..dc7cdaa77e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/mapperconfig/ConfigTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/mapperconfig/ConfigTest.java @@ -12,6 +12,9 @@ import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; +import static java.lang.reflect.Modifier.isPrivate; +import static java.lang.reflect.Modifier.isProtected; +import static java.lang.reflect.Modifier.isPublic; import static org.assertj.core.api.Assertions.assertThat; /** @@ -49,6 +52,13 @@ public void shouldUseCustomMapperViaMapperConfigForFooToDto() { assertThat( source.getFoo().getCreatedBy() ).isEqualTo( CustomMapperViaMapperConfig.class.getSimpleName() ); } + @ProcessorTest + @WithClasses( { Target.class, SourceTargetMapper.class } ) + public void shouldDeclareMapperImplementationAsPackagePrivate() throws ClassNotFoundException { + Class implementation = loadForMapper( SourceTargetMapper.class ); + assertThat( isDefault( implementation.getModifiers() )).isTrue(); + } + @ProcessorTest @WithClasses( { TargetNoFoo.class, SourceTargetMapperWarn.class } ) @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, @@ -70,4 +80,12 @@ public void shouldUseWARNViaMapper() { }) public void shouldUseERRORViaMapperConfig() { } + + private static Class loadForMapper(Class mapper) throws ClassNotFoundException { + return Thread.currentThread().getContextClassLoader().loadClass( mapper.getName() + "Impl" ); + } + + private static boolean isDefault(int modifiers) { + return !isPublic( modifiers ) && !isProtected( modifiers ) && !isPrivate( modifiers ); + } } From 5ae77ced326bf54a95df2a46f6b00a12e29bdf31 Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Fri, 19 Jun 2026 21:50:39 +0100 Subject: [PATCH 4/6] Checkstyle --- .../org/mapstruct/ap/test/mapperconfig/CentralConfig.java | 4 +++- .../java/org/mapstruct/ap/test/mapperconfig/ConfigTest.java | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/processor/src/test/java/org/mapstruct/ap/test/mapperconfig/CentralConfig.java b/processor/src/test/java/org/mapstruct/ap/test/mapperconfig/CentralConfig.java index 795e587ec3..771700ab77 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/mapperconfig/CentralConfig.java +++ b/processor/src/test/java/org/mapstruct/ap/test/mapperconfig/CentralConfig.java @@ -13,7 +13,9 @@ * * @author Sjaak Derksen */ -@MapperConfig(uses = { CustomMapperViaMapperConfig.class }, unmappedTargetPolicy = ReportingPolicy.ERROR, accessibility = ClassAccessibility.DEFAULT) +@MapperConfig(uses = {CustomMapperViaMapperConfig.class}, + unmappedTargetPolicy = ReportingPolicy.ERROR, + accessibility = ClassAccessibility.DEFAULT) public class CentralConfig { } diff --git a/processor/src/test/java/org/mapstruct/ap/test/mapperconfig/ConfigTest.java b/processor/src/test/java/org/mapstruct/ap/test/mapperconfig/ConfigTest.java index dc7cdaa77e..a13db4ab0b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/mapperconfig/ConfigTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/mapperconfig/ConfigTest.java @@ -56,7 +56,7 @@ public void shouldUseCustomMapperViaMapperConfigForFooToDto() { @WithClasses( { Target.class, SourceTargetMapper.class } ) public void shouldDeclareMapperImplementationAsPackagePrivate() throws ClassNotFoundException { Class implementation = loadForMapper( SourceTargetMapper.class ); - assertThat( isDefault( implementation.getModifiers() )).isTrue(); + assertThat( isDefault( implementation.getModifiers() ) ).isTrue(); } @ProcessorTest From 4514a92c1b1f6e37067164034b7e99fe4c2600b6 Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Tue, 23 Jun 2026 08:43:32 +0100 Subject: [PATCH 5/6] Rename accessibility options as discussed. --- core/src/main/java/org/mapstruct/ClassAccessibility.java | 4 ++-- core/src/main/java/org/mapstruct/Mapper.java | 4 ++-- core/src/main/java/org/mapstruct/MapperConfig.java | 4 ++-- .../src/main/asciidoc/chapter-4-retrieving-a-mapper.asciidoc | 4 ++-- .../org/mapstruct/ap/internal/gem/ClassAccessibilityGem.java | 2 +- .../src/main/java/org/mapstruct/ap/internal/model/Mapper.java | 4 ++-- .../org/mapstruct/ap/internal/model/common/Accessibility.java | 4 ++-- .../ap/test/classaccessibility/ClassAccessibilityTest.java | 4 ++-- ...rcedDefaultMapper.java => ForcedPackagePrivateMapper.java} | 4 ++-- .../org/mapstruct/ap/test/mapperconfig/CentralConfig.java | 2 +- 10 files changed, 18 insertions(+), 18 deletions(-) rename processor/src/test/java/org/mapstruct/ap/test/classaccessibility/{ForcedDefaultMapper.java => ForcedPackagePrivateMapper.java} (74%) diff --git a/core/src/main/java/org/mapstruct/ClassAccessibility.java b/core/src/main/java/org/mapstruct/ClassAccessibility.java index 6f598b7f14..c533b1ea32 100644 --- a/core/src/main/java/org/mapstruct/ClassAccessibility.java +++ b/core/src/main/java/org/mapstruct/ClassAccessibility.java @@ -16,7 +16,7 @@ public enum ClassAccessibility { /** * The generated Mapper will have the same modifier ({@code public} or none) as the annotated class or interface. */ - LIKE_ABSTRACTION, + DEFAULT, /** * The generated Mapper will be declared {@code public}. */ @@ -24,5 +24,5 @@ public enum ClassAccessibility { /** * The generated Mapper will have no visibility modifier ("package-private"). */ - DEFAULT + PACKAGE_PRIVATE } diff --git a/core/src/main/java/org/mapstruct/Mapper.java b/core/src/main/java/org/mapstruct/Mapper.java index 71dd475923..c502dfe8a9 100644 --- a/core/src/main/java/org/mapstruct/Mapper.java +++ b/core/src/main/java/org/mapstruct/Mapper.java @@ -14,7 +14,7 @@ import org.mapstruct.control.MappingControl; import org.mapstruct.factory.Mappers; -import static org.mapstruct.ClassAccessibility.LIKE_ABSTRACTION; +import static org.mapstruct.ClassAccessibility.DEFAULT; import static org.mapstruct.NullValueCheckStrategy.ON_IMPLICIT_CONVERSION; import static org.mapstruct.SubclassExhaustiveStrategy.COMPILE_ERROR; @@ -397,5 +397,5 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() default * @return The {@link ClassAccessibility} ({@code public} or package-private) for the generated Mapper * implementation */ - ClassAccessibility accessibility() default LIKE_ABSTRACTION; + ClassAccessibility accessibility() default DEFAULT; } diff --git a/core/src/main/java/org/mapstruct/MapperConfig.java b/core/src/main/java/org/mapstruct/MapperConfig.java index 2704f77cdf..a81e3e8d4f 100644 --- a/core/src/main/java/org/mapstruct/MapperConfig.java +++ b/core/src/main/java/org/mapstruct/MapperConfig.java @@ -14,7 +14,7 @@ import org.mapstruct.control.MappingControl; import org.mapstruct.factory.Mappers; -import static org.mapstruct.ClassAccessibility.LIKE_ABSTRACTION; +import static org.mapstruct.ClassAccessibility.DEFAULT; import static org.mapstruct.NullValueCheckStrategy.ON_IMPLICIT_CONVERSION; import static org.mapstruct.SubclassExhaustiveStrategy.COMPILE_ERROR; @@ -366,6 +366,6 @@ MappingInheritanceStrategy mappingInheritanceStrategy() * @return The {@link ClassAccessibility} ({@code public} or package-private) for the generated Mapper * implementation */ - ClassAccessibility accessibility() default LIKE_ABSTRACTION; + ClassAccessibility accessibility() default DEFAULT; } diff --git a/documentation/src/main/asciidoc/chapter-4-retrieving-a-mapper.asciidoc b/documentation/src/main/asciidoc/chapter-4-retrieving-a-mapper.asciidoc index 29713df147..ec817e0cdf 100644 --- a/documentation/src/main/asciidoc/chapter-4-retrieving-a-mapper.asciidoc +++ b/documentation/src/main/asciidoc/chapter-4-retrieving-a-mapper.asciidoc @@ -141,7 +141,7 @@ By default, the generated Mapper implementation will have the same accessibility [source, java, linenums] [subs="verbatim,attributes"] ---- -@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, accessibility = ClassAccessibility.DEFAULT) +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, accessibility = ClassAccessibility.PACKAGE_PRIVATE) public interface CarMapper { CarDto carToCarDto(Car car); @@ -150,4 +150,4 @@ public interface CarMapper { ---- ==== -In this case, the generated Mapper will have no modifier. Possible values are `ClassAccessibility.LIKE_ABSTRACTION` (the default behavior described above), `ClassAccessibility.PUBLIC`, and `ClassAccessibility.DEFAULT` (no modifier, i.e. package-private). +In this case, the generated Mapper will have no modifier. Possible values are `ClassAccessibility.DEFAULT` (the behavior described above), `ClassAccessibility.PUBLIC`, and `ClassAccessibility.PACKAGE_PRIVATE` (no modifier). diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/ClassAccessibilityGem.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/ClassAccessibilityGem.java index 71aa6f4aa6..609c6d04e7 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/gem/ClassAccessibilityGem.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/ClassAccessibilityGem.java @@ -11,5 +11,5 @@ * @author Raimund Klein */ public enum ClassAccessibilityGem { - LIKE_ABSTRACTION, PUBLIC, DEFAULT + DEFAULT, PUBLIC, PACKAGE_PRIVATE } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java index 5cb344d9cc..c261358455 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java @@ -17,7 +17,7 @@ import org.mapstruct.ap.internal.option.Options; import org.mapstruct.ap.internal.version.VersionInformation; -import static org.mapstruct.ap.internal.gem.ClassAccessibilityGem.LIKE_ABSTRACTION; +import static org.mapstruct.ap.internal.gem.ClassAccessibilityGem.DEFAULT; /** * Represents a type implementing a mapper interface (annotated with {@code @Mapper}). This is the root object of the @@ -112,7 +112,7 @@ public Mapper build() { Type definitionType = typeFactory.getType( element ); Accessibility accessibility = - classAccessibility == LIKE_ABSTRACTION ? Accessibility.fromModifiers( element.getModifiers() ) : + classAccessibility == DEFAULT ? Accessibility.fromModifiers( element.getModifiers() ) : Accessibility.valueOf( classAccessibility.name() ); return new Mapper( diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Accessibility.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Accessibility.java index ef5be01285..c516b337e5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Accessibility.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Accessibility.java @@ -14,7 +14,7 @@ * @author Andreas Gudian */ public enum Accessibility { - PRIVATE( "private" ), DEFAULT( "" ), PROTECTED( "protected" ), PUBLIC( "public" ); + PRIVATE( "private" ), PACKAGE_PRIVATE("" ), PROTECTED("protected" ), PUBLIC("public" ); private final String keyword; @@ -37,6 +37,6 @@ else if ( modifiers.contains( Modifier.PRIVATE ) ) { return PRIVATE; } - return DEFAULT; + return PACKAGE_PRIVATE; } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ClassAccessibilityTest.java b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ClassAccessibilityTest.java index 7ae082ec78..8bac5841f1 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ClassAccessibilityTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ClassAccessibilityTest.java @@ -19,7 +19,7 @@ PublicAbstractionMapper.class, PackageAbstractionMapper.class, ForcedPublicMapper.class, - ForcedDefaultMapper.class + ForcedPackagePrivateMapper.class }) public class ClassAccessibilityTest { @@ -34,7 +34,7 @@ public void shouldCreateModifierAccordingToAnnotation() throws Exception { Class forcedPublic = loadForMapper( ForcedPublicMapper.class ); assertThat( isPublic( forcedPublic.getModifiers() ) ).isTrue(); - Class forcedDefault = loadForMapper( ForcedDefaultMapper.class ); + Class forcedDefault = loadForMapper( ForcedPackagePrivateMapper.class ); assertThat( isDefault( forcedDefault.getModifiers() ) ).isTrue(); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedDefaultMapper.java b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedPackagePrivateMapper.java similarity index 74% rename from processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedDefaultMapper.java rename to processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedPackagePrivateMapper.java index a3f8677cb9..191aadb2a0 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedDefaultMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedPackagePrivateMapper.java @@ -8,8 +8,8 @@ import org.mapstruct.ClassAccessibility; import org.mapstruct.Mapper; -@Mapper(accessibility = ClassAccessibility.DEFAULT) -public interface ForcedDefaultMapper { +@Mapper(accessibility = ClassAccessibility.PACKAGE_PRIVATE) +public interface ForcedPackagePrivateMapper { Target map(Source value); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/mapperconfig/CentralConfig.java b/processor/src/test/java/org/mapstruct/ap/test/mapperconfig/CentralConfig.java index 771700ab77..3caeb8cbba 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/mapperconfig/CentralConfig.java +++ b/processor/src/test/java/org/mapstruct/ap/test/mapperconfig/CentralConfig.java @@ -15,7 +15,7 @@ */ @MapperConfig(uses = {CustomMapperViaMapperConfig.class}, unmappedTargetPolicy = ReportingPolicy.ERROR, - accessibility = ClassAccessibility.DEFAULT) + accessibility = ClassAccessibility.PACKAGE_PRIVATE) public class CentralConfig { } From 9af8669f5f056dcc050b2e4f6c70a2fb3d990523 Mon Sep 17 00:00:00 2001 From: Raimund Klein <770876+Chessray@users.noreply.github.com> Date: Tue, 23 Jun 2026 16:34:19 +0100 Subject: [PATCH 6/6] Formatting --- .../org/mapstruct/ap/internal/model/common/Accessibility.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Accessibility.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Accessibility.java index c516b337e5..1988e4f90b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Accessibility.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Accessibility.java @@ -14,7 +14,7 @@ * @author Andreas Gudian */ public enum Accessibility { - PRIVATE( "private" ), PACKAGE_PRIVATE("" ), PROTECTED("protected" ), PUBLIC("public" ); + PRIVATE( "private" ), PACKAGE_PRIVATE( "" ), PROTECTED( "protected" ), PUBLIC( "public" ); private final String keyword;