Skip to content

Commit

Permalink
mapstruct#674 Support Java 8 java.util.Optional type
Browse files Browse the repository at this point in the history
  • Loading branch information
ibogdanchikov committed Jun 6, 2022
1 parent d7c0d15 commit cbec95f
Show file tree
Hide file tree
Showing 11 changed files with 348 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ public interface CarMapper {
* Between `java.net.URL` and `String`.
** When converting from a `String`, the value needs to be a valid https://en.wikipedia.org/wiki/URL[URL] otherwise a `MalformedURLException` is thrown.

* Between `java.util.Optional<T>` and `T`.

[[mapping-object-references]]
=== Mapping object references

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.mapstruct.ap.internal.model.common.TypeFactory;
import org.mapstruct.ap.internal.util.JaxbConstants;
import org.mapstruct.ap.internal.util.JodaTimeConstants;
import org.mapstruct.ap.internal.util.OptionalConstants;
import org.mapstruct.ap.internal.util.XmlConstants;

/**
Expand All @@ -24,7 +25,7 @@ public class BuiltInMappingMethods {

public BuiltInMappingMethods(TypeFactory typeFactory) {
boolean isXmlGregorianCalendarPresent = isXmlGregorianCalendarAvailable( typeFactory );
builtInMethods = new ArrayList<>( 20 );
builtInMethods = new ArrayList<>( 22 );
if ( isXmlGregorianCalendarPresent ) {
builtInMethods.add( new DateToXmlGregorianCalendar( typeFactory ) );
builtInMethods.add( new XmlGregorianCalendarToDate( typeFactory ) );
Expand Down Expand Up @@ -56,6 +57,15 @@ public BuiltInMappingMethods(TypeFactory typeFactory) {
builtInMethods.add( new JodaLocalTimeToXmlGregorianCalendar( typeFactory ) );
builtInMethods.add( new XmlGregorianCalendarToJodaLocalTime( typeFactory ) );
}

if ( isOptionalAvailable( typeFactory ) ) {
builtInMethods.add( new OptionalToValue( typeFactory ) );
builtInMethods.add( new ValueToOptional( typeFactory ) );
}
}

private static boolean isOptionalAvailable(TypeFactory typeFactory) {
return typeFactory.isTypeAvailable( OptionalConstants.OPTIONAL_FQN );
}

private static boolean isJaxbAvailable(TypeFactory typeFactory) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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.model.source.builtin;

import java.util.Optional;
import java.util.Set;

import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.common.TypeFactory;

import static org.mapstruct.ap.internal.util.Collections.asSet;

/**
* @author Iaroslav Bogdanchikov
*/
public class OptionalToValue extends BuiltInMethod {

private final Parameter parameter;
private final Type returnType;
private final Set<Type> importTypes;

public OptionalToValue(TypeFactory typeFactory) {
Type type = typeFactory.getType( Optional.class );
this.parameter = new Parameter( "optional", type );
this.returnType = type.getTypeParameters().get( 0 );
this.importTypes = asSet( parameter.getType() );
}

@Override
public boolean doTypeVarsMatch(Type sourceType, Type targetType) {
boolean match = false;
if ( sourceType.getTypeParameters().size() == 1 ) {
match = sourceType.getTypeParameters().get( 0 ).isAssignableTo( targetType );
}
return match;
}

@Override
public Parameter getParameter() {
return parameter;
}

@Override
public Type getReturnType() {
return returnType;
}

@Override
public Set<Type> getImportTypes() {
return importTypes;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* 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.model.source.builtin;

import java.util.List;
import java.util.Optional;
import java.util.Set;

import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.common.TypeFactory;

import static org.mapstruct.ap.internal.util.Collections.asSet;
import static org.mapstruct.ap.internal.util.Collections.first;

/**
* @author Iaroslav Bogdanchikov
*/
public class ValueToOptional extends BuiltInMethod {

private final Parameter parameter;
private final Type returnType;
private final Set<Type> importTypes;

public ValueToOptional(TypeFactory typeFactory) {
Type type = typeFactory.getType( Optional.class );
this.parameter = new Parameter( "value", type.getTypeParameters().get(0) );
this.returnType = type;
this.importTypes = asSet( parameter.getType() );
}

@Override
public boolean matches(final List<Type> sourceTypes, final Type targetType) {
if ( sourceTypes.size() != 1 ) {
return false;
}

Type sourceType = first( sourceTypes );

Type returnType = getReturnType().resolveParameterToType( sourceType, getParameter().getType() ).getMatch();
if ( returnType == null ) {
return false;
}

return returnType.erasure().isAssignableTo( targetType ) && returnType != sourceType;
}

@Override
public Parameter getParameter() {
return parameter;
}

@Override
public Type getReturnType() {
return returnType;
}

@Override
public Set<Type> getImportTypes() {
return importTypes;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* 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.util;

/**
* Helper holding Optional fully qualified class name for conversion registration.
*/
public final class OptionalConstants {

public static final String OPTIONAL_FQN = "java.util.Optional";

private OptionalConstants() {
}

}
Original file line number Diff line number Diff line change
@@ -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
-->
<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" -->
private <T> T ${name}( <@includeModel object=findType("Optional") raw=true/><T> optional ) {
if ( optional == null ) {
return null;
}

return optional.orElse( null );
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<#--
Copyright MapStruct Authors.
Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
-->
<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" -->
private <T> Optional<T> ${name}( T value ) {
return Optional.ofNullable( value );
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import javax.xml.bind.JAXBElement;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
Expand All @@ -33,8 +34,10 @@
import org.mapstruct.ap.test.builtin.bean.JaxbElementProperty;
import org.mapstruct.ap.test.builtin.bean.SomeType;
import org.mapstruct.ap.test.builtin.bean.SomeTypeProperty;
import org.mapstruct.ap.test.builtin.bean.SourceOptionalProperty;
import org.mapstruct.ap.test.builtin.bean.StringListProperty;
import org.mapstruct.ap.test.builtin.bean.StringProperty;
import org.mapstruct.ap.test.builtin.bean.TargetOptionalProperty;
import org.mapstruct.ap.test.builtin.bean.XmlGregorianCalendarProperty;
import org.mapstruct.ap.test.builtin.java8time.bean.ZonedDateTimeProperty;
import org.mapstruct.ap.test.builtin.java8time.mapper.CalendarToZonedDateTimeMapper;
Expand All @@ -48,6 +51,7 @@
import org.mapstruct.ap.test.builtin.mapper.JaxbListMapper;
import org.mapstruct.ap.test.builtin.mapper.JaxbMapper;
import org.mapstruct.ap.test.builtin.mapper.MapSourceTargetMapper;
import org.mapstruct.ap.test.builtin.mapper.OptionalMapper;
import org.mapstruct.ap.test.builtin.mapper.StringToCalendarMapper;
import org.mapstruct.ap.test.builtin.mapper.StringToXmlGregCalMapper;
import org.mapstruct.ap.test.builtin.mapper.XmlGregCalToCalendarMapper;
Expand Down Expand Up @@ -84,6 +88,55 @@
@DefaultTimeZone("Europe/Berlin")
public class BuiltInTest {

@ProcessorTest
@WithClasses( {
OptionalMapper.class,
SourceOptionalProperty.class,
TargetOptionalProperty.class
} )
public void shouldApplyBuiltInOnNonEmptyOptional() {
SourceOptionalProperty source = new SourceOptionalProperty();
source.setOptional( Optional.of( "TEST" ) );
source.setFromOptionalProp( Optional.of( "FROM OPTIONAL TEST" ) );
source.setToOptionalProp( "TO OPTIONAL TEST" );
source.fromOptionalPubProp = Optional.of( "FROM OPTIONAL PUBLIC TEST" );
source.toOptionalPubProp = "TO OPTIONAL PUBLIC TEST";

TargetOptionalProperty target = OptionalMapper.INSTANCE.map( source );
assertThat( target ).isNotNull();
assertThat( target.getOptional() ).isPresent();
assertThat( target.getOptional() ).get().isEqualTo( "TEST" );
assertThat( target.getFromOptionalProp() ).isEqualTo( "FROM OPTIONAL TEST" );
assertThat( target.getToOptionalProp() ).isPresent();
assertThat( target.getToOptionalProp() ).get().isEqualTo( "TO OPTIONAL TEST" );
assertThat( target.fromOptionalPubProp ).isEqualTo( "FROM OPTIONAL PUBLIC TEST" );
assertThat( target.toOptionalPubProp ).isPresent();
assertThat( target.toOptionalPubProp ).get().isEqualTo( "TO OPTIONAL PUBLIC TEST" );
}

@ProcessorTest
@WithClasses( {
OptionalMapper.class,
SourceOptionalProperty.class,
TargetOptionalProperty.class
} )
public void shouldApplyBuiltInOnEmptyOptional() {
SourceOptionalProperty source = new SourceOptionalProperty();
source.setOptional( Optional.empty() );
source.setFromOptionalProp( Optional.empty() );
source.setToOptionalProp( null );
source.fromOptionalPubProp = Optional.empty();
source.toOptionalPubProp = null;

TargetOptionalProperty target = OptionalMapper.INSTANCE.map( source );
assertThat( target ).isNotNull();
assertThat( target.getOptional() ).isEmpty();
assertThat( target.getFromOptionalProp() ).isNull();
assertThat( target.getToOptionalProp() ).isEmpty();
assertThat( target.fromOptionalPubProp ).isNull( );
assertThat( target.toOptionalPubProp ).isEmpty();
}

@ProcessorTest
@WithClasses( {
JaxbMapper.class,
Expand Down
Original file line number Diff line number Diff line change
@@ -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.builtin.bean;

import java.util.Optional;

public class SourceOptionalProperty {

// CHECKSTYLE:OFF
public Optional<String> fromOptionalPubProp;
// CHECKSTYLE:ON

// CHECKSTYLE:OFF
public String toOptionalPubProp;
// CHECKSTYLE:ON

private Optional<String> fromOptionalProp;

private String toOptionalProp;

private Optional<String> optional;

public Optional<String> getFromOptionalProp() {
return fromOptionalProp;
}

public void setFromOptionalProp(Optional<String> fromOptionalProp) {
this.fromOptionalProp = fromOptionalProp;
}

public String getToOptionalProp() {
return toOptionalProp;
}

public void setToOptionalProp(String toOptionalProp) {
this.toOptionalProp = toOptionalProp;
}

public Optional<String> getOptional() {
return optional;
}

public void setOptional(Optional<String> optional) {
this.optional = optional;
}
}
Original file line number Diff line number Diff line change
@@ -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.builtin.bean;

import java.util.Optional;

public class TargetOptionalProperty {

// CHECKSTYLE:OFF
public Optional<String> toOptionalPubProp;
// CHECKSTYLE:ON

// CHECKSTYLE:OFF
public String fromOptionalPubProp;
// CHECKSTYLE:ON

private Optional<String> toOptionalProp;

private String fromOptionalProp;

private Optional<String> optional;

public Optional<String> getToOptionalProp() {
return toOptionalProp;
}

public void setToOptionalProp(Optional<String> toOptionalProp) {
this.toOptionalProp = toOptionalProp;
}

public String getFromOptionalProp() {
return fromOptionalProp;
}

public void setFromOptionalProp(String fromOptionalProp) {
this.fromOptionalProp = fromOptionalProp;
}

public Optional<String> getOptional() {
return optional;
}

public void setOptional(Optional<String> optional) {
this.optional = optional;
}
}
Loading

0 comments on commit cbec95f

Please sign in to comment.