diff --git a/copyright.txt b/copyright.txt index d6dc8d1b8a..ed1c0a8dda 100644 --- a/copyright.txt +++ b/copyright.txt @@ -38,6 +38,7 @@ Joshua Spoerri - https://github.com/spoerri Jude Niroshan - https://github.com/JudeNiroshan Justyna Kubica-Ledzion - https://github.com/JKLedzion Kemal Özcan - https://github.com/yekeoe +Ken Wang - https://github.com/ro0sterjam Kevin Grüneberg - https://github.com/kevcodez Lukas Lazar - https://github.com/LukeLaz Nikolas Charalambidis - https://github.com/Nikolas-Charalambidis @@ -69,4 +70,4 @@ Tomek Gubala - https://github.com/vgtworld Valentin Kulesh - https://github.com/unshare Vincent Alexander Beelte - https://github.com/grandmasterpixel Winter Andreas - https://github.dev/wandi34 -Xiu Hong Kooi - https://github.com/kooixh +Xiu Hong Kooi - https://github.com/kooixh \ No newline at end of file diff --git a/core/src/main/java/org/mapstruct/NullValueMappingStrategy.java b/core/src/main/java/org/mapstruct/NullValueMappingStrategy.java index 519a28bd81..d978750400 100644 --- a/core/src/main/java/org/mapstruct/NullValueMappingStrategy.java +++ b/core/src/main/java/org/mapstruct/NullValueMappingStrategy.java @@ -28,6 +28,7 @@ public enum NullValueMappingStrategy { * case. *
  • For iterable mapping methods an empty collection will be returned.
  • *
  • For map mapping methods an empty map will be returned.
  • + *
  • For optional mapping methods an empty optional will be returned.
  • * */ RETURN_DEFAULT; diff --git a/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java b/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java index 9e06e723b6..9c0a63cbf3 100644 --- a/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java +++ b/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java @@ -36,6 +36,7 @@ public enum NullValuePropertyMappingStrategy { *

    * This means: *

      + *
    1. For {@code Optional} MapStruct generates an {@code Optional.empty()}
    2. *
    3. For {@code List} MapStruct generates an {@code ArrayList}
    4. *
    5. For {@code Map} a {@code HashMap}
    6. *
    7. For arrays an empty array
    8. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/InitDefaultValue.java b/processor/src/main/java/org/mapstruct/ap/internal/model/InitDefaultValue.java new file mode 100644 index 0000000000..086f87ead9 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/InitDefaultValue.java @@ -0,0 +1,53 @@ +/* + * 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; + +import java.util.HashSet; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * Model element to generate code that initializes a default value for a given type. + * + *

      Uses the provided factory method if available. + * Otherwise, constructs the target object using a sensible default including but not limited to: + * - an empty array for array types + * - an empty collection for collection types + * - an empty optional for optional types + * - using the public default constructor for other types if available + * + *

      If no default value can be constructed, a null is returned instead. + * TODO: Consider throwing an exception instead of returning null. + * + * @author Ken Wang + */ +public class InitDefaultValue extends ModelElement { + + private final Type targetType; + private final MethodReference factoryMethod; + + public InitDefaultValue(Type targetType, MethodReference factoryMethod) { + this.targetType = targetType; + this.factoryMethod = factoryMethod; + } + + @Override + public Set getImportTypes() { + Set types = new HashSet<>(); + types.add( targetType ); + return types; + } + + public Type getTargetType() { + return targetType; + } + + public MethodReference getFactoryMethod() { + return factoryMethod; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java index 79cd0e0566..fc893d4439 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java @@ -67,6 +67,10 @@ public MethodReference getFactoryMethod() { return this.factoryMethod; } + public InitDefaultValue getInitDefaultValueForResultType() { + return new InitDefaultValue( this.getResultType(), this.getFactoryMethod() ); + } + public List getAnnotations() { return annotations; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/OptionalMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/OptionalMappingMethod.java new file mode 100644 index 0000000000..abc7cd3225 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/OptionalMappingMethod.java @@ -0,0 +1,122 @@ +/* + * 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; + +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.source.Method; +import org.mapstruct.ap.internal.model.source.SelectionParameters; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import static org.mapstruct.ap.internal.util.Collections.first; + +/** + * Maps from a source to a target where one or the other (or both) are an {@link Optional} type. + * + * @author Ken Wang + */ +public class OptionalMappingMethod extends ContainerMappingMethod { + + public static class Builder extends ContainerMappingMethodBuilder { + + public Builder() { + super( Builder.class, "optional element" ); + } + + @Override + protected Type getElementType(Type parameterType) { + if ( parameterType.isOptionalType() ) { + return parameterType.getTypeParameters().get( 0 ); + } + else { + return parameterType; + } + } + + @Override + protected Assignment getWrapper(Assignment assignment, Method method) { + return assignment; + } + + @Override + protected OptionalMappingMethod instantiateMappingMethod(Method method, Collection existingVariables, + Assignment assignment, MethodReference factoryMethod, boolean mapNullToDefault, String loopVariableName, + List beforeMappingMethods, + List afterMappingMethods, SelectionParameters selectionParameters) { + return new OptionalMappingMethod( + method, + getMethodAnnotations(), + existingVariables, + assignment, + factoryMethod, + mapNullToDefault, + loopVariableName, + beforeMappingMethods, + afterMappingMethods, + selectionParameters + ); + } + } + + private OptionalMappingMethod(Method method, + List annotations, + Collection existingVariables, + Assignment parameterAssignment, + MethodReference factoryMethod, + boolean mapNullToDefault, + String loopVariableName, + List beforeMappingReferences, + List afterMappingReferences, + SelectionParameters selectionParameters) { + super( + method, + annotations, + existingVariables, + parameterAssignment, + factoryMethod, + mapNullToDefault, + loopVariableName, + beforeMappingReferences, + afterMappingReferences, + selectionParameters + ); + } + + @Override + public Set getImportTypes() { + Set types = super.getImportTypes(); + + types.add( getSourceElementType() ); + return types; + } + + public Type getSourceElementType() { + Type sourceParameterType = getSourceParameter().getType(); + + if ( sourceParameterType.isOptionalType() ) { + return first( sourceParameterType.determineTypeArguments( Optional.class ) ).getTypeBound(); + } + else { + return sourceParameterType; + } + } + + @Override + public Type getResultElementType() { + Type resultParameterType = getResultType(); + + if ( resultParameterType.isOptionalType() ) { + return first( resultParameterType.determineTypeArguments( Optional.class ) ); + } + else { + return resultParameterType; + } + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java index 50f23ecbff..29fab5fef5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java @@ -316,6 +316,9 @@ else if ( ( sourceType.isIterableType() && targetType.isStreamType() ) || ( sourceType.isStreamType() && targetType.isIterableType() ) ) { assignment = forgeStreamMapping( sourceType, targetType, rightHandSide ); } + else if ( sourceType.isOptionalType() || targetType.isOptionalType() ) { + assignment = forgeOptionalMapping( sourceType, targetType, rightHandSide ); + } else { assignment = forgeMapping( rightHandSide ); } @@ -755,6 +758,24 @@ private Assignment forgeWithElementMapping(Type sourceType, Type targetType, Sou return createForgedAssignment( source, methodRef, iterableMappingMethod ); } + private Assignment forgeOptionalMapping(Type sourceType, Type targetType, SourceRHS source) { + + targetType = targetType.withoutBounds(); + ForgedMethod methodRef = prepareForgedMethod( sourceType, targetType, source, "?" ); + + OptionalMappingMethod.Builder builder = new OptionalMappingMethod.Builder(); + + ContainerMappingMethod optionalMappingMethod = builder + .mappingContext( ctx ) + .method( methodRef ) + .selectionParameters( selectionParameters ) + .callingContextTargetPropertyName( targetPropertyName ) + .positionHint( positionHint ) + .build(); + + return createForgedAssignment( source, methodRef, optionalMappingMethod ); + } + private ForgedMethod prepareForgedMethod(Type sourceType, Type targetType, SourceRHS source, String suffix) { String name = getName( sourceType, targetType ); name = Strings.getSafeVariableName( name, ctx.getReservedNames() ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java index 73ec84b6b7..9b63da938c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java @@ -120,7 +120,8 @@ public SourceReference build() { ); } - String[] segments = sourceNameTrimmed.split( "\\." ); + // Split by "." but also include "?" as a separate segment for optionals + String[] segments = sourceNameTrimmed.split( "\\.|(?=\\?)" ); // start with an invalid source reference SourceReference result = new SourceReference( null, new ArrayList<>( ), false ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index f742e1ba0f..4e3c2ef274 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -52,6 +52,7 @@ import org.mapstruct.ap.internal.util.accessor.AccessorType; import org.mapstruct.ap.internal.util.accessor.FieldElementAccessor; import org.mapstruct.ap.internal.util.accessor.MapValueAccessor; +import org.mapstruct.ap.internal.util.accessor.OptionalValueAccessor; import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor; import org.mapstruct.ap.internal.util.accessor.ReadAccessor; @@ -108,6 +109,8 @@ public class Type extends ModelElement implements Comparable { private final boolean isMapType; private final boolean isVoid; private final boolean isStream; + + private final boolean isOptional; private final boolean isLiteral; private final boolean loggingVerbose; @@ -135,6 +138,7 @@ public class Type extends ModelElement implements Comparable { private Type boxedEquivalent = null; private Boolean hasAccessibleConstructor; + private Boolean hasAccessibleDefaultConstructor; private final Filters filters; @@ -146,7 +150,7 @@ public Type(TypeUtils typeUtils, ElementUtils elementUtils, TypeFactory typeFact String packageName, String name, String qualifiedName, boolean isInterface, boolean isEnumType, boolean isIterableType, boolean isCollectionType, boolean isMapType, boolean isStreamType, - Map toBeImportedTypes, + boolean isOptionalType, Map toBeImportedTypes, Map notToBeImportedTypes, Boolean isToBeImported, boolean isLiteral, boolean loggingVerbose) { @@ -172,6 +176,7 @@ public Type(TypeUtils typeUtils, ElementUtils elementUtils, TypeFactory typeFact this.isCollectionType = isCollectionType; this.isMapType = isMapType; this.isStream = isStreamType; + this.isOptional = isOptionalType; this.isVoid = typeMirror.getKind() == TypeKind.VOID; this.isLiteral = isLiteral; @@ -231,7 +236,7 @@ public String getName() { /** * Returns a String that could be used in generated code to reference to this {@link Type}.
      - *

      + *

      * The first time a name is referred-to it will be marked as to be imported. For instance * {@code LocalDateTime} can be one of {@code java.time.LocalDateTime} and {@code org.joda.LocalDateTime}) *

      @@ -259,7 +264,7 @@ public String createReferenceName() { return name; } - if ( isTopLevelTypeToBeImported() && nameWithTopLevelTypeName != null) { + if ( isTopLevelTypeToBeImported() && nameWithTopLevelTypeName != null ) { return nameWithTopLevelTypeName; } @@ -368,7 +373,7 @@ public boolean isArrayType() { } public boolean isTypeVar() { - return (typeMirror.getKind() == TypeKind.TYPEVAR); + return ( typeMirror.getKind() == TypeKind.TYPEVAR ); } public boolean isIntersection() { @@ -392,6 +397,10 @@ public boolean isStreamType() { return isStream; } + public boolean isOptionalType() { + return isOptional; + } + /** * A wild card type can have two types of bounds (mutual exclusive): extends and super. * @@ -422,7 +431,7 @@ public boolean hasExtendsBound() { /** * A type variable type can have two types of bounds (mutual exclusive): lower and upper. - * + *

      * Note that its use is only permitted on a definition (not on the place where its used). For instance: * {@code T map( T in)} * @@ -439,7 +448,7 @@ public boolean hasLowerBound() { /** * A type variable type can have two types of bounds (mutual exclusive): lower and upper. - * + *

      * Note that its use is only permitted on a definition (not on the place where its used). For instance: * {@code> T map( T in)} * @@ -554,6 +563,7 @@ public Type erasure() { isCollectionType, isMapType, isStream, + isOptional, toBeImportedTypes, notToBeImportedTypes, isToBeImported, @@ -597,6 +607,7 @@ public Type withoutBounds() { isCollectionType, isMapType, isStream, + isOptional, toBeImportedTypes, notToBeImportedTypes, isToBeImported, @@ -625,7 +636,6 @@ private Type replaceGeneric(Type oldGenericType, Type newType) { * as well. * * @param other The other type. - * * @return {@code true} if and only if this type is assignable to the given other type. */ public boolean isAssignableTo(Type other) { @@ -644,7 +654,6 @@ public boolean isAssignableTo(Type other) { * they need to be resolved first. * * @param other The other type. - * * @return {@code true} if and only if this type is assignable to the given other type. */ public boolean isRawAssignableTo(Type other) { @@ -659,6 +668,7 @@ public boolean isRawAssignableTo(Type other) { /** * removes any bounds from this type. + * * @return the raw type */ public Type asRawType() { @@ -680,6 +690,15 @@ public ReadAccessor getReadAccessor(String propertyName, boolean allowedMapToBea .orElse( null ); return new MapValueAccessor( getMethod, typeParameters.get( 1 ).getTypeMirror(), propertyName ); } + else if ( propertyName.equals( "?" ) && isOptionalType() ) { + ExecutableElement getMethod = getAllMethods() + .stream() + .filter( m -> m.getSimpleName().contentEquals( "orElse" ) ) + .filter( m -> m.getParameters().size() == 1 ) + .findAny() + .orElse( null ); + return new OptionalValueAccessor( getMethod, typeParameters.get( 0 ).getTypeMirror(), propertyName ); + } Map readAccessors = getPropertyReadAccessors(); @@ -704,7 +723,7 @@ public Map getPropertyReadAccessors() { if ( readAccessors == null ) { Map recordAccessors = filters.recordAccessorsIn( getRecordComponents() ); - Map modifiableGetters = new LinkedHashMap<>(recordAccessors); + Map modifiableGetters = new LinkedHashMap<>( recordAccessors ); List getterList = filters.getterMethodsIn( getAllMethods() ); for ( ReadAccessor getter : getterList ) { @@ -782,7 +801,7 @@ public Map getPropertyPresenceCheckers() { * @param cmStrategy collection mapping strategy * @return an unmodifiable map of all write accessors indexed by property name */ - public Map getPropertyWriteAccessors( CollectionMappingStrategyGem cmStrategy ) { + public Map getPropertyWriteAccessors(CollectionMappingStrategyGem cmStrategy) { // collect all candidate target accessors List candidates = new ArrayList<>( getSetters() ); candidates.addAll( getAlternativeTargetAccessors() ); @@ -828,7 +847,7 @@ else if ( candidate.getAccessorType() == AccessorType.GETTER ) { continue; } } - else if ( candidate.getAccessorType() == AccessorType.FIELD && ( Executables.isFinal( candidate ) || + else if ( candidate.getAccessorType() == AccessorType.FIELD && ( Executables.isFinal( candidate ) || result.containsKey( targetPropertyName ) ) ) { // if the candidate is a field and a mapping already exists, then use that one, skip it. continue; @@ -865,7 +884,7 @@ private Type determineTargetType(Accessor candidate) { return parameter.getType(); } else if ( candidate.getAccessorType() == AccessorType.GETTER - || candidate.getAccessorType().isFieldAssignment() ) { + || candidate.getAccessorType().isFieldAssignment() ) { return typeFactory.getReturnType( (DeclaredType) typeMirror, candidate ); } return null; @@ -895,7 +914,7 @@ private List nullSafeTypeElementListConversion(Function * Matching occurs on: *

        *
      1. The generic type parameter type of the collection should match the adder method argument
      2. @@ -923,7 +942,6 @@ private String getPropertyName(ExecutableElement element) { * * @param collectionProperty property type (assumed collection) to find the adder method for * @param pluralPropertyName the property name (assumed plural) - * * @return corresponding adder method for getter when present */ private Accessor getAdderForType(Type collectionProperty, String pluralPropertyName) { @@ -962,9 +980,8 @@ else if ( collectionProperty.isStreamType() ) { * Returns all accessor candidates that start with "add" and have exactly one argument * whose type matches the collection or stream property's type argument. * - * @param property the collection or stream property + * @param property the collection or stream property * @param superclass the superclass to use for type argument lookup - * * @return accessor candidates */ private List getAccessorCandidates(Type property, Class superclass) { @@ -1023,7 +1040,7 @@ private List getAdders() { * Alternative accessors could be a getter for a collection. By means of the * {@link java.util.Collection#addAll(java.util.Collection) } this getter can still * be used as targetAccessor. JAXB XJC tool generates such constructs. - * + *

        * This method can be extended when new cases come along. * * @return an unmodifiable list of alternative target accessors. @@ -1109,7 +1126,6 @@ private boolean isSubType(TypeMirror candidate, Class clazz) { * the other type. Returns {@code 1}, if the other type is a direct super type of this type, and so on. * * @param assignableOther the other type - * * @return the length of the shortest path in the type hierarchy between this type and the specified other type */ public int distanceTo(Type assignableOther) { @@ -1138,7 +1154,7 @@ private int distanceTo(TypeMirror base, TypeMirror targetType) { } /** - * @param type the type declaring the method + * @param type the type declaring the method * @param method the method to check * @return Whether this type can access the given method declared on the given type. */ @@ -1159,7 +1175,7 @@ else if ( !method.getModifiers().contains( Modifier.PUBLIC ) ) { /** * @return A valid Java expression most suitable for representing null - useful for dealing with primitives from - * FTL. + * FTL. */ public String getNull() { if ( !isPrimitive() || isArrayType() ) { @@ -1173,7 +1189,7 @@ public String getNull() { } if ( "char".equals( getName() ) ) { //"'\u0000'" would have been better, but depends on platform encoding - return "0"; + return "0"; } if ( "double".equals( getName() ) ) { return "0.0d"; @@ -1200,6 +1216,9 @@ public String getSensibleDefault() { else if ( "String".equals( getName() ) ) { return "\"\""; } + else if ( isOptionalType() ) { + return "Optional.empty()"; + } else { if ( isNative() ) { // must be boxed, since primitive is already checked @@ -1216,8 +1235,8 @@ public int hashCode() { // are in another jar than the mapper. So the qualfiedName is a better candidate. final int prime = 31; int result = 1; - result = prime * result + ((name == null) ? 0 : name.hashCode()); - result = prime * result + ((packageName == null) ? 0 : packageName.hashCode()); + result = prime * result + ( ( name == null ) ? 0 : name.hashCode() ); + result = prime * result + ( ( packageName == null ) ? 0 : packageName.hashCode() ); return result; } @@ -1235,7 +1254,7 @@ public boolean equals(Object obj) { Type other = (Type) obj; if ( this.isWildCardBoundByTypeVar() && other.isWildCardBoundByTypeVar() ) { - return ( this.hasExtendsBound() == this.hasExtendsBound() + return ( this.hasExtendsBound() == this.hasExtendsBound() || this.hasSuperBound() == this.hasSuperBound() ) && typeUtils.isSameType( getTypeBound().getTypeMirror(), other.getTypeBound().getTypeMirror() ); } @@ -1276,7 +1295,6 @@ public String describe() { } /** - * * @return an identification that can be used as part in a forged method name. */ public String getIdentification() { @@ -1296,6 +1314,7 @@ public String getIdentification() { *

      3. {@code }, returns Object
      4. *
      5. {@code , returns Number}
      6. *
      + * * @return the bound for this parameter */ public Type getTypeBound() { @@ -1345,6 +1364,20 @@ public boolean hasAccessibleConstructor() { return hasAccessibleConstructor; } + public boolean hasAccessibleDefaultConstructor() { + if ( hasAccessibleDefaultConstructor == null ) { + hasAccessibleDefaultConstructor = false; + List constructors = ElementFilter.constructorsIn( typeElement.getEnclosedElements() ); + for ( ExecutableElement constructor : constructors ) { + if ( constructor.isDefault() && !constructor.getModifiers().contains( Modifier.PRIVATE ) ) { + hasAccessibleDefaultConstructor = true; + break; + } + } + } + return hasAccessibleDefaultConstructor; + } + /** * Returns the direct supertypes of a type. The interface types, if any, * will appear last in the list. @@ -1383,6 +1416,7 @@ public List determineTypeArguments(Class superclass) { /** * All primitive types and their corresponding boxed types are considered native. + * * @return true when native. */ public boolean isNative() { @@ -1412,12 +1446,11 @@ public boolean isLiteral() { * } * * - * @param declared the type + * @param declared the type * @param parameterized the parameterized type - * * @return - the same type when this is not a type var in the broadest sense (T, T[], or ? extends T)
      - * - the matching parameter in the parameterized type when this is a type var when found
      - * - null in all other cases + * - the matching parameter in the parameterized type when this is a type var when found
      + * - null in all other cases */ public ResolvedPair resolveParameterToType(Type declared, Type parameterized) { if ( isTypeVar() || isArrayTypeVar() || isWildCardBoundByTypeVar() ) { @@ -1476,7 +1509,7 @@ public ResolvedPair resolveParameterToType(Type declared, Type parameterized) { * result: Map * } * - * + *

      * Mismatch result examples: *

            * {@code
      @@ -1492,12 +1525,11 @@ public ResolvedPair resolveParameterToType(Type declared, Type parameterized) {
            * }
            * 
      * - * @param declared the type + * @param declared the type * @param parameterized the parameterized type - * * @return - the result of {@link #resolveParameterToType(Type, Type)} when this type itself is a type var.
      - * - the type but then with the matching type parameters replaced.
      - * - the same type when this type does not contain matching type parameters. + * - the type but then with the matching type parameters replaced.
      + * - the same type when this type does not contain matching type parameters. */ public Type resolveGenericTypeParameters(Type declared, Type parameterized) { if ( isTypeVar() || isArrayTypeVar() || isWildCardBoundByTypeVar() ) { @@ -1522,7 +1554,7 @@ public boolean isWildCardBoundByTypeVar() { } public boolean isArrayTypeVar() { - return isArrayType() && getComponentType().isTypeVar(); + return isArrayType() && getComponentType().isTypeVar(); } private static class TypeVarMatcher extends SimpleTypeVisitor8 { @@ -1533,7 +1565,7 @@ private static class TypeVarMatcher extends SimpleTypeVisitor8 results = new ArrayList<>( ); + List results = new ArrayList<>(); if ( parameterized.getTypeArguments().isEmpty() ) { return super.DEFAULT_VALUE; } @@ -1623,7 +1655,7 @@ else if ( types.isSameType( types.erasure( parameterized ), types.erasure( decla continue; } ResolvedPair result = visitDeclared( parameterized, declaredSuperType ); - if ( result != super.DEFAULT_VALUE ) { + if ( result != super.DEFAULT_VALUE ) { results.add( result ); } } @@ -1633,7 +1665,7 @@ else if ( types.isSameType( types.erasure( parameterized ), types.erasure( decla continue; } ResolvedPair result = visitDeclared( (DeclaredType) parameterizedSuper, declared ); - if ( result != super.DEFAULT_VALUE ) { + if ( result != super.DEFAULT_VALUE ) { results.add( result ); } } @@ -1649,7 +1681,7 @@ else if ( types.isSameType( types.erasure( parameterized ), types.erasure( decla private boolean isJavaLangObject(TypeMirror type) { if ( type instanceof DeclaredType ) { return ( (TypeElement) ( (DeclaredType) type ).asElement() ).getQualifiedName() - .contentEquals( Object.class.getName() ); + .contentEquals( Object.class.getName() ); } return false; } @@ -1722,7 +1754,7 @@ else if ( isPrimitive() ) { /** * It strips all the {@code []} from the {@code className}. - * + *

      * E.g. *

            *     trimSimpleClassName("String[][][]") -> "String"
      @@ -1730,7 +1762,6 @@ else if ( isPrimitive() ) {
            * 
      * * @param className that needs to be trimmed - * * @return the trimmed {@code className}, or {@code null} if the {@code className} was {@code null} */ private String trimSimpleClassName(String className) { @@ -1793,9 +1824,9 @@ public boolean isSealed() { /** * return the list of permitted TypeMirrors for the java 17+ sealed class */ - @SuppressWarnings( "unchecked" ) + @SuppressWarnings("unchecked") public List getPermittedSubclasses() { - if (SEALED_PERMITTED_SUBCLASSES_METHOD == null) { + if ( SEALED_PERMITTED_SUBCLASSES_METHOD == null ) { return emptyList(); } try { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java index f28e0c687e..97d6212c64 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java @@ -16,6 +16,7 @@ import java.util.Map; import java.util.NavigableMap; import java.util.NavigableSet; +import java.util.Optional; import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; @@ -92,6 +93,7 @@ public class TypeFactory { private final TypeMirror collectionType; private final TypeMirror mapType; private final TypeMirror streamType; + private final TypeMirror optionalType; private final Map implementationTypes = new HashMap<>(); private final Map toBeImportedTypes = new HashMap<>(); @@ -113,6 +115,7 @@ public TypeFactory(ElementUtils elementUtils, TypeUtils typeUtils, FormattingMes mapType = typeUtils.erasure( elementUtils.getTypeElement( Map.class.getCanonicalName() ).asType() ); TypeElement streamTypeElement = elementUtils.getTypeElement( JavaStreamConstants.STREAM_FQN ); streamType = streamTypeElement == null ? null : typeUtils.erasure( streamTypeElement.asType() ); + optionalType = typeUtils.erasure( elementUtils.getTypeElement( Optional.class.getCanonicalName() ).asType() ); implementationTypes.put( Iterable.class.getName(), withInitialCapacity( getType( ArrayList.class ) ) ); implementationTypes.put( Collection.class.getName(), withInitialCapacity( getType( ArrayList.class ) ) ); @@ -221,6 +224,7 @@ private Type getType(TypeMirror mirror, boolean isLiteral, Boolean alwaysImport) boolean isCollectionType = typeUtils.isSubtypeErased( mirror, collectionType ); boolean isMapType = typeUtils.isSubtypeErased( mirror, mapType ); boolean isStreamType = streamType != null && typeUtils.isSubtypeErased( mirror, streamType ); + boolean isOptionalType = typeUtils.isSubtypeErased( mirror, optionalType ); boolean isEnumType; boolean isInterface; @@ -330,6 +334,7 @@ else if ( mirror.getKind() == TypeKind.TYPEVAR ) { isCollectionType, isMapType, isStreamType, + isOptionalType, toBeImportedTypes, notToBeImportedTypes, toBeImported, @@ -556,6 +561,7 @@ private ImplementationType getImplementationType(TypeMirror mirror) { implementationType.isCollectionType(), implementationType.isMapType(), implementationType.isStreamType(), + implementationType.isOptionalType(), toBeImportedTypes, notToBeImportedTypes, null, diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.java new file mode 100644 index 0000000000..26430c2385 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.java @@ -0,0 +1,71 @@ +/* + * 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.presence; + +import java.util.Collections; +import java.util.Objects; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * Presence checker for {@link java.util.Optional} types. + * + * @author Ken Wang + */ +public class OptionalPresenceCheck extends ModelElement implements PresenceCheck { + + private final String sourceReference; + private final boolean negate; + + public OptionalPresenceCheck(String sourceReference) { + this.sourceReference = sourceReference; + this.negate = false; + } + + public OptionalPresenceCheck(String sourceReference, boolean negate) { + this.sourceReference = sourceReference; + this.negate = negate; + } + + public String getSourceReference() { + return sourceReference; + } + + @Override + public Set getImportTypes() { + return Collections.emptySet(); + } + + public boolean isNegate() { + return negate; + } + + @Override + public PresenceCheck negate() { + return new OptionalPresenceCheck( sourceReference, !negate ); + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + OptionalPresenceCheck that = (OptionalPresenceCheck) o; + return Objects.equals( sourceReference, that.sourceReference ); + } + + @Override + public int hashCode() { + return Objects.hash( sourceReference ); + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Strings.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Strings.java index 88e0bdf6f6..54a034c357 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Strings.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Strings.java @@ -148,6 +148,7 @@ public static String getSafeVariableName(String name, String... existingVariable * any Java keyword; starting with a lower-case letter */ public static String getSafeVariableName(String name, Collection existingVariableNames) { + name = name.equals( "?" ) ? "optionalValue" : name; name = decapitalize( sanitizeIdentifierName( name ) ); name = joinAndCamelize( extractParts( name ) ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/OptionalValueAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/OptionalValueAccessor.java new file mode 100644 index 0000000000..37ed2b88e5 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/OptionalValueAccessor.java @@ -0,0 +1,60 @@ +/* + * 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.accessor; + +import java.util.Collections; +import java.util.Set; +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; +import javax.lang.model.type.TypeMirror; + +/** + * An {@link Accessor} for retrieving the value inside an {@link java.util.Optional}. + * + * @author Ken Wang + */ +public class OptionalValueAccessor implements ReadAccessor { + + private final TypeMirror valueTypeMirror; + private final String simpleName; + private final Element element; + + public OptionalValueAccessor(Element element, TypeMirror valueTypeMirror, String simpleName) { + this.element = element; + this.valueTypeMirror = valueTypeMirror; + this.simpleName = simpleName; + } + + @Override + public TypeMirror getAccessedType() { + return valueTypeMirror; + } + + @Override + public String getSimpleName() { + return this.simpleName; + } + + @Override + public Set getModifiers() { + return Collections.emptySet(); + } + + @Override + public Element getElement() { + return this.element; + } + + @Override + public AccessorType getAccessorType() { + return AccessorType.GETTER; + } + + @Override + public String getReadValueSource() { + return "orElse( null )"; + } +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl index 8fb5bfdef0..1481052e77 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl @@ -117,11 +117,12 @@ <#else> - <#if mapNullToDefault>if ( <@includeModel object=getPresenceCheckByParameter(sourceParameters[0]) /> ) { + <#assign propertyMappings = propertyMappingsByParameter(sourceParameters[0])/> + <#if mapNullToDefault && (propertyMappings?size > 0)>if ( <@includeModel object=getPresenceCheckByParameter(sourceParameters[0]) /> ) { <#list propertyMappingsByParameter(sourceParameters[0]) as propertyMapping> <@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/> - <#if mapNullToDefault>} + <#if mapNullToDefault && (propertyMappings?size > 0)>} <#list constantMappings as constantMapping> <@includeModel object=constantMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping/> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/InitDefaultValue.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/InitDefaultValue.ftl new file mode 100644 index 0000000000..1adc810517 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/InitDefaultValue.ftl @@ -0,0 +1,32 @@ +<#-- + + 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.InitDefaultValue" --> +<#if factoryMethod??> + <@includeModel object=factoryMethod targetType=targetType/> +<#else> + <@constructTargetObject/> + +<#-- + macro: constructTargetObject + + purpose: Either call the constructor of the target object directly or of the implementing type. + If the target type is an Object but does not have a public default constructor, null is returned. +--> +<#macro constructTargetObject><@compress single_line=true> + <#if targetType.implementationType?? && targetType.implementationType.hasAccessibleDefaultConstructor()> + new <@includeModel object=targetType.implementationType/>() + <#elseif targetType.arrayType> + new <@includeModel object=targetType.componentType/>[0] + <#elseif targetType.sensibleDefault??> + ${targetType.sensibleDefault} + <#elseif targetType.hasAccessibleDefaultConstructor()> + new <@includeModel object=targetType/>() + <#else> + null + + \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl index 8eabe23a16..7a54a6d029 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl @@ -21,7 +21,11 @@ <#if !entry.presenceChecker?? > <#if !entry.type.primitive> if ( ${entry.name} == null ) { - return ${returnType.null}; + <#if returnType.isOptionalType()> + return Optional.empty(); + <#else> + return ${returnType.null}; + } diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/OptionalMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/OptionalMappingMethod.ftl new file mode 100644 index 0000000000..401f40fee5 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/OptionalMappingMethod.ftl @@ -0,0 +1,48 @@ +<#-- + + 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.OptionalMappingMethod" --> +<#if overridden>@Override +<#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, )<@throws/> { + <#list beforeMappingReferencesWithoutMappingTarget as callback> + <@includeModel object=callback targetBeanName=resultName targetType=resultType/> + <#if !callback_has_next> + + + + if ( <@includeModel object=sourceParameterPresenceCheck.negate() /> ) { + <#if resultType.optionalType || mapNullToDefault> + return <@includeModel object=initDefaultValueForResultType/>; + <#else> + return<#if returnType.name != "void"> null; + + } + + <#if sourceParameter.type.optionalType> + <@includeModel object=returnType/> ${resultName} = ${sourceParameter.name}.map( ${loopVariableName} -> <@includeModel object=elementAssignment/> )<#if !returnType.optionalType>.orElse( <#if mapNullToDefault><@includeModel object=initDefaultValueForResultType/><#else>null ); + <#else> + <@includeModel object=sourceElementType/> ${loopVariableName} = ${sourceParameter.name}; + <@includeModel object=returnType/> ${resultName} = Optional.ofNullable( <@includeModel object=elementAssignment/> ); + + + <#list afterMappingReferences as callback> + <#if callback_index = 0> + + + <@includeModel object=callback targetBeanName=resultName targetType=resultType/> + + + return ${resultName}; +} +<#macro throws> + <#if (thrownTypes?size > 0)><#lt> throws <@compress single_line=true> + <#list thrownTypes as exceptionType> + <@includeModel object=exceptionType/> + <#if exceptionType_has_next>, <#t> + + + \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.ftl new file mode 100644 index 0000000000..36a763ca5f --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.ftl @@ -0,0 +1,9 @@ +<#-- + + 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.presence.OptionalPresenceCheck" --> +${sourceReference}<#if isNegate()>.isEmpty()<#else>.isPresent() \ No newline at end of file diff --git a/processor/src/test/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactoryTest.java b/processor/src/test/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactoryTest.java index 646f898063..2194c04d99 100755 --- a/processor/src/test/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactoryTest.java +++ b/processor/src/test/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactoryTest.java @@ -174,6 +174,7 @@ private Type typeWithFQN(String fullQualifiedName) { false, false, false, + false, new HashMap<>( ), new HashMap<>( ), false, diff --git a/processor/src/test/java/org/mapstruct/ap/internal/model/common/DefaultConversionContextTest.java b/processor/src/test/java/org/mapstruct/ap/internal/model/common/DefaultConversionContextTest.java index ce2f895e0f..91b1c87f5a 100755 --- a/processor/src/test/java/org/mapstruct/ap/internal/model/common/DefaultConversionContextTest.java +++ b/processor/src/test/java/org/mapstruct/ap/internal/model/common/DefaultConversionContextTest.java @@ -122,6 +122,7 @@ private Type typeWithFQN(String fullQualifiedName) { false, false, false, + false, new HashMap<>( ), new HashMap<>( ), false, diff --git a/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/beforeafter/OptionalBeforeAfterMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/beforeafter/OptionalBeforeAfterMapper.java new file mode 100644 index 0000000000..f8c9a15af1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/beforeafter/OptionalBeforeAfterMapper.java @@ -0,0 +1,76 @@ +/* + * 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.optionalmapping.beforeafter; + +import java.util.Optional; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.TargetType; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface OptionalBeforeAfterMapper { + + OptionalBeforeAfterMapper INSTANCE = Mappers.getMapper( OptionalBeforeAfterMapper.class ); + + Target toTarget(Source source); + + @BeforeMapping + default void beforeDeepOptionalSourceWithNoTargetType(Optional source) { + } + + @BeforeMapping + default void beforeDeepOptionalSourceWithNonOptionalTargetType(@TargetType Class targetType, + Optional source) { + } + + @AfterMapping + default void afterDeepOptionalSourceWithNoTarget(Optional source) { + + } + + @AfterMapping + default void afterDeepOptionalSourceWithNonOptionalTarget(@MappingTarget Target.SubType target, + Optional source) { + } + + @AfterMapping + default void afterDeepOptionalSourceWithOptionalTarget(@MappingTarget Optional target, + Optional source) { + } + + @AfterMapping + default void afterDeepNonOptionalSourceOptionalTarget(@MappingTarget Optional target, + Source.SubType source) { + } + + @BeforeMapping + default void beforeShallowOptionalSourceWithNoTargetType(Optional source) { + } + + @BeforeMapping + default void beforeShallowOptionalSourceWithNonOptionalTargetType(@TargetType Class targetType, + Optional source) { + } + + @AfterMapping + default void afterShallowOptionalSourceWithNoTarget(Optional source) { + + } + + @AfterMapping + default void afterShallowOptionalSourceWithNonOptionalTarget(@MappingTarget String target, + Optional source) { + } + + @AfterMapping + default void afterShallowNonOptionalSourceOptionalTarget(@MappingTarget Optional target, String source) { + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/beforeafter/OptionalBeforeAfterTest.java b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/beforeafter/OptionalBeforeAfterTest.java new file mode 100644 index 0000000000..6a6b043313 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/beforeafter/OptionalBeforeAfterTest.java @@ -0,0 +1,26 @@ +/* + * 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.optionalmapping.beforeafter; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +@WithClasses({ + OptionalBeforeAfterMapper.class, Source.class, Target.class +}) +public class OptionalBeforeAfterTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource() + .addComparisonToFixtureFor( OptionalBeforeAfterMapper.class ); + + @ProcessorTest + public void dummyTest() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/beforeafter/Source.java b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/beforeafter/Source.java new file mode 100644 index 0000000000..4d87be0e04 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/beforeafter/Source.java @@ -0,0 +1,87 @@ +/* + * 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.optionalmapping.beforeafter; + +import java.util.Objects; +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class Source { + + private final Optional deepOptionalToOptional; + private final Optional deepOptionalToNonOptional; + private final SubType deepNonOptionalToOptional; + + private final Optional shallowOptionalToOptional; + private final Optional shallowOptionalToNonOptional; + private final String shallowNonOptionalToOptional; + + public Source(Optional deepOptionalToOptional, Optional deepOptionalToNonOptional, + SubType deepNonOptionalToOptional, Optional shallowOptionalToOptional, + Optional shallowOptionalToNonOptional, String shallowNonOptionalToOptional) { + this.deepOptionalToOptional = deepOptionalToOptional; + this.deepOptionalToNonOptional = deepOptionalToNonOptional; + this.deepNonOptionalToOptional = deepNonOptionalToOptional; + this.shallowOptionalToOptional = shallowOptionalToOptional; + this.shallowOptionalToNonOptional = shallowOptionalToNonOptional; + this.shallowNonOptionalToOptional = shallowNonOptionalToOptional; + } + + public Optional getDeepOptionalToOptional() { + return deepOptionalToOptional; + } + + public Optional getDeepOptionalToNonOptional() { + return deepOptionalToNonOptional; + } + + public SubType getDeepNonOptionalToOptional() { + return deepNonOptionalToOptional; + } + + public Optional getShallowOptionalToOptional() { + return shallowOptionalToOptional; + } + + public Optional getShallowOptionalToNonOptional() { + return shallowOptionalToNonOptional; + } + + public String getShallowNonOptionalToOptional() { + return shallowNonOptionalToOptional; + } + + public static class SubType { + + private final String value; + + public SubType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + SubType subType = (SubType) o; + return Objects.equals( value, subType.value ); + } + + @Override + public int hashCode() { + return Objects.hash( value ); + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/beforeafter/Target.java b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/beforeafter/Target.java new file mode 100644 index 0000000000..a93b4b31e7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/beforeafter/Target.java @@ -0,0 +1,86 @@ +/* + * 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.optionalmapping.beforeafter; + +import java.util.Objects; +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class Target { + + private final Optional deepOptionalToOptional; + private final SubType deepOptionalToNonOptional; + private final Optional deepNonOptionalToOptional; + + private final Optional shallowOptionalToOptional; + private final String shallowOptionalToNonOptional; + private final Optional shallowNonOptionalToOptional; + + public Target(Optional deepOptionalToOptional, SubType deepOptionalToNonOptional, + Optional deepNonOptionalToOptional, Optional shallowOptionalToOptional, + String shallowOptionalToNonOptional, Optional shallowNonOptionalToOptional) { + this.deepOptionalToOptional = deepOptionalToOptional; + this.deepOptionalToNonOptional = deepOptionalToNonOptional; + this.deepNonOptionalToOptional = deepNonOptionalToOptional; + this.shallowOptionalToOptional = shallowOptionalToOptional; + this.shallowOptionalToNonOptional = shallowOptionalToNonOptional; + this.shallowNonOptionalToOptional = shallowNonOptionalToOptional; + } + + public Optional getDeepOptionalToOptional() { + return deepOptionalToOptional; + } + + public SubType getDeepOptionalToNonOptional() { + return deepOptionalToNonOptional; + } + + public Optional getDeepNonOptionalToOptional() { + return deepNonOptionalToOptional; + } + + public Optional getShallowOptionalToOptional() { + return shallowOptionalToOptional; + } + + public String getShallowOptionalToNonOptional() { + return shallowOptionalToNonOptional; + } + + public Optional getShallowNonOptionalToOptional() { + return shallowNonOptionalToOptional; + } + + public static class SubType { + + private final String value; + + public SubType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + SubType subType = (SubType) o; + return Objects.equals( value, subType.value ); + } + + @Override + public int hashCode() { + return Objects.hash( value ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/differenttypes/OptionalDifferentTypesMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/differenttypes/OptionalDifferentTypesMapper.java new file mode 100644 index 0000000000..88f4d2e966 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/differenttypes/OptionalDifferentTypesMapper.java @@ -0,0 +1,19 @@ +/* + * 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.optionalmapping.differenttypes; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface OptionalDifferentTypesMapper { + + OptionalDifferentTypesMapper INSTANCE = Mappers.getMapper( + OptionalDifferentTypesMapper.class ); + + Target toTarget(Source source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/differenttypes/OptionalDifferentTypesTest.java b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/differenttypes/OptionalDifferentTypesTest.java new file mode 100644 index 0000000000..aae100ce72 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/differenttypes/OptionalDifferentTypesTest.java @@ -0,0 +1,234 @@ +/* + * 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.optionalmapping.differenttypes; + +import java.util.Optional; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + OptionalDifferentTypesMapper.class, Source.class, Target.class +}) +public class OptionalDifferentTypesTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource() + .addComparisonToFixtureFor( OptionalDifferentTypesMapper.class ); + + @ProcessorTest + public void constructorOptionalToOptionalWhenPresent() { + Source source = new Source( Optional.of( new Source.SubType( "some value" ) ), null, null ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorOptionalToOptional() ).contains( new Target.SubType( "some value" ) ); + } + + @ProcessorTest + public void constructorOptionalToOptionalWhenEmpty() { + Source source = new Source( Optional.empty(), null, null ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + public void constructorOptionalToOptionalWhenNull() { + Source source = new Source( null, null, null ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + public void constructorOptionalToNonOptionalWhenPresent() { + Source source = new Source( null, Optional.of( new Source.SubType( "some value" ) ), null ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorOptionalToNonOptional() ).isEqualTo( new Target.SubType( "some value" ) ); + } + + @ProcessorTest + public void constructorOptionalToNonOptionalWhenEmpty() { + Source source = new Source( null, Optional.empty(), null ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorOptionalToNonOptional() ).isNull(); + } + + @ProcessorTest + public void constructorOptionalToNonOptionalWhenNull() { + Source source = new Source( null, null, null ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorOptionalToNonOptional() ).isNull(); + } + + @ProcessorTest + public void constructorNonOptionalToOptionalWhenNotNull() { + Source source = new Source( null, null, new Source.SubType( "some value" ) ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorNonOptionalToOptional() ).contains( new Target.SubType( "some value" ) ); + } + + @ProcessorTest + public void constructorNonOptionalToOptionalWhenNull() { + Source source = new Source( null, null, null ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorNonOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + public void optionalToOptionalWhenPresent() { + Source source = new Source( null, null, null ); + source.setOptionalToOptional( Optional.of( new Source.SubType( "some value" ) ) ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToOptional() ).contains( new Target.SubType( "some value" ) ); + } + + @ProcessorTest + public void optionalToOptionalWhenEmpty() { + Source source = new Source( null, null, null ); + source.setOptionalToOptional( Optional.empty() ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + public void optionalToOptionalWhenNull() { + Source source = new Source( null, null, null ); + source.setOptionalToOptional( null ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + public void optionalToNonOptionalWhenPresent() { + Source source = new Source( null, null, null ); + source.setOptionalToNonOptional( Optional.of( new Source.SubType( "some value" ) ) ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToNonOptional() ).isEqualTo( new Target.SubType( "some value" ) ); + } + + @ProcessorTest + public void optionalToNonOptionalWhenEmpty() { + Source source = new Source( null, null, null ); + source.setOptionalToNonOptional( Optional.empty() ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToNonOptional() ).isNull(); + } + + @ProcessorTest + public void optionalToNonOptionalWhenNull() { + Source source = new Source( null, null, null ); + source.setOptionalToNonOptional( null ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToNonOptional() ).isNull(); + } + + @ProcessorTest + public void nonOptionalToOptionalWhenNotNull() { + Source source = new Source( null, null, null ); + source.setNonOptionalToOptional( new Source.SubType( "some value" ) ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getNonOptionalToOptional() ).contains( new Target.SubType( "some value" ) ); + } + + @ProcessorTest + public void nonOptionalToOptionalWhenNull() { + Source source = new Source( null, null, null ); + source.setNonOptionalToOptional( null ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getNonOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + public void publicOptionalToOptionalWhenPresent() { + Source source = new Source( null, null, null ); + source.publicOptionalToOptional = Optional.of( new Source.SubType( "some value" ) ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.publicOptionalToOptional ).contains( new Target.SubType( "some value" ) ); + } + + @ProcessorTest + public void publicOptionalToOptionalWhenEmpty() { + Source source = new Source( null, null, null ); + source.publicOptionalToOptional = Optional.empty(); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.publicOptionalToOptional ).isEmpty(); + } + + @ProcessorTest + public void publicOptionalToOptionalWhenNull() { + Source source = new Source( null, null, null ); + source.publicOptionalToOptional = null; + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.publicOptionalToOptional ).isEmpty(); + } + + @ProcessorTest + public void publicOptionalToNonOptionalWhenPresent() { + Source source = new Source( null, null, null ); + source.publicOptionalToNonOptional = Optional.of( new Source.SubType( "some value" ) ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.publicOptionalToNonOptional ).isEqualTo( new Target.SubType( "some value" ) ); + } + + @ProcessorTest + public void publicOptionalToNonOptionalWhenEmpty() { + Source source = new Source( null, null, null ); + source.publicOptionalToNonOptional = Optional.empty(); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.publicOptionalToNonOptional ).isNull(); + } + + @ProcessorTest + public void publicOptionalToNonOptionalWhenNull() { + Source source = new Source( null, null, null ); + source.publicOptionalToNonOptional = null; + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.publicOptionalToNonOptional ).isNull(); + } + + @ProcessorTest + public void publicNonOptionalToOptionalWhenNotNull() { + Source source = new Source( null, null, null ); + source.publicNonOptionalToOptional = new Source.SubType( "some value" ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.publicNonOptionalToOptional ).contains( new Target.SubType( "some value" ) ); + } + + @ProcessorTest + public void publicNonOptionalToOptionalWhenNull() { + Source source = new Source( null, null, null ); + source.publicNonOptionalToOptional = null; + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.publicNonOptionalToOptional ).isEmpty(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/differenttypes/Source.java b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/differenttypes/Source.java new file mode 100644 index 0000000000..0c9c92811d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/differenttypes/Source.java @@ -0,0 +1,102 @@ +/* + * 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.optionalmapping.differenttypes; + +import java.util.Objects; +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class Source { + + private final Optional constructorOptionalToOptional; + private final Optional constructorOptionalToNonOptional; + private final SubType constructorNonOptionalToOptional; + + private Optional optionalToOptional; + private Optional optionalToNonOptional; + private SubType nonOptionalToOptional; + + @SuppressWarnings( "VisibilityModifier" ) + public Optional publicOptionalToOptional; + @SuppressWarnings( "VisibilityModifier" ) + public Optional publicOptionalToNonOptional; + @SuppressWarnings( "VisibilityModifier" ) + public SubType publicNonOptionalToOptional; + + public Source(Optional constructorOptionalToOptional, Optional constructorOptionalToNonOptional, + SubType constructorNonOptionalToOptional) { + this.constructorOptionalToOptional = constructorOptionalToOptional; + this.constructorOptionalToNonOptional = constructorOptionalToNonOptional; + this.constructorNonOptionalToOptional = constructorNonOptionalToOptional; + } + + public Optional getConstructorOptionalToOptional() { + return constructorOptionalToOptional; + } + + public Optional getConstructorOptionalToNonOptional() { + return constructorOptionalToNonOptional; + } + + public SubType getConstructorNonOptionalToOptional() { + return constructorNonOptionalToOptional; + } + + public Optional getOptionalToOptional() { + return optionalToOptional; + } + + public void setOptionalToOptional(Optional optionalToOptional) { + this.optionalToOptional = optionalToOptional; + } + + public Optional getOptionalToNonOptional() { + return optionalToNonOptional; + } + + public void setOptionalToNonOptional(Optional optionalToNonOptional) { + this.optionalToNonOptional = optionalToNonOptional; + } + + public SubType getNonOptionalToOptional() { + return nonOptionalToOptional; + } + + public void setNonOptionalToOptional(SubType nonOptionalToOptional) { + this.nonOptionalToOptional = nonOptionalToOptional; + } + + public static class SubType { + + private final String value; + + public SubType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + SubType subType = (SubType) o; + return Objects.equals( value, subType.value ); + } + + @Override + public int hashCode() { + return Objects.hash( value ); + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/differenttypes/Target.java b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/differenttypes/Target.java new file mode 100644 index 0000000000..7784e8ee52 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/differenttypes/Target.java @@ -0,0 +1,101 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optionalmapping.differenttypes; + +import java.util.Objects; +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class Target { + + private final Optional constructorOptionalToOptional; + private final SubType constructorOptionalToNonOptional; + private final Optional constructorNonOptionalToOptional; + + private Optional optionalToOptional; + private SubType optionalToNonOptional; + private Optional nonOptionalToOptional; + + @SuppressWarnings( "VisibilityModifier" ) + public Optional publicOptionalToOptional; + @SuppressWarnings( "VisibilityModifier" ) + public SubType publicOptionalToNonOptional; + @SuppressWarnings( "VisibilityModifier" ) + public Optional publicNonOptionalToOptional; + + public Target(Optional constructorOptionalToOptional, SubType constructorOptionalToNonOptional, + Optional constructorNonOptionalToOptional) { + this.constructorOptionalToOptional = constructorOptionalToOptional; + this.constructorOptionalToNonOptional = constructorOptionalToNonOptional; + this.constructorNonOptionalToOptional = constructorNonOptionalToOptional; + } + + public Optional getConstructorOptionalToOptional() { + return constructorOptionalToOptional; + } + + public SubType getConstructorOptionalToNonOptional() { + return constructorOptionalToNonOptional; + } + + public Optional getConstructorNonOptionalToOptional() { + return constructorNonOptionalToOptional; + } + + public Optional getOptionalToOptional() { + return optionalToOptional; + } + + public void setOptionalToOptional(Optional optionalToOptional) { + this.optionalToOptional = optionalToOptional; + } + + public SubType getOptionalToNonOptional() { + return optionalToNonOptional; + } + + public void setOptionalToNonOptional(SubType optionalToNonOptional) { + this.optionalToNonOptional = optionalToNonOptional; + } + + public Optional getNonOptionalToOptional() { + return nonOptionalToOptional; + } + + public void setNonOptionalToOptional(Optional nonOptionalToOptional) { + this.nonOptionalToOptional = nonOptionalToOptional; + } + + public static class SubType { + + private final String value; + + public SubType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Target.SubType subType = (Target.SubType) o; + return Objects.equals( value, subType.value ); + } + + @Override + public int hashCode() { + return Objects.hash( value ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nested/OptionalNestedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nested/OptionalNestedMapper.java new file mode 100644 index 0000000000..59ae1b1ee2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nested/OptionalNestedMapper.java @@ -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 + */ +package org.mapstruct.ap.test.optionalmapping.nested; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface OptionalNestedMapper { + + OptionalNestedMapper INSTANCE = Mappers.getMapper( OptionalNestedMapper.class ); + + @Mapping(source = "optionalToNonOptional?.value?", target = "optionalToNonOptional") + @Mapping(source = "optionalToOptional?.value", target = "optionalToOptional") + @Mapping(source = "nonOptionalToNonOptional?.value", target = "nonOptionalToNonOptional") + @Mapping(source = "nonOptionalToOptional?.value", target = "nonOptionalToOptional") + Target toTarget(Source source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nested/OptionalNestedTest.java b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nested/OptionalNestedTest.java new file mode 100644 index 0000000000..f9c0edc0cc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nested/OptionalNestedTest.java @@ -0,0 +1,188 @@ +/* + * 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.optionalmapping.nested; + +import java.util.Optional; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + OptionalNestedMapper.class, Source.class, Target.class +}) +public class OptionalNestedTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource() + .addComparisonToFixtureFor( OptionalNestedMapper.class ); + + @ProcessorTest + public void optionalToNonOptionalWhenEmpty() { + Source source = new Source(); + source.setOptionalToNonOptional( Optional.empty() ); + + Target target = OptionalNestedMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToNonOptional() ).isNull(); + } + + @ProcessorTest + public void optionalToNonOptionalWhenNull() { + Source source = new Source(); + source.setOptionalToNonOptional( null ); + + Target target = OptionalNestedMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToNonOptional() ).isNull(); + } + + @ProcessorTest + public void nestedOptionalToNonOptionalWhenEmpty() { + Source source = new Source(); + source.setOptionalToNonOptional( Optional.of( new Source.NestedOptional( Optional.empty() ) ) ); + + Target target = OptionalNestedMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToNonOptional() ).isNull(); + } + + @ProcessorTest + public void nestedOptionalToNonOptionalWhenNull() { + Source source = new Source(); + source.setOptionalToNonOptional( Optional.of( new Source.NestedOptional( null ) ) ); + + Target target = OptionalNestedMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToNonOptional() ).isNull(); + } + + @ProcessorTest + public void nestedOptionalToNonOptionalWhenNotNull() { + Source source = new Source(); + source.setOptionalToNonOptional( Optional.of( new Source.NestedOptional( Optional.of( "some value" ) ) ) ); + + Target target = OptionalNestedMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToNonOptional() ).isEqualTo( "some value" ); + } + + @ProcessorTest + public void optionalToOptionalWhenEmpty() { + Source source = new Source(); + source.setOptionalToOptional( Optional.empty() ); + + Target target = OptionalNestedMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + public void optionalToOptionalWhenNull() { + Source source = new Source(); + source.setOptionalToOptional( null ); + + Target target = OptionalNestedMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + public void nestedOptionalToOptionalWhenEmpty() { + Source source = new Source(); + source.setOptionalToOptional( Optional.of( new Source.NestedOptional( Optional.empty() ) ) ); + + Target target = OptionalNestedMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + public void nestedOptionalToOptionalWhenNull() { + Source source = new Source(); + source.setOptionalToOptional( Optional.of( new Source.NestedOptional( null ) ) ); + + Target target = OptionalNestedMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToOptional() ).isNull(); + } + + @ProcessorTest + public void nestedOptionalToOptionalWhenNotNull() { + Source source = new Source(); + source.setOptionalToOptional( Optional.of( new Source.NestedOptional( Optional.of( "some value" ) ) ) ); + + Target target = OptionalNestedMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToOptional() ).contains( "some value" ); + } + + @ProcessorTest + public void nonOptionalToNonOptionalWhenEmpty() { + Source source = new Source(); + source.setNonOptionalToNonOptional( Optional.empty() ); + + Target target = OptionalNestedMapper.INSTANCE.toTarget( source ); + assertThat( target.getNonOptionalToNonOptional() ).isNull(); + } + + @ProcessorTest + public void nonOptionalToNonOptionalWhenNull() { + Source source = new Source(); + source.setNonOptionalToNonOptional( null ); + + Target target = OptionalNestedMapper.INSTANCE.toTarget( source ); + assertThat( target.getNonOptionalToNonOptional() ).isNull(); + } + + @ProcessorTest + public void nestedNonOptionalToNonOptionalWhenNull() { + Source source = new Source(); + source.setNonOptionalToNonOptional( Optional.of( new Source.NestedNonOptional( null ) ) ); + + Target target = OptionalNestedMapper.INSTANCE.toTarget( source ); + assertThat( target.getNonOptionalToNonOptional() ).isNull(); + } + + @ProcessorTest + public void nestedNonOptionalToNonOptionalWhenNotNull() { + Source source = new Source(); + source.setNonOptionalToNonOptional( Optional.of( new Source.NestedNonOptional( "some value" ) ) ); + + Target target = OptionalNestedMapper.INSTANCE.toTarget( source ); + assertThat( target.getNonOptionalToNonOptional() ).isEqualTo( "some value" ); + } + + @ProcessorTest + public void nonOptionalToOptionalWhenEmpty() { + Source source = new Source(); + source.setNonOptionalToOptional( Optional.empty() ); + + Target target = OptionalNestedMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + public void nonOptionalToOptionalWhenNull() { + Source source = new Source(); + source.setNonOptionalToOptional( null ); + + Target target = OptionalNestedMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + public void nestedNonOptionalToOptionalWhenNull() { + Source source = new Source(); + source.setNonOptionalToOptional( Optional.of( new Source.NestedNonOptional( null ) ) ); + + Target target = OptionalNestedMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + public void nestedNonOptionalToOptionalWhenNotNull() { + Source source = new Source(); + source.setNonOptionalToOptional( Optional.of( new Source.NestedNonOptional( "some value" ) ) ); + + Target target = OptionalNestedMapper.INSTANCE.toTarget( source ); + assertThat( target.getNonOptionalToOptional() ).contains( "some value" ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nested/Source.java b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nested/Source.java new file mode 100644 index 0000000000..efafb1fb6b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nested/Source.java @@ -0,0 +1,76 @@ +/* + * 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.optionalmapping.nested; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class Source { + + private Optional optionalToNonOptional; + private Optional optionalToOptional; + private Optional nonOptionalToNonOptional; + private Optional nonOptionalToOptional; + + public Optional getOptionalToNonOptional() { + return optionalToNonOptional; + } + + public void setOptionalToNonOptional(Optional optionalToNonOptional) { + this.optionalToNonOptional = optionalToNonOptional; + } + + public Optional getOptionalToOptional() { + return optionalToOptional; + } + + public void setOptionalToOptional(Optional optionalToOptional) { + this.optionalToOptional = optionalToOptional; + } + + public Optional getNonOptionalToNonOptional() { + return nonOptionalToNonOptional; + } + + public void setNonOptionalToNonOptional(Optional nonOptionalToNonOptional) { + this.nonOptionalToNonOptional = nonOptionalToNonOptional; + } + + public Optional getNonOptionalToOptional() { + return nonOptionalToOptional; + } + + public void setNonOptionalToOptional(Optional nonOptionalToOptional) { + this.nonOptionalToOptional = nonOptionalToOptional; + } + + public static class NestedOptional { + + private final Optional value; + + public NestedOptional(Optional value) { + this.value = value; + } + + public Optional getValue() { + return value; + } + } + + public static class NestedNonOptional { + + private final String value; + + public NestedNonOptional(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nested/Target.java b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nested/Target.java new file mode 100644 index 0000000000..6f60833dc3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nested/Target.java @@ -0,0 +1,48 @@ +/* + * 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.optionalmapping.nested; + +import java.util.Optional; + +public class Target { + + private String optionalToNonOptional; + private Optional optionalToOptional; + private String nonOptionalToNonOptional; + private Optional nonOptionalToOptional; + + public String getOptionalToNonOptional() { + return optionalToNonOptional; + } + + public void setOptionalToNonOptional(String optionalToNonOptional) { + this.optionalToNonOptional = optionalToNonOptional; + } + + public Optional getOptionalToOptional() { + return optionalToOptional; + } + + public void setOptionalToOptional(Optional optionalToOptional) { + this.optionalToOptional = optionalToOptional; + } + + public String getNonOptionalToNonOptional() { + return nonOptionalToNonOptional; + } + + public void setNonOptionalToNonOptional(String nonOptionalToNonOptional) { + this.nonOptionalToNonOptional = nonOptionalToNonOptional; + } + + public Optional getNonOptionalToOptional() { + return nonOptionalToOptional; + } + + public void setNonOptionalToOptional(Optional nonOptionalToOptional) { + this.nonOptionalToOptional = nonOptionalToOptional; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullcheckalways/OptionalNullCheckAlwaysMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullcheckalways/OptionalNullCheckAlwaysMapper.java new file mode 100644 index 0000000000..9f170da90d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullcheckalways/OptionalNullCheckAlwaysMapper.java @@ -0,0 +1,19 @@ +/* + * 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.optionalmapping.nullcheckalways; + +import org.mapstruct.Mapper; +import org.mapstruct.NullValueCheckStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper(nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS) +public interface OptionalNullCheckAlwaysMapper { + + OptionalNullCheckAlwaysMapper INSTANCE = Mappers.getMapper( OptionalNullCheckAlwaysMapper.class ); + + Target toTarget(Source source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullcheckalways/OptionalNullCheckAlwaysTest.java b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullcheckalways/OptionalNullCheckAlwaysTest.java new file mode 100644 index 0000000000..ce404409f0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullcheckalways/OptionalNullCheckAlwaysTest.java @@ -0,0 +1,51 @@ +/* + * 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.optionalmapping.nullcheckalways; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + OptionalNullCheckAlwaysMapper.class, Source.class, Target.class +}) +public class OptionalNullCheckAlwaysTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource() + .addComparisonToFixtureFor( OptionalNullCheckAlwaysMapper.class ); + + @ProcessorTest + public void optionalToOptionalWhenNull() { + Source source = new Source(); + source.setOptionalToOptional( null ); + + Target target = OptionalNullCheckAlwaysMapper.INSTANCE.toTarget( source ); + assertThat( target.isOptionalToOptionalCalled() ).isFalse(); + } + + @ProcessorTest + public void optionalToNonOptionalWhenNull() { + Source source = new Source(); + source.setOptionalToNonOptional( null ); + + Target target = OptionalNullCheckAlwaysMapper.INSTANCE.toTarget( source ); + assertThat( target.isOptionalToNonOptionalCalled() ).isFalse(); + } + + @ProcessorTest + public void nonOptionalToOptionalWhenNull() { + Source source = new Source(); + source.setNonOptionalToOptional( null ); + + Target target = OptionalNullCheckAlwaysMapper.INSTANCE.toTarget( source ); + assertThat( target.isNonOptionalToOptionalCalled() ).isFalse(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullcheckalways/Source.java b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullcheckalways/Source.java new file mode 100644 index 0000000000..d6c06cce3a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullcheckalways/Source.java @@ -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.optionalmapping.nullcheckalways; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class Source { + + private Optional optionalToOptional; + private Optional optionalToNonOptional; + private String nonOptionalToOptional; + + public Optional getOptionalToOptional() { + return optionalToOptional; + } + + public void setOptionalToOptional(Optional optionalToOptional) { + this.optionalToOptional = optionalToOptional; + } + + public Optional getOptionalToNonOptional() { + return optionalToNonOptional; + } + + public void setOptionalToNonOptional(Optional optionalToNonOptional) { + this.optionalToNonOptional = optionalToNonOptional; + } + + public String getNonOptionalToOptional() { + return nonOptionalToOptional; + } + + public void setNonOptionalToOptional(String nonOptionalToOptional) { + this.nonOptionalToOptional = nonOptionalToOptional; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullcheckalways/Target.java b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullcheckalways/Target.java new file mode 100644 index 0000000000..451e407dde --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullcheckalways/Target.java @@ -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.optionalmapping.nullcheckalways; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class Target { + + private boolean optionalToOptionalCalled; + private boolean optionalToNonOptionalCalled; + private boolean nonOptionalToOptionalCalled; + + public void setOptionalToOptional(Optional optionalToOptional) { + this.optionalToOptionalCalled = true; + } + + public void setOptionalToNonOptional(String optionalToNonOptional) { + this.optionalToNonOptionalCalled = true; + } + + public void setNonOptionalToOptional(Optional nonOptionalToOptional) { + this.nonOptionalToOptionalCalled = true; + } + + public boolean isOptionalToOptionalCalled() { + return optionalToOptionalCalled; + } + + public boolean isOptionalToNonOptionalCalled() { + return optionalToNonOptionalCalled; + } + + public boolean isNonOptionalToOptionalCalled() { + return nonOptionalToOptionalCalled; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullvaluepropertytodefault/NullValuePropertyToDefaultMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullvaluepropertytodefault/NullValuePropertyToDefaultMapper.java new file mode 100644 index 0000000000..1784550c52 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullvaluepropertytodefault/NullValuePropertyToDefaultMapper.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.optionalmapping.nullvaluepropertytodefault; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT) +public interface NullValuePropertyToDefaultMapper { + + NullValuePropertyToDefaultMapper INSTANCE = Mappers.getMapper( + NullValuePropertyToDefaultMapper.class ); + + void mapTarget(Source source, @MappingTarget Target target); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullvaluepropertytodefault/NullValuePropertyToDefaultTest.java b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullvaluepropertytodefault/NullValuePropertyToDefaultTest.java new file mode 100644 index 0000000000..5e2a23678b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullvaluepropertytodefault/NullValuePropertyToDefaultTest.java @@ -0,0 +1,66 @@ +/* + * 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.optionalmapping.nullvaluepropertytodefault; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + NullValuePropertyToDefaultMapper.class, + Source.class, + Target.class +}) +public class NullValuePropertyToDefaultTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource() + .addComparisonToFixtureFor( NullValuePropertyToDefaultMapper.class ); + + @ProcessorTest + public void optionalToOptionalWhenNull() { + Source source = new Source(); + source.setOptionalToOptional( null ); + + Target target = new Target(); + NullValuePropertyToDefaultMapper.INSTANCE.mapTarget( source, target ); + assertThat( target.getOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + public void nonOptionalToOptionalWhenNull() { + Source source = new Source(); + source.setNonOptionalToOptional( null ); + + Target target = new Target(); + NullValuePropertyToDefaultMapper.INSTANCE.mapTarget( source, target ); + assertThat( target.getNonOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + public void publicOptionalToOptionalWhenNull() { + Source source = new Source(); + source.publicOptionalToOptional = null; + + Target target = new Target(); + NullValuePropertyToDefaultMapper.INSTANCE.mapTarget( source, target ); + assertThat( target.publicOptionalToOptional ).isEmpty(); + } + + @ProcessorTest + public void publicNonOptionalToOptionalWhenNull() { + Source source = new Source(); + source.publicNonOptionalToOptional = null; + + Target target = new Target(); + NullValuePropertyToDefaultMapper.INSTANCE.mapTarget( source, target ); + assertThat( target.publicNonOptionalToOptional ).isEmpty(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullvaluepropertytodefault/Source.java b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullvaluepropertytodefault/Source.java new file mode 100644 index 0000000000..01a6efb1de --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullvaluepropertytodefault/Source.java @@ -0,0 +1,68 @@ +/* + * 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.optionalmapping.nullvaluepropertytodefault; + +import java.util.Objects; +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class Source { + + private Optional optionalToOptional; + private SubType nonOptionalToOptional; + + @SuppressWarnings( "VisibilityModifier" ) + public Optional publicOptionalToOptional; + @SuppressWarnings( "VisibilityModifier" ) + public SubType publicNonOptionalToOptional; + + public Optional getOptionalToOptional() { + return optionalToOptional; + } + + public void setOptionalToOptional(Optional optionalToOptional) { + this.optionalToOptional = optionalToOptional; + } + + public SubType getNonOptionalToOptional() { + return nonOptionalToOptional; + } + + public void setNonOptionalToOptional(SubType nonOptionalToOptional) { + this.nonOptionalToOptional = nonOptionalToOptional; + } + + public static class SubType { + + private final String value; + + public SubType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + SubType subType = (SubType) o; + return Objects.equals( value, subType.value ); + } + + @Override + public int hashCode() { + return Objects.hash( value ); + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullvaluepropertytodefault/Target.java b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullvaluepropertytodefault/Target.java new file mode 100644 index 0000000000..e374e6940d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullvaluepropertytodefault/Target.java @@ -0,0 +1,67 @@ +/* + * 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.optionalmapping.nullvaluepropertytodefault; + +import java.util.Objects; +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class Target { + + private Optional optionalToOptional; + private Optional nonOptionalToOptional; + + @SuppressWarnings( "VisibilityModifier" ) + public Optional publicOptionalToOptional; + @SuppressWarnings( "VisibilityModifier" ) + public Optional publicNonOptionalToOptional; + + public Optional getOptionalToOptional() { + return optionalToOptional; + } + + public void setOptionalToOptional(Optional optionalToOptional) { + this.optionalToOptional = optionalToOptional; + } + + public Optional getNonOptionalToOptional() { + return nonOptionalToOptional; + } + + public void setNonOptionalToOptional(Optional nonOptionalToOptional) { + this.nonOptionalToOptional = nonOptionalToOptional; + } + + public static class SubType { + + private final String value; + + public SubType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + SubType subType = (SubType) o; + return Objects.equals( value, subType.value ); + } + + @Override + public int hashCode() { + return Objects.hash( value ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullvaluetodefault/NullValueToDefaultMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullvaluetodefault/NullValueToDefaultMapper.java new file mode 100644 index 0000000000..8cfd09dd6f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullvaluetodefault/NullValueToDefaultMapper.java @@ -0,0 +1,20 @@ +/* + * 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.optionalmapping.nullvaluetodefault; + +import org.mapstruct.Mapper; +import org.mapstruct.NullValueMappingStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper(nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT) +public interface NullValueToDefaultMapper { + + NullValueToDefaultMapper INSTANCE = Mappers.getMapper( + NullValueToDefaultMapper.class ); + + Target toTarget(Source source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullvaluetodefault/NullValueToDefaultTest.java b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullvaluetodefault/NullValueToDefaultTest.java new file mode 100644 index 0000000000..03e4b11de9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullvaluetodefault/NullValueToDefaultTest.java @@ -0,0 +1,104 @@ +/* + * 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.optionalmapping.nullvaluetodefault; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + NullValueToDefaultMapper.class, + Source.class, + Target.class +}) +public class NullValueToDefaultTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource() + .addComparisonToFixtureFor( NullValueToDefaultMapper.class ); + + @ProcessorTest + public void constructorOptionalToOptionalWhenNull() { + Source source = new Source( null, null, null ); + + Target target = NullValueToDefaultMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + public void constructorOptionalToNonOptionalWhenNull() { + Source source = new Source( null, null, null ); + + Target target = NullValueToDefaultMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorOptionalToNonOptional() ).isNull(); + } + + @ProcessorTest + public void constructorNonOptionalToOptionalWhenNull() { + Source source = new Source( null, null, null ); + + Target target = NullValueToDefaultMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorNonOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + public void optionalToOptionalWhenNull() { + Source source = new Source( null, null, null ); + source.setOptionalToOptional( null ); + + Target target = NullValueToDefaultMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + public void optionalToNonOptionalWhenNull() { + Source source = new Source( null, null, null ); + source.setOptionalToNonOptional( null ); + + Target target = NullValueToDefaultMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToNonOptional() ).isNull(); + } + + @ProcessorTest + public void nonOptionalToOptionalWhenNull() { + Source source = new Source( null, null, null ); + source.setNonOptionalToOptional( null ); + + Target target = NullValueToDefaultMapper.INSTANCE.toTarget( source ); + assertThat( target.getNonOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + public void publicOptionalToOptionalWhenNull() { + Source source = new Source( null, null, null ); + source.publicOptionalToOptional = null; + + Target target = NullValueToDefaultMapper.INSTANCE.toTarget( source ); + assertThat( target.publicOptionalToOptional ).isEmpty(); + } + + @ProcessorTest + public void publicOptionalToNonOptionalWhenNull() { + Source source = new Source( null, null, null ); + source.publicOptionalToNonOptional = null; + + Target target = NullValueToDefaultMapper.INSTANCE.toTarget( source ); + assertThat( target.publicOptionalToNonOptional ).isNull(); + } + + @ProcessorTest + public void publicNonOptionalToOptionalWhenNull() { + Source source = new Source( null, null, null ); + source.publicNonOptionalToOptional = null; + + Target target = NullValueToDefaultMapper.INSTANCE.toTarget( source ); + assertThat( target.publicNonOptionalToOptional ).isEmpty(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullvaluetodefault/Source.java b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullvaluetodefault/Source.java new file mode 100644 index 0000000000..48881cbdf2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullvaluetodefault/Source.java @@ -0,0 +1,102 @@ +/* + * 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.optionalmapping.nullvaluetodefault; + +import java.util.Objects; +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class Source { + + private final Optional constructorOptionalToOptional; + private final Optional constructorOptionalToNonOptional; + private final SubType constructorNonOptionalToOptional; + + private Optional optionalToOptional; + private Optional optionalToNonOptional; + private SubType nonOptionalToOptional; + + @SuppressWarnings( "VisibilityModifier" ) + public Optional publicOptionalToOptional; + @SuppressWarnings( "VisibilityModifier" ) + public Optional publicOptionalToNonOptional; + @SuppressWarnings( "VisibilityModifier" ) + public SubType publicNonOptionalToOptional; + + public Source(Optional constructorOptionalToOptional, Optional constructorOptionalToNonOptional, + SubType constructorNonOptionalToOptional) { + this.constructorOptionalToOptional = constructorOptionalToOptional; + this.constructorOptionalToNonOptional = constructorOptionalToNonOptional; + this.constructorNonOptionalToOptional = constructorNonOptionalToOptional; + } + + public Optional getConstructorOptionalToOptional() { + return constructorOptionalToOptional; + } + + public Optional getConstructorOptionalToNonOptional() { + return constructorOptionalToNonOptional; + } + + public SubType getConstructorNonOptionalToOptional() { + return constructorNonOptionalToOptional; + } + + public Optional getOptionalToOptional() { + return optionalToOptional; + } + + public void setOptionalToOptional(Optional optionalToOptional) { + this.optionalToOptional = optionalToOptional; + } + + public Optional getOptionalToNonOptional() { + return optionalToNonOptional; + } + + public void setOptionalToNonOptional(Optional optionalToNonOptional) { + this.optionalToNonOptional = optionalToNonOptional; + } + + public SubType getNonOptionalToOptional() { + return nonOptionalToOptional; + } + + public void setNonOptionalToOptional(SubType nonOptionalToOptional) { + this.nonOptionalToOptional = nonOptionalToOptional; + } + + public static class SubType { + + private final String value; + + public SubType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + SubType subType = (SubType) o; + return Objects.equals( value, subType.value ); + } + + @Override + public int hashCode() { + return Objects.hash( value ); + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullvaluetodefault/Target.java b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullvaluetodefault/Target.java new file mode 100644 index 0000000000..bc7fd34540 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/nullvaluetodefault/Target.java @@ -0,0 +1,101 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optionalmapping.nullvaluetodefault; + +import java.util.Objects; +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class Target { + + private final Optional constructorOptionalToOptional; + private final SubType constructorOptionalToNonOptional; + private final Optional constructorNonOptionalToOptional; + + private Optional optionalToOptional; + private SubType optionalToNonOptional; + private Optional nonOptionalToOptional; + + @SuppressWarnings( "VisibilityModifier" ) + public Optional publicOptionalToOptional; + @SuppressWarnings( "VisibilityModifier" ) + public SubType publicOptionalToNonOptional; + @SuppressWarnings( "VisibilityModifier" ) + public Optional publicNonOptionalToOptional; + + public Target(Optional constructorOptionalToOptional, SubType constructorOptionalToNonOptional, + Optional constructorNonOptionalToOptional) { + this.constructorOptionalToOptional = constructorOptionalToOptional; + this.constructorOptionalToNonOptional = constructorOptionalToNonOptional; + this.constructorNonOptionalToOptional = constructorNonOptionalToOptional; + } + + public Optional getConstructorOptionalToOptional() { + return constructorOptionalToOptional; + } + + public SubType getConstructorOptionalToNonOptional() { + return constructorOptionalToNonOptional; + } + + public Optional getConstructorNonOptionalToOptional() { + return constructorNonOptionalToOptional; + } + + public Optional getOptionalToOptional() { + return optionalToOptional; + } + + public void setOptionalToOptional(Optional optionalToOptional) { + this.optionalToOptional = optionalToOptional; + } + + public SubType getOptionalToNonOptional() { + return optionalToNonOptional; + } + + public void setOptionalToNonOptional(SubType optionalToNonOptional) { + this.optionalToNonOptional = optionalToNonOptional; + } + + public Optional getNonOptionalToOptional() { + return nonOptionalToOptional; + } + + public void setNonOptionalToOptional(Optional nonOptionalToOptional) { + this.nonOptionalToOptional = nonOptionalToOptional; + } + + public static class SubType { + + private final String value; + + public SubType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + SubType subType = (SubType) o; + return Objects.equals( value, subType.value ); + } + + @Override + public int hashCode() { + return Objects.hash( value ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/sametype/OptionalSameTypeMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/sametype/OptionalSameTypeMapper.java new file mode 100644 index 0000000000..6a34d231bd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/sametype/OptionalSameTypeMapper.java @@ -0,0 +1,20 @@ +/* + * 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.optionalmapping.sametype; + +import org.mapstruct.Mapper; +import org.mapstruct.NullValueCheckStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper(nullValueCheckStrategy = NullValueCheckStrategy.ON_IMPLICIT_CONVERSION) +public interface OptionalSameTypeMapper { + + OptionalSameTypeMapper INSTANCE = Mappers.getMapper( + OptionalSameTypeMapper.class ); + + Target toTarget(Source source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/sametype/OptionalSameTypeTest.java b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/sametype/OptionalSameTypeTest.java new file mode 100644 index 0000000000..686e23bf68 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/sametype/OptionalSameTypeTest.java @@ -0,0 +1,252 @@ +/* + * 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.optionalmapping.sametype; + +import java.util.Optional; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + OptionalSameTypeMapper.class, Source.class, Target.class +}) +public class OptionalSameTypeTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource() + .addComparisonToFixtureFor( OptionalSameTypeMapper.class ); + + @ProcessorTest + public void constructorOptionalToOptionalWhenPresent() { + Source source = new Source( Optional.of( "some value" ), null, null ); + + Target target = OptionalSameTypeMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorOptionalToOptional() ).contains( "some value" ); + } + + @ProcessorTest + public void constructorOptionalToOptionalWhenEmpty() { + Source source = new Source( Optional.empty(), null, null ); + + Target target = OptionalSameTypeMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorOptionalToOptional() ).isEmpty(); + } + + // TODO Contentious + // Should null Optional map to null Optional, or explicitly to empty? + // This one is a bit stranger than the "different types" case. + // Because here, both the source and target are the same type: + // Optional -> Optional, therefore no nested mapping is required. + // By default, the mapper just generates `target.prop = source.prop`. + @ProcessorTest + public void constructorOptionalToOptionalWhenNull() { + Source source = new Source( null, null, null ); + + Target target = OptionalSameTypeMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorOptionalToOptional() ).isNull(); + } + + @ProcessorTest + public void constructorOptionalToNonOptionalWhenPresent() { + Source source = new Source( null, Optional.of( "some value" ), null ); + + Target target = OptionalSameTypeMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorOptionalToNonOptional() ).isEqualTo( "some value" ); + } + + @ProcessorTest + public void constructorOptionalToNonOptionalWhenEmpty() { + Source source = new Source( null, Optional.empty(), null ); + + Target target = OptionalSameTypeMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorOptionalToNonOptional() ).isNull(); + } + + @ProcessorTest + public void constructorOptionalToNonOptionalWhenNull() { + Source source = new Source( null, null, null ); + + Target target = OptionalSameTypeMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorOptionalToNonOptional() ).isNull(); + } + + @ProcessorTest + public void constructorNonOptionalToOptionalWhenNotNull() { + Source source = new Source( null, null, "some value" ); + + Target target = OptionalSameTypeMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorNonOptionalToOptional() ).contains( "some value" ); + } + + @ProcessorTest + public void constructorNonOptionalToOptionalWhenNull() { + Source source = new Source( null, null, null ); + + Target target = OptionalSameTypeMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorNonOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + public void optionalToOptionalWhenPresent() { + Source source = new Source( null, null, null ); + source.setOptionalToOptional( Optional.of( "some value" ) ); + + Target target = OptionalSameTypeMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToOptional() ).contains( "some value" ); + } + + @ProcessorTest + public void optionalToOptionalWhenEmpty() { + Source source = new Source( null, null, null ); + source.setOptionalToOptional( Optional.empty() ); + + Target target = OptionalSameTypeMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToOptional() ).isEmpty(); + } + + // TODO Contentious + // Should null Optional map to null Optional, or explicitly to empty? + // This one is a bit stranger than the "different types" case. + // Because here, both the source and target are the same type: + // Optional -> Optional, therefore no nested mapping is required. + // By default, the mapper just generates `target.prop = source.prop`. + @ProcessorTest + public void optionalToOptionalWhenNull() { + Source source = new Source( null, null, null ); + source.setOptionalToOptional( null ); + + Target target = OptionalSameTypeMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToOptional() ).isNull(); + } + + @ProcessorTest + public void optionalToNonOptionalWhenPresent() { + Source source = new Source( null, null, null ); + source.setOptionalToNonOptional( Optional.of( "some value" ) ); + + Target target = OptionalSameTypeMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToNonOptional() ).isEqualTo( "some value" ); + } + + @ProcessorTest + public void optionalToNonOptionalWhenEmpty() { + Source source = new Source( null, null, null ); + source.setOptionalToNonOptional( Optional.empty() ); + + Target target = OptionalSameTypeMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToNonOptional() ).isNull(); + } + + @ProcessorTest + public void optionalToNonOptionalWhenNull() { + Source source = new Source( null, null, null ); + source.setOptionalToNonOptional( null ); + + Target target = OptionalSameTypeMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToNonOptional() ).isNull(); + } + + @ProcessorTest + public void nonOptionalToOptionalWhenNotNull() { + Source source = new Source( null, null, null ); + source.setNonOptionalToOptional( "some value" ); + + Target target = OptionalSameTypeMapper.INSTANCE.toTarget( source ); + assertThat( target.getNonOptionalToOptional() ).contains( "some value" ); + } + + @ProcessorTest + public void nonOptionalToOptionalWhenNull() { + Source source = new Source( null, null, null ); + source.setNonOptionalToOptional( null ); + + Target target = OptionalSameTypeMapper.INSTANCE.toTarget( source ); + assertThat( target.getNonOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + public void publicOptionalToOptionalWhenPresent() { + Source source = new Source( null, null, null ); + source.publicOptionalToOptional = Optional.of( "some value" ); + + Target target = OptionalSameTypeMapper.INSTANCE.toTarget( source ); + assertThat( target.publicOptionalToOptional ).contains( "some value" ); + } + + @ProcessorTest + public void publicOptionalToOptionalWhenEmpty() { + Source source = new Source( null, null, null ); + source.publicOptionalToOptional = Optional.empty(); + + Target target = OptionalSameTypeMapper.INSTANCE.toTarget( source ); + assertThat( target.publicOptionalToOptional ).isEmpty(); + } + + // TODO Contentious + // Should null Optional map to null Optional, or explicitly to empty? + // This one is a bit stranger than the "different types" case. + // Because here, both the source and target are the same type: + // Optional -> Optional, therefore no nested mapping is required. + // By default, the mapper just generates `target.prop = source.prop`. + @ProcessorTest + public void publicOptionalToOptionalWhenNull() { + Source source = new Source( null, null, null ); + source.publicOptionalToOptional = null; + + Target target = OptionalSameTypeMapper.INSTANCE.toTarget( source ); + assertThat( target.publicOptionalToOptional ).isNull(); + } + + @ProcessorTest + public void publicOptionalToNonOptionalWhenPresent() { + Source source = new Source( null, null, null ); + source.publicOptionalToNonOptional = Optional.of( "some value" ); + + Target target = OptionalSameTypeMapper.INSTANCE.toTarget( source ); + assertThat( target.publicOptionalToNonOptional ).isEqualTo( "some value" ); + } + + @ProcessorTest + public void publicOptionalToNonOptionalWhenEmpty() { + Source source = new Source( null, null, null ); + source.publicOptionalToNonOptional = Optional.empty(); + + Target target = OptionalSameTypeMapper.INSTANCE.toTarget( source ); + assertThat( target.publicOptionalToNonOptional ).isNull(); + } + + @ProcessorTest + public void publicOptionalToNonOptionalWhenNull() { + Source source = new Source( null, null, null ); + source.publicOptionalToNonOptional = null; + + Target target = OptionalSameTypeMapper.INSTANCE.toTarget( source ); + assertThat( target.publicOptionalToNonOptional ).isNull(); + } + + @ProcessorTest + public void publicNonOptionalToOptionalWhenNotNull() { + Source source = new Source( null, null, null ); + source.publicNonOptionalToOptional = "some value"; + + Target target = OptionalSameTypeMapper.INSTANCE.toTarget( source ); + assertThat( target.publicNonOptionalToOptional ).contains( "some value" ); + } + + @ProcessorTest + public void publicNonOptionalToOptionalWhenNull() { + Source source = new Source( null, null, null ); + source.publicNonOptionalToOptional = null; + + Target target = OptionalSameTypeMapper.INSTANCE.toTarget( source ); + assertThat( target.publicNonOptionalToOptional ).isEmpty(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/sametype/Source.java b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/sametype/Source.java new file mode 100644 index 0000000000..6d136056d3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/sametype/Source.java @@ -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.optionalmapping.sametype; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class Source { + + private final Optional constructorOptionalToOptional; + private final Optional constructorOptionalToNonOptional; + private final String constructorNonOptionalToOptional; + + private Optional optionalToOptional; + private Optional optionalToNonOptional; + private String nonOptionalToOptional; + + @SuppressWarnings( "VisibilityModifier" ) + public Optional publicOptionalToOptional; + @SuppressWarnings( "VisibilityModifier" ) + public Optional publicOptionalToNonOptional; + @SuppressWarnings( "VisibilityModifier" ) + public String publicNonOptionalToOptional; + + public Source(Optional constructorOptionalToOptional, Optional constructorOptionalToNonOptional, + String constructorNonOptionalToOptional) { + this.constructorOptionalToOptional = constructorOptionalToOptional; + this.constructorOptionalToNonOptional = constructorOptionalToNonOptional; + this.constructorNonOptionalToOptional = constructorNonOptionalToOptional; + } + + public Optional getConstructorOptionalToOptional() { + return constructorOptionalToOptional; + } + + public Optional getConstructorOptionalToNonOptional() { + return constructorOptionalToNonOptional; + } + + public String getConstructorNonOptionalToOptional() { + return constructorNonOptionalToOptional; + } + + public Optional getOptionalToOptional() { + return optionalToOptional; + } + + public void setOptionalToOptional(Optional optionalToOptional) { + this.optionalToOptional = optionalToOptional; + } + + public Optional getOptionalToNonOptional() { + return optionalToNonOptional; + } + + public void setOptionalToNonOptional(Optional optionalToNonOptional) { + this.optionalToNonOptional = optionalToNonOptional; + } + + public String getNonOptionalToOptional() { + return nonOptionalToOptional; + } + + public void setNonOptionalToOptional(String nonOptionalToOptional) { + this.nonOptionalToOptional = nonOptionalToOptional; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/sametype/Target.java b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/sametype/Target.java new file mode 100644 index 0000000000..d1210aff05 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optionalmapping/sametype/Target.java @@ -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.optionalmapping.sametype; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class Target { + + private final Optional constructorOptionalToOptional; + private final String constructorOptionalToNonOptional; + private final Optional constructorNonOptionalToOptional; + + private Optional optionalToOptional; + private String optionalToNonOptional; + private Optional nonOptionalToOptional; + + @SuppressWarnings( "VisibilityModifier" ) + public Optional publicOptionalToOptional; + @SuppressWarnings( "VisibilityModifier" ) + public String publicOptionalToNonOptional; + @SuppressWarnings( "VisibilityModifier" ) + public Optional publicNonOptionalToOptional; + + public Target(Optional constructorOptionalToOptional, String constructorOptionalToNonOptional, + Optional constructorNonOptionalToOptional) { + this.constructorOptionalToOptional = constructorOptionalToOptional; + this.constructorOptionalToNonOptional = constructorOptionalToNonOptional; + this.constructorNonOptionalToOptional = constructorNonOptionalToOptional; + } + + public Optional getConstructorOptionalToOptional() { + return constructorOptionalToOptional; + } + + public String getConstructorOptionalToNonOptional() { + return constructorOptionalToNonOptional; + } + + public Optional getConstructorNonOptionalToOptional() { + return constructorNonOptionalToOptional; + } + + public Optional getOptionalToOptional() { + return optionalToOptional; + } + + public void setOptionalToOptional(Optional optionalToOptional) { + this.optionalToOptional = optionalToOptional; + } + + public String getOptionalToNonOptional() { + return optionalToNonOptional; + } + + public void setOptionalToNonOptional(String optionalToNonOptional) { + this.optionalToNonOptional = optionalToNonOptional; + } + + public Optional getNonOptionalToOptional() { + return nonOptionalToOptional; + } + + public void setNonOptionalToOptional(Optional nonOptionalToOptional) { + this.nonOptionalToOptional = nonOptionalToOptional; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/assertions/JavaFileAssert.java b/processor/src/test/java/org/mapstruct/ap/testutil/assertions/JavaFileAssert.java index e6eabd9f84..3ceb58fbc9 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/assertions/JavaFileAssert.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/assertions/JavaFileAssert.java @@ -33,7 +33,7 @@ public class JavaFileAssert extends FileAssert { private static final String FIRST_LINE_LICENSE_REGEX = ".*Copyright MapStruct Authors.*"; private static final String GENERATED_DATE_REGEX = "\\s+date = " + - "\"\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\+\\d{4}\","; + "\"\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}[+-]\\d{4}\","; private static final String GENERATED_COMMENTS_REGEX = "\\s+comments = \"version: , compiler: .*, environment: " + ".*\""; private static final String IMPORT_GENERATED_ANNOTATION_REGEX = "import javax\\.annotation\\.(processing\\.)?" + diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/optionalmapping/beforeafter/OptionalBeforeAfterMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/optionalmapping/beforeafter/OptionalBeforeAfterMapperImpl.java new file mode 100644 index 0000000000..8a3b57b4a2 --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/optionalmapping/beforeafter/OptionalBeforeAfterMapperImpl.java @@ -0,0 +1,129 @@ +/* + * 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.optionalmapping.beforeafter; + +import java.util.Optional; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2023-11-20T22:01:31-0500", + comments = "version: , compiler: javac, environment: Java 11.0.19 (Homebrew)" +) +public class OptionalBeforeAfterMapperImpl implements OptionalBeforeAfterMapper { + + @Override + public Target toTarget(Source source) { + if ( source == null ) { + return null; + } + + Optional deepOptionalToOptional = null; + Target.SubType deepOptionalToNonOptional = null; + Optional deepNonOptionalToOptional = null; + Optional shallowOptionalToOptional = null; + String shallowOptionalToNonOptional = null; + Optional shallowNonOptionalToOptional = null; + + deepOptionalToOptional = subTypeOptionalToSubTypeOptional( source.getDeepOptionalToOptional() ); + deepOptionalToNonOptional = subTypeOptionalToSubType( source.getDeepOptionalToNonOptional() ); + deepNonOptionalToOptional = subTypeToSubTypeOptional( source.getDeepNonOptionalToOptional() ); + shallowOptionalToOptional = source.getShallowOptionalToOptional(); + shallowOptionalToNonOptional = stringOptionalToString( source.getShallowOptionalToNonOptional() ); + shallowNonOptionalToOptional = stringToStringOptional( source.getShallowNonOptionalToOptional() ); + + Target target = new Target( deepOptionalToOptional, deepOptionalToNonOptional, deepNonOptionalToOptional, shallowOptionalToOptional, shallowOptionalToNonOptional, shallowNonOptionalToOptional ); + + return target; + } + + protected Target.SubType subTypeToSubType(Source.SubType subType) { + if ( subType == null ) { + return null; + } + + String value = null; + + value = subType.getValue(); + + Target.SubType subType1 = new Target.SubType( value ); + + return subType1; + } + + protected Optional subTypeOptionalToSubTypeOptional(Optional optional) { + beforeDeepOptionalSourceWithNoTargetType( optional ); + + if ( optional == null ) { + return Optional.empty(); + } + + Optional optional1 = optional.map( subType -> subTypeToSubType( subType ) ); + + afterDeepOptionalSourceWithNoTarget( optional ); + afterDeepOptionalSourceWithOptionalTarget( optional1, optional ); + + return optional1; + } + + protected Target.SubType subTypeOptionalToSubType(Optional optional) { + beforeDeepOptionalSourceWithNoTargetType( optional ); + beforeDeepOptionalSourceWithNonOptionalTargetType( Target.SubType.class, optional ); + + if ( optional == null ) { + return null; + } + + Target.SubType subType1 = optional.map( subType -> subTypeToSubType( subType ) ).orElse( null ); + + afterDeepOptionalSourceWithNoTarget( optional ); + afterDeepOptionalSourceWithNonOptionalTarget( subType1, optional ); + + return subType1; + } + + protected Optional subTypeToSubTypeOptional(Source.SubType subType) { + if ( subType == null ) { + return Optional.empty(); + } + + Source.SubType subType1 = subType; + Optional optional = Optional.ofNullable( subTypeToSubType( subType1 ) ); + + afterDeepNonOptionalSourceOptionalTarget( optional, subType ); + + return optional; + } + + protected String stringOptionalToString(Optional optional) { + beforeShallowOptionalSourceWithNoTargetType( optional ); + beforeShallowOptionalSourceWithNonOptionalTargetType( String.class, optional ); + + if ( optional == null ) { + return null; + } + + String string1 = optional.map( string -> string ).orElse( null ); + + afterShallowOptionalSourceWithNoTarget( optional ); + afterShallowOptionalSourceWithNonOptionalTarget( string1, optional ); + + return string1; + } + + protected Optional stringToStringOptional(String string) { + if ( string == null ) { + return Optional.empty(); + } + + String string1 = string; + Optional optional = Optional.ofNullable( string1 ); + + afterShallowNonOptionalSourceOptionalTarget( optional, string ); + + return optional; + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/optionalmapping/differenttypes/OptionalDifferentTypesMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/optionalmapping/differenttypes/OptionalDifferentTypesMapperImpl.java new file mode 100644 index 0000000000..a5f6a4b0f1 --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/optionalmapping/differenttypes/OptionalDifferentTypesMapperImpl.java @@ -0,0 +1,88 @@ +/* + * 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.optionalmapping.differenttypes; + +import java.util.Optional; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2023-11-20T21:40:58-0500", + comments = "version: , compiler: javac, environment: Java 11.0.19 (Homebrew)" +) +public class OptionalDifferentTypesMapperImpl implements OptionalDifferentTypesMapper { + + @Override + public Target toTarget(Source source) { + if ( source == null ) { + return null; + } + + Optional constructorOptionalToOptional = null; + Target.SubType constructorOptionalToNonOptional = null; + Optional constructorNonOptionalToOptional = null; + + constructorOptionalToOptional = subTypeOptionalToSubTypeOptional( source.getConstructorOptionalToOptional() ); + constructorOptionalToNonOptional = subTypeOptionalToSubType( source.getConstructorOptionalToNonOptional() ); + constructorNonOptionalToOptional = subTypeToSubTypeOptional( source.getConstructorNonOptionalToOptional() ); + + Target target = new Target( constructorOptionalToOptional, constructorOptionalToNonOptional, constructorNonOptionalToOptional ); + + target.setOptionalToOptional( subTypeOptionalToSubTypeOptional( source.getOptionalToOptional() ) ); + target.setOptionalToNonOptional( subTypeOptionalToSubType( source.getOptionalToNonOptional() ) ); + target.setNonOptionalToOptional( subTypeToSubTypeOptional( source.getNonOptionalToOptional() ) ); + target.publicOptionalToOptional = subTypeOptionalToSubTypeOptional( source.publicOptionalToOptional ); + target.publicOptionalToNonOptional = subTypeOptionalToSubType( source.publicOptionalToNonOptional ); + target.publicNonOptionalToOptional = subTypeToSubTypeOptional( source.publicNonOptionalToOptional ); + + return target; + } + + protected Target.SubType subTypeToSubType(Source.SubType subType) { + if ( subType == null ) { + return null; + } + + String value = null; + + value = subType.getValue(); + + Target.SubType subType1 = new Target.SubType( value ); + + return subType1; + } + + protected Optional subTypeOptionalToSubTypeOptional(Optional optional) { + if ( optional == null ) { + return Optional.empty(); + } + + Optional optional1 = optional.map( subType -> subTypeToSubType( subType ) ); + + return optional1; + } + + protected Target.SubType subTypeOptionalToSubType(Optional optional) { + if ( optional == null ) { + return null; + } + + Target.SubType subType1 = optional.map( subType -> subTypeToSubType( subType ) ).orElse( null ); + + return subType1; + } + + protected Optional subTypeToSubTypeOptional(Source.SubType subType) { + if ( subType == null ) { + return Optional.empty(); + } + + Source.SubType subType1 = subType; + Optional optional = Optional.ofNullable( subTypeToSubType( subType1 ) ); + + return optional; + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/optionalmapping/nested/OptionalNestedMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/optionalmapping/nested/OptionalNestedMapperImpl.java new file mode 100644 index 0000000000..1bb3a85dbc --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/optionalmapping/nested/OptionalNestedMapperImpl.java @@ -0,0 +1,96 @@ +/* + * 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.optionalmapping.nested; + +import java.util.Optional; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2023-11-20T21:40:54-0500", + comments = "version: , compiler: javac, environment: Java 11.0.19 (Homebrew)" +) +public class OptionalNestedMapperImpl implements OptionalNestedMapper { + + @Override + public Target toTarget(Source source) { + if ( source == null ) { + return null; + } + + Target target = new Target(); + + target.setOptionalToNonOptional( sourceOptionalToNonOptional_Value_( source ) ); + target.setOptionalToOptional( sourceOptionalToOptional_Value( source ) ); + target.setNonOptionalToNonOptional( sourceNonOptionalToNonOptional_Value( source ) ); + target.setNonOptionalToOptional( stringToStringOptional( sourceNonOptionalToOptional_Value( source ) ) ); + + return target; + } + + private String sourceOptionalToNonOptional_Value_(Source source) { + Optional optionalToNonOptional = source.getOptionalToNonOptional(); + if ( optionalToNonOptional == null ) { + return null; + } + Source.NestedOptional optionalValue = optionalToNonOptional.orElse( null ); + if ( optionalValue == null ) { + return null; + } + Optional value = optionalValue.getValue(); + if ( value == null ) { + return null; + } + return value.orElse( null ); + } + + private Optional sourceOptionalToOptional_Value(Source source) { + Optional optionalToOptional = source.getOptionalToOptional(); + if ( optionalToOptional == null ) { + return Optional.empty(); + } + Source.NestedOptional optionalValue = optionalToOptional.orElse( null ); + if ( optionalValue == null ) { + return Optional.empty(); + } + return optionalValue.getValue(); + } + + private String sourceNonOptionalToNonOptional_Value(Source source) { + Optional nonOptionalToNonOptional = source.getNonOptionalToNonOptional(); + if ( nonOptionalToNonOptional == null ) { + return null; + } + Source.NestedNonOptional optionalValue = nonOptionalToNonOptional.orElse( null ); + if ( optionalValue == null ) { + return null; + } + return optionalValue.getValue(); + } + + private String sourceNonOptionalToOptional_Value(Source source) { + Optional nonOptionalToOptional = source.getNonOptionalToOptional(); + if ( nonOptionalToOptional == null ) { + return null; + } + Source.NestedNonOptional optionalValue = nonOptionalToOptional.orElse( null ); + if ( optionalValue == null ) { + return null; + } + return optionalValue.getValue(); + } + + protected Optional stringToStringOptional(String string) { + if ( string == null ) { + return Optional.empty(); + } + + String string1 = string; + Optional optional = Optional.ofNullable( string1 ); + + return optional; + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/optionalmapping/nullcheckalways/OptionalNullCheckAlwaysMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/optionalmapping/nullcheckalways/OptionalNullCheckAlwaysMapperImpl.java new file mode 100644 index 0000000000..8d923e47e1 --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/optionalmapping/nullcheckalways/OptionalNullCheckAlwaysMapperImpl.java @@ -0,0 +1,59 @@ +/* + * 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.optionalmapping.nullcheckalways; + +import java.util.Optional; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2023-11-20T21:40:55-0500", + comments = "version: , compiler: javac, environment: Java 11.0.19 (Homebrew)" +) +public class OptionalNullCheckAlwaysMapperImpl implements OptionalNullCheckAlwaysMapper { + + @Override + public Target toTarget(Source source) { + if ( source == null ) { + return null; + } + + Target target = new Target(); + + if ( source.getOptionalToOptional() != null ) { + target.setOptionalToOptional( source.getOptionalToOptional() ); + } + if ( source.getOptionalToNonOptional() != null ) { + target.setOptionalToNonOptional( stringOptionalToString( source.getOptionalToNonOptional() ) ); + } + if ( source.getNonOptionalToOptional() != null ) { + target.setNonOptionalToOptional( stringToStringOptional( source.getNonOptionalToOptional() ) ); + } + + return target; + } + + protected String stringOptionalToString(Optional optional) { + if ( optional == null ) { + return null; + } + + String string1 = optional.map( string -> string ).orElse( null ); + + return string1; + } + + protected Optional stringToStringOptional(String string) { + if ( string == null ) { + return Optional.empty(); + } + + String string1 = string; + Optional optional = Optional.ofNullable( string1 ); + + return optional; + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/optionalmapping/nullvaluepropertytodefault/NullValuePropertyToDefaultMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/optionalmapping/nullvaluepropertytodefault/NullValuePropertyToDefaultMapperImpl.java new file mode 100644 index 0000000000..8bb4bb5c51 --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/optionalmapping/nullvaluepropertytodefault/NullValuePropertyToDefaultMapperImpl.java @@ -0,0 +1,84 @@ +/* + * 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.optionalmapping.nullvaluepropertytodefault; + +import java.util.Optional; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2023-11-20T21:40:59-0500", + comments = "version: , compiler: javac, environment: Java 11.0.19 (Homebrew)" +) +public class NullValuePropertyToDefaultMapperImpl implements NullValuePropertyToDefaultMapper { + + @Override + public void mapTarget(Source source, Target target) { + if ( source == null ) { + return; + } + + if ( source.getOptionalToOptional() != null ) { + target.setOptionalToOptional( subTypeOptionalToSubTypeOptional( source.getOptionalToOptional() ) ); + } + else { + target.setOptionalToOptional( Optional.empty() ); + } + if ( source.getNonOptionalToOptional() != null ) { + target.setNonOptionalToOptional( subTypeToSubTypeOptional( source.getNonOptionalToOptional() ) ); + } + else { + target.setNonOptionalToOptional( Optional.empty() ); + } + if ( source.publicOptionalToOptional != null ) { + target.publicOptionalToOptional = subTypeOptionalToSubTypeOptional( source.publicOptionalToOptional ); + } + else { + target.publicOptionalToOptional = Optional.empty(); + } + if ( source.publicNonOptionalToOptional != null ) { + target.publicNonOptionalToOptional = subTypeToSubTypeOptional( source.publicNonOptionalToOptional ); + } + else { + target.publicNonOptionalToOptional = Optional.empty(); + } + } + + protected Target.SubType subTypeToSubType(Source.SubType subType) { + if ( subType == null ) { + return null; + } + + String value = null; + + value = subType.getValue(); + + Target.SubType subType1 = new Target.SubType( value ); + + return subType1; + } + + protected Optional subTypeOptionalToSubTypeOptional(Optional optional) { + if ( optional == null ) { + return Optional.empty(); + } + + Optional optional1 = optional.map( subType -> subTypeToSubType( subType ) ); + + return optional1; + } + + protected Optional subTypeToSubTypeOptional(Source.SubType subType) { + if ( subType == null ) { + return Optional.empty(); + } + + Source.SubType subType1 = subType; + Optional optional = Optional.ofNullable( subTypeToSubType( subType1 ) ); + + return optional; + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/optionalmapping/nullvaluetodefault/NullValueToDefaultMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/optionalmapping/nullvaluetodefault/NullValueToDefaultMapperImpl.java new file mode 100644 index 0000000000..3097b3d21d --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/optionalmapping/nullvaluetodefault/NullValueToDefaultMapperImpl.java @@ -0,0 +1,86 @@ +/* + * 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.optionalmapping.nullvaluetodefault; + +import java.util.Optional; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2023-11-20T21:40:57-0500", + comments = "version: , compiler: javac, environment: Java 11.0.19 (Homebrew)" +) +public class NullValueToDefaultMapperImpl implements NullValueToDefaultMapper { + + @Override + public Target toTarget(Source source) { + + Optional constructorOptionalToOptional = null; + Target.SubType constructorOptionalToNonOptional = null; + Optional constructorNonOptionalToOptional = null; + if ( source != null ) { + constructorOptionalToOptional = subTypeOptionalToSubTypeOptional( source.getConstructorOptionalToOptional() ); + constructorOptionalToNonOptional = subTypeOptionalToSubType( source.getConstructorOptionalToNonOptional() ); + constructorNonOptionalToOptional = subTypeToSubTypeOptional( source.getConstructorNonOptionalToOptional() ); + } + + Target target = new Target( constructorOptionalToOptional, constructorOptionalToNonOptional, constructorNonOptionalToOptional ); + + if ( source != null ) { + target.setOptionalToOptional( subTypeOptionalToSubTypeOptional( source.getOptionalToOptional() ) ); + target.setOptionalToNonOptional( subTypeOptionalToSubType( source.getOptionalToNonOptional() ) ); + target.setNonOptionalToOptional( subTypeToSubTypeOptional( source.getNonOptionalToOptional() ) ); + target.publicOptionalToOptional = subTypeOptionalToSubTypeOptional( source.publicOptionalToOptional ); + target.publicOptionalToNonOptional = subTypeOptionalToSubType( source.publicOptionalToNonOptional ); + target.publicNonOptionalToOptional = subTypeToSubTypeOptional( source.publicNonOptionalToOptional ); + } + + return target; + } + + protected Target.SubType subTypeToSubType(Source.SubType subType) { + + String value = null; + if ( subType != null ) { + value = subType.getValue(); + } + + Target.SubType subType1 = new Target.SubType( value ); + + return subType1; + } + + protected Optional subTypeOptionalToSubTypeOptional(Optional optional) { + if ( optional == null ) { + return Optional.empty(); + } + + Optional optional1 = optional.map( subType -> subTypeToSubType( subType ) ); + + return optional1; + } + + protected Target.SubType subTypeOptionalToSubType(Optional optional) { + if ( optional == null ) { + return null; + } + + Target.SubType subType1 = optional.map( subType -> subTypeToSubType( subType ) ).orElse( null ); + + return subType1; + } + + protected Optional subTypeToSubTypeOptional(Source.SubType subType) { + if ( subType == null ) { + return Optional.empty(); + } + + Source.SubType subType1 = subType; + Optional optional = Optional.ofNullable( subTypeToSubType( subType1 ) ); + + return optional; + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/optionalmapping/sametype/OptionalSameTypeMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/optionalmapping/sametype/OptionalSameTypeMapperImpl.java new file mode 100644 index 0000000000..3e467f9ef4 --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/optionalmapping/sametype/OptionalSameTypeMapperImpl.java @@ -0,0 +1,64 @@ +/* + * 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.optionalmapping.sametype; + +import java.util.Optional; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2023-11-20T21:40:52-0500", + comments = "version: , compiler: javac, environment: Java 11.0.19 (Homebrew)" +) +public class OptionalSameTypeMapperImpl implements OptionalSameTypeMapper { + + @Override + public Target toTarget(Source source) { + if ( source == null ) { + return null; + } + + Optional constructorOptionalToOptional = null; + String constructorOptionalToNonOptional = null; + Optional constructorNonOptionalToOptional = null; + + constructorOptionalToOptional = source.getConstructorOptionalToOptional(); + constructorOptionalToNonOptional = stringOptionalToString( source.getConstructorOptionalToNonOptional() ); + constructorNonOptionalToOptional = stringToStringOptional( source.getConstructorNonOptionalToOptional() ); + + Target target = new Target( constructorOptionalToOptional, constructorOptionalToNonOptional, constructorNonOptionalToOptional ); + + target.setOptionalToOptional( source.getOptionalToOptional() ); + target.setOptionalToNonOptional( stringOptionalToString( source.getOptionalToNonOptional() ) ); + target.setNonOptionalToOptional( stringToStringOptional( source.getNonOptionalToOptional() ) ); + target.publicOptionalToOptional = source.publicOptionalToOptional; + target.publicOptionalToNonOptional = stringOptionalToString( source.publicOptionalToNonOptional ); + target.publicNonOptionalToOptional = stringToStringOptional( source.publicNonOptionalToOptional ); + + return target; + } + + protected String stringOptionalToString(Optional optional) { + if ( optional == null ) { + return null; + } + + String string1 = optional.map( string -> string ).orElse( null ); + + return string1; + } + + protected Optional stringToStringOptional(String string) { + if ( string == null ) { + return Optional.empty(); + } + + String string1 = string; + Optional optional = Optional.ofNullable( string1 ); + + return optional; + } +}