Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,14 @@ private static List<LifecycleCallbackMethodReference> collectLifecycleCallbackMe
selectionParameters,
ctx.getTypeFactory(),
parameterBindingsProvider
)
),
ctx::markLifecycleCallbackMethodAsUsed
);

for ( SelectedMethod<SourceMethod> matchingMethod : matchingMethods ) {
ctx.markLifecycleCallbackMethodAsUsed( matchingMethod.getMethod() );
}

return toLifecycleCallbackMethodRefs(
method,
matchingMethods,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
Expand Down Expand Up @@ -124,6 +125,8 @@ Assignment getTargetAssignment(Method mappingMethod, ForgedMethodHistory descrip
private final List<MappingMethod> mappingsToGenerate = new ArrayList<>();
private final Map<ForgedMethod, ForgedMethod> forgedMethodsUnderCreation =
new HashMap<>();
private final Set<SourceMethod> lifecycleCallbackMethods = new LinkedHashSet<>();
private final Set<SourceMethod> usedLifecycleCallbackMethods = new HashSet<>();

//CHECKSTYLE:OFF
public MappingBuilderContext(TypeFactory typeFactory,
Expand Down Expand Up @@ -154,6 +157,11 @@ public MappingBuilderContext(TypeFactory typeFactory,
this.mapperTypeElement = mapper;
this.sourceModel = sourceModel;
this.mapperReferences = mapperReferences;
for ( SourceMethod sourceMethod : sourceModel ) {
if ( isLifecycleCallbackDeclaredOnMapper( sourceMethod ) ) {
lifecycleCallbackMethods.add( sourceMethod );
}
}
}
//CHECKSTYLE:ON

Expand All @@ -178,6 +186,43 @@ public List<SourceMethod> getSourceModel() {
return sourceModel;
}

/**
* Records that a lifecycle callback declared by this mapper was applicable to a generated mapping method.
* Methods that match by type but are later dropped in favor of a more specific overload still count as used.
*
* @param lifecycleCallbackMethod the applicable callback method
*/
public void markLifecycleCallbackMethodAsUsed(SourceMethod lifecycleCallbackMethod) {
if ( lifecycleCallbackMethods.contains( lifecycleCallbackMethod ) ) {
usedLifecycleCallbackMethods.add( lifecycleCallbackMethod );
}
}

/**
* Returns lifecycle callbacks declared directly on this mapper that were not applicable to any generated
* mapping method.
*
* @return the unused lifecycle callback methods
*/
public Set<SourceMethod> getUnusedLifecycleCallbackMethods() {
Set<SourceMethod> unusedLifecycleCallbackMethods = new LinkedHashSet<>( lifecycleCallbackMethods );
unusedLifecycleCallbackMethods.removeAll( usedLifecycleCallbackMethods );
return unusedLifecycleCallbackMethods;
}

/**
* Only callbacks declared on the mapper being processed are reported. Inherited methods and methods coming from
* {@code Mapper#uses} are ignored, because those are commonly overloaded by type and shared across mappers.
*
* @param sourceMethod the method to inspect
* @return {@code true} if the method is a lifecycle callback declared on this mapper
*/
private boolean isLifecycleCallbackDeclaredOnMapper(SourceMethod sourceMethod) {
return sourceMethod.isLifecycleCallbackMethod()
&& sourceMethod.getDeclaringMapper() == null
&& mapperTypeElement.equals( sourceMethod.getExecutable().getEnclosingElement() );
}

public List<MapperReference> getMapperReferences() {
return mapperReferences;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;

import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.option.Options;
Expand Down Expand Up @@ -58,13 +59,36 @@ public MethodSelectors(TypeUtils typeUtils, ElementUtils elementUtils,
*/
public <T extends Method> List<SelectedMethod<T>> getMatchingMethods(List<T> methods,
SelectionContext context) {
return getMatchingMethods( methods, context, null );
}

/**
* Selects those methods which match the given types and other criteria.
*
* @param <T> either SourceMethod or BuiltInMethod
* @param methods list of available methods
* @param context the selection context that should be used in the matching process
* @param applicableBeforeLifecycleDedup invoked with every remaining candidate right before overload
* deduplication; used to treat type-matching lifecycle overloads as used even when a more specific
* overload is selected. May be {@code null}.
* @return list of methods that passes the matching process
*/
public <T extends Method> List<SelectedMethod<T>> getMatchingMethods(List<T> methods,
SelectionContext context,
Consumer<T> applicableBeforeLifecycleDedup) {

List<SelectedMethod<T>> candidates = new ArrayList<>( methods.size() );
for ( T method : methods ) {
candidates.add( new SelectedMethod<>( method ) );
}

for ( MethodSelector selector : selectors ) {
if ( applicableBeforeLifecycleDedup != null
&& selector instanceof LifecycleOverloadDeduplicateSelector ) {
for ( SelectedMethod<T> candidate : candidates ) {
applicableBeforeLifecycleDedup.accept( candidate.getMethod() );
}
}
candidates = selector.getMatchingMethods( candidates, context );
}
return candidates;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,14 @@ private Mapper getMapper(TypeElement element, MapperOptions mapperOptions, List<
);
}

for ( SourceMethod lifecycleCallbackMethod : mappingContext.getUnusedLifecycleCallbackMethods() ) {
messager.printMessage(
lifecycleCallbackMethod.getExecutable(),
Message.LIFECYCLEMETHOD_NOT_USED,
lifecycleCallbackMethod.getName()
);
}

return mapper;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ public enum Message {
SUBCLASSMAPPING_ILLOGICAL_ORDER( "SubclassMapping annotation for '%s' found after '%s', but all '%s' objects are also instances of '%s'.", Diagnostic.Kind.WARNING ),

LIFECYCLEMETHOD_AMBIGUOUS_PARAMETERS( "Lifecycle method has multiple matching parameters (e. g. same type), in this case please ensure to name the parameters in the lifecycle and mapping method identical. This lifecycle method will not be used for the mapping method '%s'.", Diagnostic.Kind.WARNING),
LIFECYCLEMETHOD_NOT_USED( "Lifecycle method %s is not used.", Diagnostic.Kind.WARNING ),

DECORATOR_NO_SUBTYPE( "Specified decorator type is no subtype of the annotated mapper type." ),
DECORATOR_CONSTRUCTOR( "Specified decorator type has no default constructor nor a constructor with a single parameter accepting the decorated mapper type." ),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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.bugs._3385;

import org.mapstruct.AfterMapping;
import org.mapstruct.BeforeMapping;
import org.mapstruct.Mapper;

@Mapper
public interface Issue3385Mapper {

Target map(Source source);

@AfterMapping
@SuppressWarnings( "unused" )
default void used(Source source) {
}

@AfterMapping
@SuppressWarnings( "unused" )
default void unused(String notAMappingParameter, Source source) {
}

@BeforeMapping
@SuppressWarnings( "unused" )
default void unusedBefore(String notAMappingParameter, Source source) {
}

class Source {
private String name;

public String getName() {
return name;
}

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

class Target {
private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* 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.bugs._3385;

import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.ProcessorTest;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;
import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic;
import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;

@WithClasses(Issue3385Mapper.class)
@IssueKey("3385")
class Issue3385Test {

@ProcessorTest
@ExpectedCompilationOutcome(
value = CompilationResult.SUCCEEDED,
diagnostics = {
@Diagnostic(
type = Issue3385Mapper.class,
kind = javax.tools.Diagnostic.Kind.WARNING,
line = 24,
message = "Lifecycle method unused is not used."
),
@Diagnostic(
type = Issue3385Mapper.class,
kind = javax.tools.Diagnostic.Kind.WARNING,
line = 29,
message = "Lifecycle method unusedBefore is not used."
)
}
)
void shouldWarnForUnusedLifecycleMethod() {
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
import org.mapstruct.ap.test.callbacks.typematching.CarMapper.CarEntity;
import org.mapstruct.ap.testutil.ProcessorTest;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;
import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic;
import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;

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

Expand All @@ -21,6 +24,23 @@
})
public class CallbackMethodTypeMatchingTest {
@ProcessorTest
@ExpectedCompilationOutcome(
value = CompilationResult.SUCCEEDED,
diagnostics = {
@Diagnostic(
type = CarMapper.class,
kind = javax.tools.Diagnostic.Kind.WARNING,
line = 26,
message = "Lifecycle method neverMatched is not used."
),
@Diagnostic(
type = CarMapper.class,
kind = javax.tools.Diagnostic.Kind.WARNING,
line = 31,
message = "Lifecycle method neverMatched is not used."
)
}
)
public void callbackMethodAreCalled() {
CarEntity carEntity = CarMapper.INSTANCE.toCarEntity( new CarDto() );

Expand Down