Skip to content

Commit

Permalink
changes for using a large constructor
Browse files Browse the repository at this point in the history
  • Loading branch information
Quinn Gil committed Mar 11, 2021
1 parent 228660c commit c036a66
Show file tree
Hide file tree
Showing 10 changed files with 284 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
MappingProcessor.UNMAPPED_TARGET_POLICY,
MappingProcessor.DEFAULT_COMPONENT_MODEL,
MappingProcessor.DEFAULT_INJECTION_STRATEGY,
MappingProcessor.USE_MOST_PARAMETERS_CONSTRUCTOR,
MappingProcessor.VERBOSE
})
public class MappingProcessor extends AbstractProcessor {
Expand All @@ -102,6 +103,7 @@ public class MappingProcessor extends AbstractProcessor {
protected static final String DEFAULT_COMPONENT_MODEL = "mapstruct.defaultComponentModel";
protected static final String DEFAULT_INJECTION_STRATEGY = "mapstruct.defaultInjectionStrategy";
protected static final String ALWAYS_GENERATE_SERVICE_FILE = "mapstruct.alwaysGenerateServicesFile";
protected static final String USE_MOST_PARAMETERS_CONSTRUCTOR = "mapstruct.useMostParametersConstructor";
protected static final String VERBOSE = "mapstruct.verbose";

private Options options;
Expand Down Expand Up @@ -142,6 +144,7 @@ private Options createOptions() {
processingEnv.getOptions().get( DEFAULT_COMPONENT_MODEL ),
processingEnv.getOptions().get( DEFAULT_INJECTION_STRATEGY ),
Boolean.valueOf( processingEnv.getOptions().get( ALWAYS_GENERATE_SERVICE_FILE ) ),
Boolean.valueOf( processingEnv.getOptions().get( USE_MOST_PARAMETERS_CONSTRUCTOR ) ),
Boolean.valueOf( processingEnv.getOptions().get( VERBOSE ) )
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -628,13 +628,16 @@ private ConstructorAccessor getConstructorAccessor(Type type) {

// The rules for picking a constructor are the following:
// 1. Constructor annotated with @Default (from any package) has highest precedence
// 2. If there is a single public constructor then it would be used to construct the object
// 3. If a parameterless constructor exists then it would be used to construct the object, and the other
// 2. If configured to use the single largest parameter constructor, use that
// 3. If there is a single public constructor then it would be used to construct the object
// 4. If a parameterless constructor exists then it would be used to construct the object, and the other
// constructors will be ignored
ExecutableElement defaultAnnotatedConstructor = null;
ExecutableElement parameterLessConstructor = null;
ExecutableElement mostParametersConstructor = null;
List<ExecutableElement> accessibleConstructors = new ArrayList<>( constructors.size() );
List<ExecutableElement> publicConstructors = new ArrayList<>( );
boolean multipleMostParametersConstructor = false;

for ( ExecutableElement constructor : constructors ) {
if ( constructor.getModifiers().contains( Modifier.PRIVATE ) ) {
Expand All @@ -647,6 +650,23 @@ private ConstructorAccessor getConstructorAccessor(Type type) {
break;
}

if ( ctx.getOptions().isUseMostParametersConstructor() && !constructor.getParameters().isEmpty() ) {
// If configured to select the single most parameter constructor
// and the new constructor has parameters (no parameters is handled separately)
if ( mostParametersConstructor == null ||
mostParametersConstructor.getParameters().size() < constructor.getParameters().size() ) {
// Select the constructor if one hasn't been set
// or if the constructor has more parameters than our existing selection
mostParametersConstructor = constructor;
// Assigning a new mostParametersConstructor means we don't have multiple
multipleMostParametersConstructor = false;
}
else if ( mostParametersConstructor.getParameters().size() == constructor.getParameters().size() ) {
// If the constructors have the same length, flag it.
multipleMostParametersConstructor = true;
}
}

if ( constructor.getParameters().isEmpty() ) {
parameterLessConstructor = constructor;
}
Expand All @@ -664,6 +684,14 @@ private ConstructorAccessor getConstructorAccessor(Type type) {
return getConstructorAccessor( type, defaultAnnotatedConstructor );
}

if ( ctx.getOptions().isUseMostParametersConstructor() &&
!multipleMostParametersConstructor &&
mostParametersConstructor != null ) {
// If configured to use the largest parameter
// and there aren't multiple at the same length
return getConstructorAccessor( type, mostParametersConstructor );
}

if ( publicConstructors.size() == 1 ) {
// If there is a single public constructor then use that one
ExecutableElement publicConstructor = publicConstructors.get( 0 );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,21 @@ public class Options {
private final boolean alwaysGenerateSpi;
private final String defaultComponentModel;
private final String defaultInjectionStrategy;
private final boolean useMostParametersConstructor;
private final boolean verbose;

public Options(boolean suppressGeneratorTimestamp, boolean suppressGeneratorVersionComment,
ReportingPolicyGem unmappedTargetPolicy,
String defaultComponentModel, String defaultInjectionStrategy,
boolean alwaysGenerateSpi, boolean verbose) {
boolean alwaysGenerateSpi, boolean useMostParametersConstructor,
boolean verbose) {
this.suppressGeneratorTimestamp = suppressGeneratorTimestamp;
this.suppressGeneratorVersionComment = suppressGeneratorVersionComment;
this.unmappedTargetPolicy = unmappedTargetPolicy;
this.defaultComponentModel = defaultComponentModel;
this.defaultInjectionStrategy = defaultInjectionStrategy;
this.alwaysGenerateSpi = alwaysGenerateSpi;
this.useMostParametersConstructor = useMostParametersConstructor;
this.verbose = verbose;
}

Expand Down Expand Up @@ -59,6 +62,10 @@ public boolean isAlwaysGenerateSpi() {
return alwaysGenerateSpi;
}

public boolean isUseMostParametersConstructor() {
return useMostParametersConstructor;
}

public boolean isVerbose() {
return verbose;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/

/**
* @author Quinn Gil
*
*/
package org.mapstruct.ap.test.largestconstructor;

public class IntSourceDto {
private final int value;

public IntSourceDto(int value) {
this.value = value;
}

public int getIntValue() {
return value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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.largestconstructor;

import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

/**
* @author Quinn Gil
*
*/
@Mapper
public interface LargestConstructorMapper {

LargestConstructorMapper INSTANCE = Mappers.getMapper( LargestConstructorMapper.class );

SingleLargestDto mapSingleToSingle(StringSourceDto dto);

SingleLargestWithDefaultDto mapMultipleToDefault(IntSourceDto dto);

MultipleLargestDto mapMultipleToEmpty(StringSourceDto dto);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* 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.largestconstructor;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mapstruct.ap.test.constructor.Default;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption;
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;

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

/***
* @author Quinn Gil
*/
@WithClasses({
Default.class,
LargestConstructorMapper.class,
SingleLargestDto.class,
StringSourceDto.class,
IntSourceDto.class,
SingleLargestWithDefaultDto.class,
MultipleLargestDto.class
})
@RunWith(AnnotationProcessorTestRunner.class)
public class LargestConstructorTest {

@Test
@ProcessorOption( name = "mapstruct.useMostParametersConstructor", value = "true")
public void useLargestConstructorWithSetting() {
StringSourceDto source = new StringSourceDto("source value");

SingleLargestDto target = LargestConstructorMapper.INSTANCE.mapSingleToSingle( source );

assertThat( target.getStringValue() ).isEqualTo( "source value" );
}

@Test
public void useEmptyConstructorWithoutSetting() {
StringSourceDto source = new StringSourceDto("source value");

SingleLargestDto target = LargestConstructorMapper.INSTANCE.mapSingleToSingle( source );

assertThat( target.getStringValue() ).isEqualTo( "from empty constructor" );
}

@Test
@ProcessorOption( name = "mapstruct.useMostParametersConstructor", value = "true")
public void useDefaultAnnotationWithSetting() {
IntSourceDto source = new IntSourceDto(1234);

SingleLargestWithDefaultDto target = LargestConstructorMapper.INSTANCE.mapMultipleToDefault( source );

assertThat( target.getValue() ).isEqualTo( "from default annotation constructor" );
}

@Test
@ProcessorOption( name = "mapstruct.useMostParametersConstructor", value = "true")
public void useEmptyWhenMultipleLargestPresent() {
StringSourceDto source = new StringSourceDto("source value");

MultipleLargestDto target = LargestConstructorMapper.INSTANCE.mapMultipleToEmpty( source );

assertThat( target.getStringValue() ).isEqualTo( "from empty constructor" );
}
}
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.largestconstructor;

/**
* @author Quinn Gil
*
*/
public class MultipleLargestDto {

private final String stringValue;
private final int intValue;

public MultipleLargestDto(String stringValue) {
this.stringValue = stringValue;
this.intValue = -1;
}

public MultipleLargestDto(int intValue) {
this.intValue = intValue;
this.stringValue = "from int constructor";
}

public MultipleLargestDto() {
stringValue = "from empty constructor";
intValue = 1;
}

public String getStringValue() {
return stringValue;
}

public int getIntValue() {
return intValue;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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.largestconstructor;

/**
* @author Quinn Gil
*
*/
public class SingleLargestDto {

private final String stringValue;

public SingleLargestDto(String stringValue) {
this.stringValue = stringValue;
}

public SingleLargestDto() {
stringValue = "from empty constructor";
}

public String getStringValue() {
return stringValue;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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.largestconstructor;

import org.mapstruct.ap.test.constructor.Default;

/**
* @author Quinn Gil
*
*/
public class SingleLargestWithDefaultDto {

private final String value;
private final int intValue;

public SingleLargestWithDefaultDto(String value, int intValue) {
this.value = value;
this.intValue = intValue;
}

@Default
public SingleLargestWithDefaultDto(int intValue) {
value = "from default annotation constructor";
this.intValue = intValue;
}

public String getValue() {
return value;
}

public int getIntValue() {
return intValue;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* 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.largestconstructor;

/**
* @author Quinn Gil
*
*/
public class StringSourceDto {
private final String value;

public StringSourceDto(String value) {
this.value = value;
}

public String getStringValue() {
return value;
}
}

0 comments on commit c036a66

Please sign in to comment.