mirror of
https://github.com/mapstruct/mapstruct.git
synced 2025-07-12 00:00:08 +08:00
#3089 Improve support for Map attributes for Immutables
Co-Authored-By: thunderhook <8238759+thunderhook@users.noreply.github.com>
This commit is contained in:
parent
ea997f83ce
commit
f61a3acec3
@ -26,6 +26,7 @@ public final class FullFeatureCompilationExclusionCliEnhancer implements Process
|
||||
// SPI not working correctly here.. (not picked up)
|
||||
additionalExcludes.add( "org/mapstruct/ap/test/bugs/_1596/*.java" );
|
||||
additionalExcludes.add( "org/mapstruct/ap/test/bugs/_1801/*.java" );
|
||||
additionalExcludes.add( "org/mapstruct/ap/test/bugs/_3089/*.java" );
|
||||
|
||||
switch ( currentJreVersion ) {
|
||||
case JAVA_8:
|
||||
|
@ -21,7 +21,18 @@ public class ImmutablesAccessorNamingStrategy extends DefaultAccessorNamingStrat
|
||||
|
||||
@Override
|
||||
protected boolean isFluentSetter(ExecutableElement method) {
|
||||
return super.isFluentSetter( method ) && !method.getSimpleName().toString().equals( "from" );
|
||||
return super.isFluentSetter( method ) &&
|
||||
!method.getSimpleName().toString().equals( "from" ) &&
|
||||
!isPutterWithUpperCase4thCharacter( method );
|
||||
}
|
||||
|
||||
private boolean isPutterWithUpperCase4thCharacter(ExecutableElement method) {
|
||||
return isPutterMethod( method ) && Character.isUpperCase( method.getSimpleName().toString().charAt( 3 ) );
|
||||
}
|
||||
|
||||
public boolean isPutterMethod(ExecutableElement method) {
|
||||
String methodName = method.getSimpleName().toString();
|
||||
return methodName.startsWith( "put" ) && methodName.length() > 3;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
public class Issue1801Test {
|
||||
|
||||
@ProcessorTest
|
||||
public void shouldIncludeBuildeType() {
|
||||
public void shouldIncludeBuilderType() {
|
||||
|
||||
ItemDTO item = ImmutableItemDTO.builder().id( "test" ).build();
|
||||
|
||||
|
@ -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.bugs._3089;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.Name;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.util.ElementFilter;
|
||||
|
||||
import org.mapstruct.ap.spi.BuilderInfo;
|
||||
import org.mapstruct.ap.spi.BuilderProvider;
|
||||
import org.mapstruct.ap.spi.ImmutablesBuilderProvider;
|
||||
|
||||
/**
|
||||
* @author Oliver Erhart
|
||||
*/
|
||||
public class Issue3089BuilderProvider extends ImmutablesBuilderProvider implements BuilderProvider {
|
||||
|
||||
@Override
|
||||
protected BuilderInfo findBuilderInfo(TypeElement typeElement) {
|
||||
Name name = typeElement.getQualifiedName();
|
||||
if ( name.toString().endsWith( ".Item" ) ) {
|
||||
BuilderInfo info = findBuilderInfoFromInnerBuilderClass( typeElement );
|
||||
if ( info != null ) {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
return super.findBuilderInfo( typeElement );
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks for inner builder class in the Immutable interface / abstract class.
|
||||
*
|
||||
* The inner builder class should be be declared with the following line
|
||||
*
|
||||
* <pre>
|
||||
* public static Builder() extends ImmutableItem.Builder { }
|
||||
* </pre>
|
||||
*
|
||||
* The Immutable instance should be created with the following line
|
||||
*
|
||||
* <pre>
|
||||
* new Item.Builder().withId("123").build();
|
||||
* </pre>
|
||||
*
|
||||
* @see org.mapstruct.ap.test.bugs._3089.domain.Item
|
||||
*
|
||||
* @param typeElement
|
||||
* @return
|
||||
*/
|
||||
private BuilderInfo findBuilderInfoFromInnerBuilderClass(TypeElement typeElement) {
|
||||
if (shouldIgnore( typeElement )) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<TypeElement> innerTypes = ElementFilter.typesIn( typeElement.getEnclosedElements() );
|
||||
ExecutableElement defaultConstructor = innerTypes.stream()
|
||||
.filter( this::isBuilderCandidate )
|
||||
.map( this::getEmptyArgPublicConstructor )
|
||||
.filter( Objects::nonNull )
|
||||
.findAny()
|
||||
.orElse( null );
|
||||
|
||||
if ( defaultConstructor != null ) {
|
||||
return new BuilderInfo.Builder()
|
||||
.builderCreationMethod( defaultConstructor )
|
||||
.buildMethod( findBuildMethods( (TypeElement) defaultConstructor.getEnclosingElement(), typeElement ) )
|
||||
.build();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isBuilderCandidate(TypeElement innerType ) {
|
||||
TypeElement outerType = (TypeElement) innerType.getEnclosingElement();
|
||||
String packageName = this.elementUtils.getPackageOf( outerType ).getQualifiedName().toString();
|
||||
Name outerSimpleName = outerType.getSimpleName();
|
||||
String builderClassName = packageName + ".Immutable" + outerSimpleName + ".Builder";
|
||||
return innerType.getSimpleName().contentEquals( "Builder" )
|
||||
&& getTypeElement( innerType.getSuperclass() ).getQualifiedName().contentEquals( builderClassName )
|
||||
&& innerType.getModifiers().contains( Modifier.PUBLIC );
|
||||
}
|
||||
|
||||
private ExecutableElement getEmptyArgPublicConstructor(TypeElement builderType) {
|
||||
return ElementFilter.constructorsIn( builderType.getEnclosedElements() ).stream()
|
||||
.filter( c -> c.getParameters().isEmpty() )
|
||||
.filter( c -> c.getModifiers().contains( Modifier.PUBLIC ) )
|
||||
.findAny()
|
||||
.orElse( null );
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright MapStruct Authors.
|
||||
*
|
||||
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package org.mapstruct.ap.test.bugs._3089;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.mapstruct.ap.spi.AccessorNamingStrategy;
|
||||
import org.mapstruct.ap.spi.BuilderProvider;
|
||||
import org.mapstruct.ap.spi.ImmutablesAccessorNamingStrategy;
|
||||
import org.mapstruct.ap.test.bugs._3089.domain.ImmutableItem;
|
||||
import org.mapstruct.ap.test.bugs._3089.domain.Item;
|
||||
import org.mapstruct.ap.test.bugs._3089.dto.ImmutableItemDTO;
|
||||
import org.mapstruct.ap.test.bugs._3089.dto.ItemDTO;
|
||||
import org.mapstruct.ap.testutil.IssueKey;
|
||||
import org.mapstruct.ap.testutil.ProcessorTest;
|
||||
import org.mapstruct.ap.testutil.WithClasses;
|
||||
import org.mapstruct.ap.testutil.WithServiceImplementation;
|
||||
import org.mapstruct.ap.testutil.WithServiceImplementations;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Oliver Erhart
|
||||
*/
|
||||
@WithClasses({
|
||||
ItemMapper.class,
|
||||
Item.class,
|
||||
ImmutableItem.class,
|
||||
ItemDTO.class,
|
||||
ImmutableItemDTO.class
|
||||
})
|
||||
@IssueKey("3089")
|
||||
@WithServiceImplementations({
|
||||
@WithServiceImplementation(provides = BuilderProvider.class, value = Issue3089BuilderProvider.class),
|
||||
@WithServiceImplementation(provides = AccessorNamingStrategy.class, value = ImmutablesAccessorNamingStrategy.class)
|
||||
})
|
||||
public class Issue3089Test {
|
||||
|
||||
@ProcessorTest
|
||||
public void shouldIgnorePutterOfMap() {
|
||||
|
||||
Map<String, String> attributesMap = new HashMap<>();
|
||||
attributesMap.put( "a", "b" );
|
||||
attributesMap.put( "c", "d" );
|
||||
|
||||
ItemDTO item = ImmutableItemDTO.builder()
|
||||
.id( "test" )
|
||||
.attributes( attributesMap )
|
||||
.build();
|
||||
|
||||
Item target = ItemMapper.INSTANCE.map( item );
|
||||
|
||||
assertThat( target ).isNotNull();
|
||||
assertThat( target.getId() ).isEqualTo( "test" );
|
||||
assertThat( target.getAttributes() ).isEqualTo( attributesMap );
|
||||
}
|
||||
}
|
@ -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.bugs._3089;
|
||||
|
||||
import org.mapstruct.Builder;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.ap.test.bugs._3089.domain.Item;
|
||||
import org.mapstruct.ap.test.bugs._3089.dto.ItemDTO;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/**
|
||||
* @author Oliver Erhart
|
||||
*/
|
||||
@Mapper(builder = @Builder)
|
||||
public abstract class ItemMapper {
|
||||
|
||||
public static final ItemMapper INSTANCE = Mappers.getMapper( ItemMapper.class );
|
||||
|
||||
public abstract Item map(ItemDTO itemDTO);
|
||||
}
|
@ -0,0 +1,296 @@
|
||||
/*
|
||||
* Copyright MapStruct Authors.
|
||||
*
|
||||
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package org.mapstruct.ap.test.bugs._3089.domain;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Immutable implementation of {@link Item}.
|
||||
* <p>
|
||||
* Superclass should expose a static subclass of the Builder to create immutable instance
|
||||
* {@code public static Builder extends ImmutableItem.Builder}.
|
||||
*
|
||||
* @author Oliver Erhart
|
||||
*/
|
||||
@SuppressWarnings({"all"})
|
||||
public final class ImmutableItem extends Item {
|
||||
private final String id;
|
||||
private final Map<String, String> attributes;
|
||||
|
||||
private ImmutableItem(String id, Map<String, String> attributes) {
|
||||
this.id = id;
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The value of the {@code id} attribute
|
||||
*/
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The value of the {@code attributes} attribute
|
||||
*/
|
||||
@Override
|
||||
public Map<String, String> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the current immutable object by setting a value for the {@link Item#getId() id} attribute.
|
||||
* An equals check used to prevent copying of the same value by returning {@code this}.
|
||||
* @param value A new value for id
|
||||
* @return A modified copy of the {@code this} object
|
||||
*/
|
||||
public final ImmutableItem withId(String value) {
|
||||
String newValue = Objects.requireNonNull(value, "id");
|
||||
if (this.id.equals(newValue)) return this;
|
||||
return new ImmutableItem(newValue, this.attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the current immutable object by replacing the {@link Item#getAttributes() attributes} map with the specified map.
|
||||
* Nulls are not permitted as keys or values.
|
||||
* A shallow reference equality check is used to prevent copying of the same value by returning {@code this}.
|
||||
* @param entries The entries to be added to the attributes map
|
||||
* @return A modified copy of {@code this} object
|
||||
*/
|
||||
public final ImmutableItem withAttributes(Map<String, ? extends String> entries) {
|
||||
if (this.attributes == entries) return this;
|
||||
Map<String, String> newValue = createUnmodifiableMap(true, false, entries);
|
||||
return new ImmutableItem(this.id, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* This instance is equal to all instances of {@code ImmutableItem} that have equal attribute values.
|
||||
* @return {@code true} if {@code this} is equal to {@code another} instance
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object another) {
|
||||
if (this == another) return true;
|
||||
return another instanceof ImmutableItem
|
||||
&& equalTo((ImmutableItem) another);
|
||||
}
|
||||
|
||||
private boolean equalTo(ImmutableItem another) {
|
||||
return id.equals(another.id)
|
||||
&& attributes.equals(another.attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a hash code from attributes: {@code id}, {@code attributes}.
|
||||
* @return hashCode value
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int h = 5381;
|
||||
h += (h << 5) + id.hashCode();
|
||||
h += (h << 5) + attributes.hashCode();
|
||||
return h;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the immutable value {@code Item} with attribute values.
|
||||
* @return A string representation of the value
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Item{"
|
||||
+ "id=" + id
|
||||
+ ", attributes=" + attributes
|
||||
+ "}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an immutable copy of a {@link Item} value.
|
||||
* Uses accessors to get values to initialize the new immutable instance.
|
||||
* If an instance is already immutable, it is returned as is.
|
||||
* @param instance The instance to copy
|
||||
* @return A copied immutable Item instance
|
||||
*/
|
||||
public static ImmutableItem copyOf(Item instance) {
|
||||
if (instance instanceof ImmutableItem) {
|
||||
return (ImmutableItem) instance;
|
||||
}
|
||||
return ImmutableItem.builder()
|
||||
.from(instance)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder for {@link ImmutableItem ImmutableItem}.
|
||||
* <pre>
|
||||
* ImmutableItem.builder()
|
||||
* .id(String) // required {@link Item#getId() id}
|
||||
* .putAttributes|putAllAttributes(String => String) // {@link Item#getAttributes() attributes} mappings
|
||||
* .build();
|
||||
* </pre>
|
||||
* @return A new ImmutableItem builder
|
||||
*/
|
||||
public static ImmutableItem.Builder builder() {
|
||||
return new ImmutableItem.Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds instances of type {@link ImmutableItem ImmutableItem}.
|
||||
* Initialize attributes and then invoke the {@link #build()} method to create an
|
||||
* immutable instance.
|
||||
* <p><em>{@code Builder} is not thread-safe and generally should not be stored in a field or collection,
|
||||
* but instead used immediately to create instances.</em>
|
||||
*/
|
||||
public static class Builder {
|
||||
private static final long INIT_BIT_ID = 0x1L;
|
||||
private long initBits = 0x1L;
|
||||
|
||||
private String id;
|
||||
private Map<String, String> attributes = new LinkedHashMap<String, String>();
|
||||
|
||||
public Builder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill a builder with attribute values from the provided {@code Item} instance.
|
||||
* Regular attribute values will be replaced with those from the given instance.
|
||||
* Absent optional values will not replace present values.
|
||||
* Collection elements and entries will be added, not replaced.
|
||||
* @param instance The instance from which to copy values
|
||||
* @return {@code this} builder for use in a chained invocation
|
||||
*/
|
||||
public final Builder from(Item instance) {
|
||||
Objects.requireNonNull(instance, "instance");
|
||||
id(instance.getId());
|
||||
putAllAttributes(instance.getAttributes());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the value for the {@link Item#getId() id} attribute.
|
||||
* @param id The value for id
|
||||
* @return {@code this} builder for use in a chained invocation
|
||||
*/
|
||||
public final Builder id(String id) {
|
||||
this.id = Objects.requireNonNull(id, "id");
|
||||
initBits &= ~INIT_BIT_ID;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put one entry to the {@link Item#getAttributes() attributes} map.
|
||||
* @param key The key in the attributes map
|
||||
* @param value The associated value in the attributes map
|
||||
* @return {@code this} builder for use in a chained invocation
|
||||
*/
|
||||
public final Builder putAttributes(String key, String value) {
|
||||
this.attributes.put(
|
||||
Objects.requireNonNull(key, "attributes key"),
|
||||
Objects.requireNonNull(value, "attributes value"));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put one entry to the {@link Item#getAttributes() attributes} map. Nulls are not permitted
|
||||
* @param entry The key and value entry
|
||||
* @return {@code this} builder for use in a chained invocation
|
||||
*/
|
||||
public final Builder putAttributes(Map.Entry<String, ? extends String> entry) {
|
||||
String k = entry.getKey();
|
||||
String v = entry.getValue();
|
||||
this.attributes.put(
|
||||
Objects.requireNonNull(k, "attributes key"),
|
||||
Objects.requireNonNull(v, "attributes value"));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or replaces all mappings from the specified map as entries for the {@link Item#getAttributes() attributes} map. Nulls are not permitted
|
||||
* @param entries The entries that will be added to the attributes map
|
||||
* @return {@code this} builder for use in a chained invocation
|
||||
*/
|
||||
public final Builder attributes(Map<String, ? extends String> entries) {
|
||||
this.attributes.clear();
|
||||
return putAllAttributes(entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put all mappings from the specified map as entries to {@link Item#getAttributes() attributes} map. Nulls are not permitted
|
||||
* @param entries The entries that will be added to the attributes map
|
||||
* @return {@code this} builder for use in a chained invocation
|
||||
*/
|
||||
public final Builder putAllAttributes(Map<String, ? extends String> entries) {
|
||||
for (Map.Entry<String, ? extends String> e : entries.entrySet()) {
|
||||
String k = e.getKey();
|
||||
String v = e.getValue();
|
||||
this.attributes.put(
|
||||
Objects.requireNonNull(k, "attributes key"),
|
||||
Objects.requireNonNull(v, "attributes value"));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new {@link ImmutableItem ImmutableItem}.
|
||||
* @return An immutable instance of Item
|
||||
* @throws java.lang.IllegalStateException if any required attributes are missing
|
||||
*/
|
||||
public ImmutableItem build() {
|
||||
if (initBits != 0) {
|
||||
throw new IllegalStateException(formatRequiredAttributesMessage());
|
||||
}
|
||||
return new ImmutableItem(id, createUnmodifiableMap(false, false, attributes));
|
||||
}
|
||||
|
||||
private String formatRequiredAttributesMessage() {
|
||||
List<String> attributes = new ArrayList<>();
|
||||
if ((initBits & INIT_BIT_ID) != 0) attributes.add("id");
|
||||
return "Cannot build Item, some of required attributes are not set " + attributes;
|
||||
}
|
||||
}
|
||||
|
||||
private static <K, V> Map<K, V> createUnmodifiableMap(boolean checkNulls, boolean skipNulls, Map<? extends K, ? extends V> map) {
|
||||
switch (map.size()) {
|
||||
case 0: return Collections.emptyMap();
|
||||
case 1: {
|
||||
Map.Entry<? extends K, ? extends V> e = map.entrySet().iterator().next();
|
||||
K k = e.getKey();
|
||||
V v = e.getValue();
|
||||
if (checkNulls) {
|
||||
Objects.requireNonNull(k, "key");
|
||||
Objects.requireNonNull(v, "value");
|
||||
}
|
||||
if (skipNulls && (k == null || v == null)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
return Collections.singletonMap(k, v);
|
||||
}
|
||||
default: {
|
||||
Map<K, V> linkedMap = new LinkedHashMap<>(map.size());
|
||||
if (skipNulls || checkNulls) {
|
||||
for (Map.Entry<? extends K, ? extends V> e : map.entrySet()) {
|
||||
K k = e.getKey();
|
||||
V v = e.getValue();
|
||||
if (skipNulls) {
|
||||
if (k == null || v == null) continue;
|
||||
} else if (checkNulls) {
|
||||
Objects.requireNonNull(k, "key");
|
||||
Objects.requireNonNull(v, "value");
|
||||
}
|
||||
linkedMap.put(k, v);
|
||||
}
|
||||
} else {
|
||||
linkedMap.putAll(map);
|
||||
}
|
||||
return Collections.unmodifiableMap(linkedMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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.bugs._3089.domain;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Oliver Erhart
|
||||
*/
|
||||
public abstract class Item {
|
||||
public abstract String getId();
|
||||
|
||||
public abstract Map<String, String> getAttributes();
|
||||
|
||||
public static class Builder extends ImmutableItem.Builder { }
|
||||
}
|
@ -0,0 +1,330 @@
|
||||
/*
|
||||
* Copyright MapStruct Authors.
|
||||
*
|
||||
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package org.mapstruct.ap.test.bugs._3089.dto;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Immutable implementation of {@link ItemDTO}.
|
||||
* <p>
|
||||
* Use the builder to create immutable instances:
|
||||
* {@code ImmutableItemDTO.builder()}.
|
||||
*
|
||||
* @author Oliver Erhart
|
||||
*/
|
||||
public final class ImmutableItemDTO extends ItemDTO {
|
||||
private final String id;
|
||||
private final Map<String, String> attributes;
|
||||
|
||||
private ImmutableItemDTO(String id, Map<String, String> attributes) {
|
||||
this.id = id;
|
||||
this.attributes = attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The value of the {@code id} attribute
|
||||
*/
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The value of the {@code attributes} attribute
|
||||
*/
|
||||
@Override
|
||||
public Map<String, String> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the current immutable object by setting a value for the {@link ItemDTO#getId() id} attribute.
|
||||
* An equals check used to prevent copying of the same value by returning {@code this}.
|
||||
*
|
||||
* @param value A new value for id
|
||||
* @return A modified copy of the {@code this} object
|
||||
*/
|
||||
public ImmutableItemDTO withId(String value) {
|
||||
String newValue = Objects.requireNonNull( value, "id" );
|
||||
if ( this.id.equals( newValue ) ) {
|
||||
return this;
|
||||
}
|
||||
return new ImmutableItemDTO( newValue, this.attributes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the current immutable object by replacing the {@link ItemDTO#getAttributes() attributes} map with
|
||||
* the specified map.
|
||||
* Nulls are not permitted as keys or values.
|
||||
* A shallow reference equality check is used to prevent copying of the same value by returning {@code this}.
|
||||
*
|
||||
* @param entries The entries to be added to the attributes map
|
||||
* @return A modified copy of {@code this} object
|
||||
*/
|
||||
public ImmutableItemDTO withAttributes(Map<String, ? extends String> entries) {
|
||||
if ( this.attributes == entries ) {
|
||||
return this;
|
||||
}
|
||||
Map<String, String> newValue = createUnmodifiableMap( true, false, entries );
|
||||
return new ImmutableItemDTO( this.id, newValue );
|
||||
}
|
||||
|
||||
/**
|
||||
* This instance is equal to all instances of {@code ImmutableItemDTO} that have equal attribute values.
|
||||
*
|
||||
* @return {@code true} if {@code this} is equal to {@code another} instance
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object another) {
|
||||
if ( this == another ) {
|
||||
return true;
|
||||
}
|
||||
return another instanceof ImmutableItemDTO
|
||||
&& equalTo( (ImmutableItemDTO) another );
|
||||
}
|
||||
|
||||
private boolean equalTo(ImmutableItemDTO another) {
|
||||
return id.equals( another.id )
|
||||
&& attributes.equals( another.attributes );
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a hash code from attributes: {@code id}, {@code attributes}.
|
||||
*
|
||||
* @return hashCode value
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int h = 5381;
|
||||
h += ( h << 5 ) + id.hashCode();
|
||||
h += ( h << 5 ) + attributes.hashCode();
|
||||
return h;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the immutable value {@code Item} with attribute values.
|
||||
*
|
||||
* @return A string representation of the value
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Item{"
|
||||
+ "id=" + id
|
||||
+ ", attributes=" + attributes
|
||||
+ "}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an immutable copy of a {@link ItemDTO} value.
|
||||
* Uses accessors to get values to initialize the new immutable instance.
|
||||
* If an instance is already immutable, it is returned as is.
|
||||
*
|
||||
* @param instance The instance to copy
|
||||
* @return A copied immutable Item instance
|
||||
*/
|
||||
public static ImmutableItemDTO copyOf(ItemDTO instance) {
|
||||
if ( instance instanceof ImmutableItemDTO ) {
|
||||
return (ImmutableItemDTO) instance;
|
||||
}
|
||||
return ImmutableItemDTO.builder()
|
||||
.from( instance )
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a builder for {@link ImmutableItemDTO ImmutableItemDTO}.
|
||||
* <pre>
|
||||
* ImmutableItemDTO.builder()
|
||||
* .id(String) // required {@link ItemDTO#getId() id}
|
||||
* .putAttributes|putAllAttributes(String => String) // {@link ItemDTO#getAttributes() attributes} mappings
|
||||
* .build();
|
||||
* </pre>
|
||||
*
|
||||
* @return A new ImmutableItemDTO builder
|
||||
*/
|
||||
public static ImmutableItemDTO.Builder builder() {
|
||||
return new ImmutableItemDTO.Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds instances of type {@link ImmutableItemDTO ImmutableItemDTO}.
|
||||
* Initialize attributes and then invoke the {@link #build()} method to create an
|
||||
* immutable instance.
|
||||
* <p><em>{@code Builder} is not thread-safe and generally should not be stored in a field or collection,
|
||||
* but instead used immediately to create instances.</em>
|
||||
*/
|
||||
public static class Builder {
|
||||
private static final long INIT_BIT_ID = 0x1L;
|
||||
private long initBits = 0x1L;
|
||||
|
||||
private String id;
|
||||
private Map<String, String> attributes = new LinkedHashMap<String, String>();
|
||||
|
||||
public Builder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill a builder with attribute values from the provided {@code Item} instance.
|
||||
* Regular attribute values will be replaced with those from the given instance.
|
||||
* Absent optional values will not replace present values.
|
||||
* Collection elements and entries will be added, not replaced.
|
||||
*
|
||||
* @param instance The instance from which to copy values
|
||||
* @return {@code this} builder for use in a chained invocation
|
||||
*/
|
||||
public final ImmutableItemDTO.Builder from(ItemDTO instance) {
|
||||
Objects.requireNonNull( instance, "instance" );
|
||||
id( instance.getId() );
|
||||
putAllAttributes( instance.getAttributes() );
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the value for the {@link ItemDTO#getId() id} attribute.
|
||||
*
|
||||
* @param id The value for id
|
||||
* @return {@code this} builder for use in a chained invocation
|
||||
*/
|
||||
public final ImmutableItemDTO.Builder id(String id) {
|
||||
this.id = Objects.requireNonNull( id, "id" );
|
||||
initBits &= ~INIT_BIT_ID;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put one entry to the {@link ItemDTO#getAttributes() attributes} map.
|
||||
*
|
||||
* @param key The key in the attributes map
|
||||
* @param value The associated value in the attributes map
|
||||
* @return {@code this} builder for use in a chained invocation
|
||||
*/
|
||||
public final ImmutableItemDTO.Builder putAttributes(String key, String value) {
|
||||
this.attributes.put(
|
||||
Objects.requireNonNull( key, "attributes key" ),
|
||||
Objects.requireNonNull( value, "attributes value" )
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Put one entry to the {@link ItemDTO#getAttributes() attributes} map. Nulls are not permitted
|
||||
*
|
||||
* @param entry The key and value entry
|
||||
* @return {@code this} builder for use in a chained invocation
|
||||
*/
|
||||
public final ImmutableItemDTO.Builder putAttributes(Map.Entry<String, ? extends String> entry) {
|
||||
String k = entry.getKey();
|
||||
String v = entry.getValue();
|
||||
this.attributes.put(
|
||||
Objects.requireNonNull( k, "attributes key" ),
|
||||
Objects.requireNonNull( v, "attributes value" )
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or replaces all mappings from the specified map as entries for the {@link ItemDTO#getAttributes()
|
||||
* attributes} map. Nulls are not permitted
|
||||
*
|
||||
* @param entries The entries that will be added to the attributes map
|
||||
* @return {@code this} builder for use in a chained invocation
|
||||
*/
|
||||
public final ImmutableItemDTO.Builder attributes(Map<String, ? extends String> entries) {
|
||||
this.attributes.clear();
|
||||
return putAllAttributes( entries );
|
||||
}
|
||||
|
||||
/**
|
||||
* Put all mappings from the specified map as entries to {@link ItemDTO#getAttributes() attributes} map.
|
||||
* Nulls are not permitted
|
||||
*
|
||||
* @param entries The entries that will be added to the attributes map
|
||||
* @return {@code this} builder for use in a chained invocation
|
||||
*/
|
||||
public final ImmutableItemDTO.Builder putAllAttributes(Map<String, ? extends String> entries) {
|
||||
for ( Map.Entry<String, ? extends String> e : entries.entrySet() ) {
|
||||
String k = e.getKey();
|
||||
String v = e.getValue();
|
||||
this.attributes.put(
|
||||
Objects.requireNonNull( k, "attributes key" ),
|
||||
Objects.requireNonNull( v, "attributes value" )
|
||||
);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new {@link ImmutableItemDTO ImmutableItemDTO}.
|
||||
*
|
||||
* @return An immutable instance of Item
|
||||
* @throws java.lang.IllegalStateException if any required attributes are missing
|
||||
*/
|
||||
public ImmutableItemDTO build() {
|
||||
if ( initBits != 0 ) {
|
||||
throw new IllegalStateException( formatRequiredAttributesMessage() );
|
||||
}
|
||||
return new ImmutableItemDTO( id, createUnmodifiableMap( false, false, attributes ) );
|
||||
}
|
||||
|
||||
private String formatRequiredAttributesMessage() {
|
||||
List<String> attributes = new ArrayList<>();
|
||||
if ( ( initBits & INIT_BIT_ID ) != 0 ) {
|
||||
attributes.add( "id" );
|
||||
}
|
||||
return "Cannot build Item, some of required attributes are not set " + attributes;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings( "checkstyle:AvoidNestedBlocks" )
|
||||
private static <K, V> Map<K, V> createUnmodifiableMap(boolean checkNulls, boolean skipNulls,
|
||||
Map<? extends K, ? extends V> map) {
|
||||
switch ( map.size() ) {
|
||||
case 0:
|
||||
return Collections.emptyMap();
|
||||
case 1: {
|
||||
Map.Entry<? extends K, ? extends V> e = map.entrySet().iterator().next();
|
||||
K k = e.getKey();
|
||||
V v = e.getValue();
|
||||
if ( checkNulls ) {
|
||||
Objects.requireNonNull( k, "key" );
|
||||
Objects.requireNonNull( v, "value" );
|
||||
}
|
||||
if ( skipNulls && ( k == null || v == null ) ) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
return Collections.singletonMap( k, v );
|
||||
}
|
||||
default: {
|
||||
Map<K, V> linkedMap = new LinkedHashMap<>( map.size() );
|
||||
if ( skipNulls || checkNulls ) {
|
||||
for ( Map.Entry<? extends K, ? extends V> e : map.entrySet() ) {
|
||||
K k = e.getKey();
|
||||
V v = e.getValue();
|
||||
if ( skipNulls ) {
|
||||
if ( k == null || v == null ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if ( checkNulls ) {
|
||||
Objects.requireNonNull( k, "key" );
|
||||
Objects.requireNonNull( v, "value" );
|
||||
}
|
||||
linkedMap.put( k, v );
|
||||
}
|
||||
}
|
||||
else {
|
||||
linkedMap.putAll( map );
|
||||
}
|
||||
return Collections.unmodifiableMap( linkedMap );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright MapStruct Authors.
|
||||
*
|
||||
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package org.mapstruct.ap.test.bugs._3089.dto;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Oliver Erhart
|
||||
*/
|
||||
public abstract class ItemDTO {
|
||||
public abstract String getId();
|
||||
|
||||
public abstract Map<String, String> getAttributes();
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user