Github Project - Version 1.0
Mailing list - Issues
Created & Maintained by Séven Le Mesle

Java bean mapping that compiles

Clone, map and transform beans like manual without pain

Rock solid

Mapping is checked at compilation, it is refactoring proof and debuggable

Lightening fast

Uses no reflection, no runtime processing, works as fast as manual mapping

Configuration as code

Uses only annotations to describe and configure mapping, no external configuration files

Annotation processor

Built with JSR 269, supports any Java version from v6, works anywhere (IDE, Maven, Gradle, ...)

Stupid Simple

Lightweight API, easy to learn, gives rich compiler message with tips for fix

Customizable

Supports custom mappers, mapping post-processor, custom field to field mapping

 

Getting started

Add Selma to your build

Selma is available in maven central, feel free to add it to your pom dependencies.

<!-- scope provided because the processor is only needed at compile time-->
<dependency>
    <groupId>fr.xebia.extras</groupId>
    <artifactId>selma-processor</artifactId>
    <version>1.0</version>
    <scope>provided</scope>
</dependency>

<!-- This is the only real dependency you will have in your binaries -->
<dependency>
    <groupId>fr.xebia.extras</groupId>
    <artifactId>selma</artifactId>
    <version>1.0</version>
</dependency>

Declare the mapper

Selma uses a mapper interface to describe and configure the needed mapping. Here we want to map Order to OrderDto and reverse.

@Mapper(
    withCustomFields = {
        @Field({"customer.fullName", "customerFullName"}),
        @Field({"reference", "ref"})
    },
    withIgnoredFields = "id"
)
public interface SelmaMapper {

    // This will build a fresh new OrderDto
    OrderDto asOrderDto(Order in);

     // This will update the given order
    Order asOrder(OrderDto in, Order out);

}

Here we defined some configuration for fields:

  1. Field fullName from the Customer class should be mapped to the field customerFullName in OrderDto
  2. Field reference from any class should be mapped to the field ref in any matching class
  3. Field id should be skipped because it does not exist in OrderDto

Use the mapper

Once built the generated mapper can be retrieved via Selma class.

// Get SelmaMapper
SelmaMapper mapper = Selma.builder(SelmaMapper.class).build();

// Map my InBean
OrderDto res = mapper.asOrderDto(in);

        

Build and enjoy

Compiler will report any issues like not mappable properties, unused mapping configuration or API miss-use.
Check out our sample app project.


 

News

Finally a 1.0 release

Why a 1.0 release now ? Selma now support what I expected in it from the beginning : Custom type to type or field to field and type to type mapping, cyclic mapping, factories, enums, Collections, IoC, Arrays, all kinds of types mapping and so on. The 1.0 release of Selma comes with bug fixes and many new features. Like custom field to field mapping, Maps inheritance, Bean Aggregation and CDI support.

  • #106: Add support for custom mapping method on per field basis
  • 101: Add support for abstract custom mappers
  • #100: Add support for aggregated bean mapping
  • #102: Fix default lower bound type resolution for generic types
  • #109: Fix custom mapper call with out parameter
  • #111: Add support for inner mapper interface
  • #115: Add support for CDI injection
  • #118: Filter enum private members to avoid
  • #104: Remove compiler warnings in custom mapper validation
  • #119: Add support for mapping interfaces instead of beans
  • #120: Add support for abstract getters and setters in mapped beans
  • #125: Fix declared bean array mapping
  • #127: Fix any type to String default mapping
  • #133: Fix mapping embedded array of prime
  • #137: Improve CDI support with CDI and CDI_SINGLETON
  • #138: Add @InheritMaps to avoid @Maps duplication

See release notes or Milestone 1.0 or Github Release Tag.

Brand new release 0.15

This release fixes bugs in generic handling and factory methods. It also implement a new cyclic mapping support using an instance cache. Many thanks to all bug reports and pull requests. 1.0 release will be next big release with specific field to field custom mappers. This will be the last release before final 1.0

  • #2: Support for cyclic mapping with new @Mapper(withCyclicMapping = true) Thanks to Julien Audo
  • 101: Fix generic types resolving in source and destination bean
  • #81: Make a whole package immutable with new @Mapper(withImmutablesPackages = {"org.project.immutables"}) Thanks to Julien Audo
  • #90: Automatic conversion of primitives types to String
  • #80: Add factory support for beans without public constructor Thanks to @facboy
  • #97: Add support for mapping raw types and improve error handling Thanks to @facboy
  • #95: Add support for Builder-style setters Thanks to @facboy
  • #92: Fix using interceptor inside abstract mapper class Thanks to @vakoroteev
  • #104: Remove compiler warnings in custom mapper validation
  • #89: Ignore static methods in mapped beans
  • #101: Fix generic types mapping support

See release notes or Milestone 0.15.

Brand new release 0.14

This release introduces factory methods, it is now possible to declare factory beans inside the mapper. Generic type handling has also been improved to support declaring a Mapper generic interface. Many thanks to all bug reports and pull requests. 1.0 release is coming we just cyclic mapping handling and specific field to field custom mappers.

  • #63: Support for generic type in mappers
  • #39: Add Factory method and class support
  • #78: Add WithIoCServiceName to name Spring service using annotation, Thanks to Benoit Charret
  • #75: Fix generic inherited type handling in collections and map, Thanks to @julaudo
  • #70: Handle custom mappers in a .class package, Thanks to Antoine Leveugle
  • #57: Improve error messages for failing custom field to field mapping

See release notes or Milestone 0.14.

Brand new release 0.13

This release introduces abstract class mappers, it is now possible to declare custom mappings inside the mapper and call mapping methods from custom mappings. There are also bug fixes. Many thanks to all bug reports and pull requests.

  • #53: Support for Abstract class mappers to allow custom mapping method to call mapper methods with this.
  • #65: Fix properties matching to force exact name match instead of startsWith match
  • #66: Fix temporary variables names on embedded field mapping
  • #67: Fix custom mapper registry to match properly both update and immutable methods
  • #68: Fix bad AnnotationMirror cast not valid in eclipse compiler

See release notes or Milestone 0.13.

Release 0.12

This release adds support for collection mapping strategy. The mapping strategy allows to choose wether you want to populate collections from getter or not.

@Mapper(withCollectionStrategy = ALLOW_GETTER) // Enable collections mapping from getter
        

See release notes or Milestone 0.12.

Release 0.11

This release adds Spring support using the new withIoc=SPRING. It is also now possible to use a Selma mapper as the custom mapper of another one.

See release notes or Milestone 0.11.

Release 0.10

This release deprecates @IgnoreFields and @Fields. The new @Maps annotation should now be used instead. It supports all the parameters available in @Mapper at the method scope.

  • #35: Deploy snapshots to sonatype snapshot repository
  • #36: Deprecate @IgnoreFields and @Fields in favor of a new annotation
  • #37: Add support for update graph in custom mappers
  • #38: Add support for scoped custom mappers
  • #40: Add compilation error when default enum value does not exist
  • #41: Compilation error when fields have no getter
  • #42: Support scoped mapping interceptor
  • #43: Remove deprecated @IgnoreFields from warning messages
  • #44: Add support for qualified class name in custom fields mapping
  • #45: Add support for custom embedded field to root level field
  • #46: Add support for ignore missing fields in source bean, destination bean or both
  • #47: Fix missing custom mapper in type mapping registry

See release notes or Milestone 0.10.


 

Mapping beans properties

Selma match properties using setter and getter names. Selma can map only same field names. By default, properties not existing in both source and destination beans will break compilation.

Same propertie names

Duplicate Beans

You can use Selma to build duplicates of your beans or map from model to DTO when they shares same property names.

Model Bean
public class Person {

    private String firstName;
    private String lastName;
    private Date birthDay;
    private int age;
    private Long[] indices;
    private Collection tags;

    // + Getters and Setters
}
Mapper interface
@Mapper
public interface PersonMapper {

    // Returns a duplicate of the source Person
    Person duplicatePerson(Person source);
}

Mapping to DTO

Selma can reduce a model bean to a DTO containing a sublist of the properties.

DTO Bean
public class PersonDTO {

    private String firstName;
    private String lastName;
    private Date birthDay;

    // + Getters and Setters
}
Mapper interface
@Mapper(withIgnoreMissing = IgnoreMissing.DESTINATION)
public interface PersonMapper {

    // Returns a new instance of PersonDTO mapped from Person source
    PersonDto asPersonDTO(Person source);
}

Here we used IgnoreMissing.DESTINATION to indicate we wish to ignore properties (age, indices, tags) from source bean missing in destination bean. But we could also choose to specify each ignored fields.

Mapper listing ignored fields
@Mapper(withIgnoreFields = {"age", "Person.indices", "fr.xebia.selma.Person.tags"})
public interface PersonMapper {

    // Returns a new instance of PersonDTO mapped from Person source
    PersonDto asPersonDTO(Person source);
}

Here we used prefixes Person and fr.xebia.selma.Person to show that you can match ignored fields using simple class name or FQCN prefix.

Mapping from DTO

Selma can use a reduced DTO to build a model bean.

Mapper interface
@Mapper(withIgnoreMissing = IgnoreMissing.SOURCE)
public interface PersonMapper {

    // Returns a new instance of Person mapped from PersonDTO source
    Person asPerson(PersonDTO source);
}

Here we used IgnoreMissing.SOURCE to indicate we wish to ignore properties (age, indices, tags) missing in source bean.

Mapping to and from DTO

A mapper interface can describe multiple mapping methods.

Mapper interface
@Mapper
public interface PersonMapper {

    // Returns a new instance of PersonDTO mapped from Person source
    @Maps(withIgnoreMissing = IgnoreMissing.DESTINATION)
    PersonDto asPersonDTO(Person source);

    // Returns a new instance of Person mapped from PersonDTO source
    @Maps(withIgnoreMissing = IgnoreMissing.SOURCE)
    Person asPerson(PersonDTO source);
}

Here we used @Maps annotation to describe mapping per method. Please notice that @Mapper configuration is available for all mapping methods while @Maps is only applied to a single method. Both annotations provides the exact same parameters.

Disabling missing field checks

By default Selma checks every single missing properties but you can choose to disable these helpfull checks.

Mapper ignoring all missing properties
@Mapper(withIgnoreMissing = IgnoreMissing.ALL)
public interface PersonMapper {

    // Returns a new instance of PersonDTO mapped from Person source
    PersonDto asPersonDTO(Person source);

    // Returns a new instance of Person mapped from PersonDTO source
    Person asPerson(PersonDTO source);
}

Here we used IgnoreMissing.ALL to disable missing properties checking. So we removed @Maps annotation, feel free to combine @Mapper and @Maps in the same mapper.

@Maps configuration inherits and override @Mapper configuration.

Mapping nested beans

Selma takes care of generating mapping for the complete bean graph. Each embedded bean will be mapped in it's own method of the generated code. You can customize the mapping

Person Bean
public class Person {

    private String firstName;
    private String lastName;
    private Date birthDay;
    private Address residency;

    // + Getters and Setters
}
Address Bean
public class Address {

    private String line1;
    private String line2;
    private String zipCode;
    private String city;
    private String country;

    // + Getters and Setters
}
Mapper declaring nested bean
@Mapper
public interface PersonMapper {

    // Returns a new instance of PersonDTO mapped from Person source
    PersonDto asPersonDTO(Person source);

    // Returns a new instance of AddressDTO mapped from Address source
    @Maps(withIgnoreFields = "country")
    AddressDTO asAddress(Address source);
}

Here we declared the asAddress(Address source) method in the mapper interface to demonstrate we can do it. asPersonDTO(Person source) implementation will call asAddress(Address source). Note that you do not need to declare this method for the mapping to work.

@Maps annotation can be used to configure the mapping of each nested bean in the graph.

Mapping cyclic beans

Selma can handle cyclic mapping. This behaviour is disabled by default because it has a small performance impact. You can enable it using the withCyclicMapping parameter available in @Mapper.

Person Bean
public class Person {

    private String firstName;
    private String lastName;
    private Date birthDay;
    private Address residency;

    // + Getters and Setters
}
Address Bean
public class Address {

    private String line1;
    private String line2;
    private String zipCode;
    private String city;
    private String country;
    private Person person // Cyclic reference here !

    // + Getters and Setters
}
Mapper supporting cyclic beans
@Mapper(withCyclicMappings = true)
public interface PersonMapper {

    // Returns a new instance of PersonDTO mapped from Person source
    PersonDto asPersonDTO(Person source);

}

Here we declared the asPersonDTO(Person source) method in the mapper interface to map Person bean and adress beans. The person bean will be mapped only one time and propagated to the person field of the Address bean.

This feature uses a hashmap to know whether a bean has already been mapped.

Customize property names mapping

You can define custom property to property mapping using withCustomFields parameter available in @Mapper and @Maps.

Person Bean
public class Person {

    private String firstName;
    private String lastName;
    private Date birthDay;

    // + Getters and Setters
}
PersonDTO Bean
public class PersonDTO {

    private String name;
    private String lastName;
    private Date birthDate;

    // + Getters and Setters
}
Mapper with custom property names
@Mapper(
    withCustomFields = {
        @Field({"firstName","name"}), @Field({"Person.birthDay","birthDate"})
    }
)
public interface PersonMapper {

    // Returns a new instance of PersonDTO mapped from Person source
    PersonDto asPersonDTO(Person source);

    // Returns a new instance of Person mapped from PersonDTO source
    Person asPerson(PersonDTO source);
}

This configuration will map firstName to name property and birthDay to birthDate property. The @Field annotation define a route that will be applied from PersonDTO to Person and reverse.

@Field supports prefixes to match fields names using simple class name or FQCN prefix. You can also define custom fields in @Maps annotation.

Flatten bean mapping

You can flatten properties from nested bean using withCustomFields parameter available in @Mapper and @Maps.

Person Bean
public class Person {

    private String firstName;
    private String lastName;
    private Date birthDay;

    // See Address bean (line1, line2, zipCode, city, country)
    private Address address;

    // + Getters and Setters
}
PersonDTO Bean
public class PersonDTO {

    private String firstName;
    private String lastName;
    private Date birthDay;
    private String addressLine1;
    private String addressLine2;
    private String addressZipCode;
    private String addressCity;
    private String addressCountry;

    // + Getters and Setters
}
Mapper flattening properties from nested bean
@Mapper(
    withCustomFields = {
        @Field({"address.line1","addressLine1"}), @Field({"address.line2","addressLine2"}),
        @Field({"address.zipCode","addressZipCode"}), @Field({"address.city","addressCity"}),
        @Field({"address.country","addressCountry"})
    }
)
public interface PersonMapper {

    // Returns a new instance of PersonDTO mapped from Person source
    PersonDto asPersonDTO(Person source);

    // Returns a new instance of Person mapped from PersonDTO source
    Person asPerson(PersonDTO source);
}

This configuration will flatten properties from Address bean to address* properties in PersonDTO. It also works to unflatten the properties from PersonDTO to Person and Address.

@Field may flatten from any level of the graph. You are not limited to first nested bean but can point a property from any nesting level.

Reduce @Maps duplication with @InheritMaps

@Maps annotation is powerfull and can be used multiple times in the same mapper interface. The interface can comes to an end where you define the exact same @Maps for multiple method. To avoid this kind of code duplication @InheritMaps is here to help you. Instead of duplicating the @Maps you can simply use @InheritMaps on the corresponding methods.

Mapper with @InheritMaps
@Mapper
public interface ExtendMapper {


    @Maps(withCustomFields = {
            @Field({"Proposal.passenger.age", "ProposalDto.passengerAge"}), @Field({"passenger.card", "passengerCard"}),
            @Field({"Proposal.passenger.date", "ProposalDto.passengerDate"})
    }) ProposalDto asProposalDto(Proposal proposal);

    @InheritMaps
    Proposal asProposal(ProposalDto proposal);

    @InheritMaps
    Proposal asProposal(ProposalDto proposal, Proposal out);
}
Mapper with named @InheritMaps
@Mapper
public interface ExtendNamedMapper {


    @Maps(withCustomFields = {
            @Field({"Proposal.passenger.age", "ProposalDto.passengerAge"}), @Field({"passenger.card", "passengerCard"}),
            @Field({"Proposal.passenger.date", "ProposalDto.passengerDate"})
    }) ProposalDto asProposalDto(Proposal proposal);

    @Maps(withIgnoreFields = {"passengerAge", "passengerDate", "passenger", "passengerCard"})
    ProposalDto asReducedProposalDto(Proposal proposal);

    @InheritMaps(method = "asReducedProposalDto")
    Proposal asProposal(ProposalDto proposal);

    @InheritMaps(method = "asReducedProposalDto")
    Proposal asProposal(ProposalDto proposal, Proposal out);
}

As you can see, the @InheritMaps inherits its configuration from the @Maps. Selma does its best to identify the good @Maps decorated method to inherit from. By default Selma choose the method with the same in/out types pair.
Sometimes Selma finds multiple eligible methods to inherit from, in such situation you just need to provide the method name to choose in the @InheritMaps parameters.

Updating destination bean

Instead of creating a new instance of the destination bean, you can choose to map the source bean against a given instance of the destination bean. You only need to declare a second parameter in your mapping methods that will be used as destination instance.

Mapper updating a destination bean
@Mapper
public interface PersonMapper {

    // Returns the given destination PersonDto mapped from Person source
    PersonDto updatePersonDTOFromPerson(Person source, PersonDto destination);

    // Returns the given destination Person mapped from PersonDTO source
    Person updatePersonFromPersonDTO(PersonDTO source, Person destination);
}

Mapping collections

Selma can map collections using a copy clone strategy. Selma will generate a new collection containing the values mapped from source collection. We support almost all kind of collections and the generated code takes care of preserving the order of elements.

By default Selma uses a setter to populate the newly created collection to the destination bean. When using a Jaxb generated bean you won't have a setter, so you'll need to use the getter to retrieve a fresh new instance of an empty collection to populate with mapped values. For this to work you'll need to use the withCollectionStrategy = ALLOW_GETTER of the @Mapper or @Maps annotation.

Collection bean source
public class CollectionBeanSource {

  private List<String> strings;

  public CollectionBeanSource(List<String> strings) {
      this.strings = strings;
  }

  public List<String> getStrings() {
      if(strings == null){
          strings = new ArrayList<String>();
      }
      return strings;
  }
}
Collection bean destination
public class CollectionBeanDestination {

    private List<String> strings;

    public CollectionBeanDestination() {}

    public CollectionBeanDestination(List<String> strings) {
        this.strings = strings;
    }

    public List<String> getStrings() {
        if(strings == null){
            strings = new ArrayList<String>();
        }
        return strings;
    }

}
Mapper using getter for collection
@Mapper(withCollectionStrategy = ALLOW_GETTER)
public interface CollectionMapper {

    CollectionBeanDestination asCollectionBeanDestination(CollectionBeanSource source);

    CollectionBeanDefensiveDestination asCollectionBeanDefensiveDestination(CollectionBeanSource source);

}

The generated code will call getStrings() from destination bean to populate the collection. Be aware that default strategy does not allow to use getter for collection mapping.

Mapping enums

Selma can map enums using a same value strategy by default. Selma will generate a new method for every single enum to enum mapping required, using a switch block without default.

Person Bean
public class Person {

    private String firstName;
    private String lastName;
    private Date birthDay;
    private PersonKind kind;

    public enum PersonKind {
       SIMPLE_CUSTOMER, PRODUCER, BUSINESS_CUSTOMER
    }
    // + Getters and Setters
}
PersonDTO Bean
public class PersonDTO {

  private String firstName;
    private String lastName;
    private Date birthDay;
    private PersonKindDto kind;

    public enum PersonKindDto {
       SIMPLE_CUSTOMER, PRODUCER, BUSINESS_CUSTOMER
    }
    // + Getters and Setters
}
Mapper mapping same enum values
@Mapper
public interface PersonMapper {

    // Returns a new instance of PersonDTO mapped from Person source
    PersonDto asPersonDTO(Person source);

}
Generated mapping method
  /**
   * Mapping method overridden by Selma
   */
  public final PersonKindDto asSalesChannelDto(PersonKind in) {
    fr.xebia.extras.selma.beans.PersonKindDto out = null;
    if (in != null) {
      switch (in) {
        case SIMPLE_CUSTOMER :  {
          out = fr.xebia.extras.selma.beans.PersonKindDto.SIMPLE_CUSTOMER;
          break;
        }
        case PRODUCER :  {
          out = fr.xebia.extras.selma.beans.PersonKindDto.PRODUCER;
          break;
        }
        case BUSINESS_CUSTOMER :  {
          out = fr.xebia.extras.selma.beans.PersonKindDto.BUSINESS_CUSTOMER;
          break;
        }
      }
    }
    return out;
  }

Selma will raise compilation errors when both enums does not contain the same values.

Enums with default value

Selma can map enums using a same value strategy with a default value. The default value will be used for values not existing in both enums.

Person Bean
public class PersonDto {

    private String firstName;
    private String lastName;
    private Date birthDay;
    private PersonKindDto kind;

    public enum PersonKindDto {
       CUSTOMER, PRODUCER
    }
    // + Getters and Setters
}
Default value
@Mapper
public interface PersonMapper {

    // Map SIMPLE_CUSTOMER and BUSINESS_CUSTOMER to CUSTOMER
    @Maps(
         withEnums = @EnumMapper(from=PersonKind.class, to=PersonKindDto.class, defaultValue="CUSTOMER")
    )
    PersonDto asPersonDTO(Person source);

    // Map SIMPLE_CUSTOMER and BUSINESS_CUSTOMER to CUSTOMER
    @EnumMapper(defaultValue="CUSTOMER")
    PersonKindDto asPersonKindDto(PersonKind source);

}

Here we demonstrated 2 ways of declaring a default value for your enum mapping:

  1. First one, declares a @Maps with enum, so we need to declare both from and to enum with a default value.
  2. Second one, declares a @EnumMapper directly on a method mapping two enums types, with a default value.

It is also possible to declare the enumMapper inside @Mapper.
With this configuration SIMPLE_CUSTOMER and BUSINESS_CUSTOMER will be mapped to CUSTOMER.

To have more control over the enum mapping, you will need to declare a custom mapper.

Mapping with multiple source beans

Sometimes, you need to aggregate multiple source beans in the same destination bean. Selma can take care of this, if you define the mapping method with multiple source beans.
This does not change anything for updates method, just add as the last parameter the bean to be updated.

Mapping method with multiple source beans
/**
 * This mapper interface demonstrate the use of Bean Aggregation.
 */
@Mapper(withIgnoreFields = "missingProperty")
public interface AggregationMapper {

    AggregatedBean mapFromAggregate(FirstBean first, SecondBean second);

    @Maps(withCustom = AggregatedInterceptor.class)
    AggregatedBean mapFromAggregateWithInterceptor(FirstBean first, SecondBean second);

    AggregatedBean mapFromAggregateInUpdate(FirstBean first, SecondBean second, AggregatedBean out);
}

The AggregationMapper generated class maps FirstBean and SecondBean properties to the AggregatedBean properties. You can notice the mapFromAggregateInUpdate method which will update AggregatedBean out and return the result.
As you can see in mapFromAggregateWithInterceptor method, interceptors ares also supported. For them to work, just define the intercepting method with all ordered source beans prior to the out bean parameters.

Selma for now does not checks for properties present in multiple source beans. This means that such properties can be mapped multiple times in the generated code.

Mapping with custom mapper

Sometimes, Selma will not do what you need. Let say, you want to convert from String to date or from Integer to Float. In Selma, we do not want magic conversion, so converting from boxed Integer to native int and reverse is supported but, we do not support auto-magically convert from int to float because there can be data loss. So you can define your own custom mapper.
A custom mapper is a class that contains one or more methods taking an input parameter (the source bean / value) and return the destination value after hand coded mapping. You only need to add the class to the withCustom attribute of the @Mapper or @Maps annotation.

Person Bean
public class Person {

    private String name;
    private Address address;

    // + Getters and Setters
}
PersonDTO Bean
public class PersonDTO {

    private String name;
    private String address;

    // + Getters and Setters
}
Custom mapper mapping address to string
public class AddressCustomMapper {

    // Returns a string representation of the address
    public String addressAsString(Address source){

        return source.toString();
    }

}
Mapper declaring a custom mapper
@Mapper(withCustom = AddressCustomMapper.class)
public class PersonMapper {

    // Returns a PersonDTO instance mapped from source
    PersonDTO asPersonDTO(Person source);

}

The PersonMapper generated class will use the AddressCustomMapper addressAsString method to map the Address to String.

Custom mapper for specific fields

For now we've seen that it's possible to use custom mappers to apply on specific type to type conversion. If you need to apply a specific type to type conversion just for one propertie its also possible.

Custom mapper updating destination
@Mapper(
        withIgnoreFields = {"fr.xebia.extras.selma.beans.PersonIn.male", "fr.xebia.extras.selma.beans.PersonOut.biography"},
        withCustomFields = {
                @Field( value = "age", withCustom = CustomFieldToFieldMapper.CustomAgeAndNamesMapper.class)
        }
)
public interface CustomFieldToFieldMapper {

    String FROM_CUSTOM_FIELD2FIELD_MAPPING = " from custom field2field mapping";
    int AGE_INCREMENT = 42;
    String STRING_TEMPLATE_FOR_STREET = " %s from %s  with @Field(value = \"street\", withCustom = CustomStreetMapper.class)";
    String INTERCEPTED_BY_CUSTOM_INTERCEPTOR = "intercepted by CustomInterceptor";

    @Maps(withCustomFields = {
            @Field(value = "firstName", withCustom = CustomAgeAndNamesMapper.class),
            @Field(value = "street", withCustom = CustomStreetMapper.class)
    })
    PersonOut mapWithCustom(PersonIn in);

    @Maps(withCustomFields = {
            @Field(value = {"firstName", "lastName"}, withCustom = CustomAgeAndNamesMapper.class)
    })
    PersonIn asPersonInInvertingNames(PersonOut in);

    /**
     * Test the use of an interceptor against a field
     */
    @Maps(withIgnoreFields = "Library.name",
            withCustomFields = {
        @Field(value = "books", withCustom = CustomBookInterceptor.class)
    }) LibraryDTO asBookDTO(Library in);

    /**
     * This mapper is called to map the firstname field
     */
    class CustomAgeAndNamesMapper {

        public String mapFirstname(String firstname){
            return firstname + FROM_CUSTOM_FIELD2FIELD_MAPPING;
        }

        public int incrementAge(int age) {
            return age + AGE_INCREMENT;
        }
    }

    /**
     * This mapper is called to map the Street field for
     * PersonOut mapWithCustom(PersonIn in);
     */
    class CustomStreetMapper {
        public String mapStreet(String street){
            return String.format(STRING_TEMPLATE_FOR_STREET,
                    street, CustomStreetMapper.class);
        }
    }

    /**
     * This interceptor is called after the books to books mapping in
     * LibraryDTO asBookDTO(Library in);
     */
    class CustomBookInterceptor {
        public void intercept(List<Book> in, List<BookDTO> out){
            for (BookDTO book : out)
                book.setAuthor(INTERCEPTED_BY_CUSTOM_INTERCEPTOR);
        }
    }
}

As you can see, the @Field annotation now provides a parameter withCustom to handle custom mappers to apply to specific fields. They can be applied on Field to Field or on single field name. This does work for both interceptors and custom mappers.

Update destination bean

You can use custom mappers to update the destination bean as described in updating destination bean. For this kind of use the custom mapping method should declare a second parameter giving the destination bean.

Custom mapper updating destination
public class AddressCustomMapper {

    // Update des CustomMapper
    public Address carFromDto(String addressString, Address destination) {
        String [] lines = addressString.split('\n');
        if (lines.length > 0){
            dest.setFirstLine(lines[0]);
        }
        if (lines.length > 1){
            dest.setSecondLine(lines[1]);
        }
        return dest;
    }

}

All custom mapper class should define a public default constructor so the generated class can instantiate it. Just remember, for selma, method name is nothing, just ensure to define the good In/Out type pair.
Unused custom mapper will be reported as a compilation warning.

Mapping interceptor

Not convinced by custom mapper, you need to do special things on the source or target bean after the mapping occurs. Selma, allows you to define a hook in the custom mapper which will be executed after the mapping itself. You just need to define a method returning void and taking two parameters the source bean and the destination bean. See the example below:

public class AddressCustomMapper {

    public void interceptAddressToDto(Address source, AddressDto destination) {
        // Do some processing here the destination is already mapped.
    }

}

interceptAddressToDto will be called by the generated mapping code at the end of the mapping process.

Every custom mapper instance can be injected in the generated class when you build it from Selma. This allows custom mappers to use some services of your application code.

Abstract mapper

The last way to declare custom mappers is to use an abstract class as the mapper de scription instead of an interface. Selma parse non abstract methods and load every custom mapper method it contains. This can simplify the mapping declaration and allows to call a generated mapping method from a custom mapper.

@Mapper
public abstract class AbstractMapperWithCustom {

    public static final int NUMBER_INCREMENT = 10000;

    public abstract PersonOut asPersonOut(PersonIn in);

    public abstract CityOut asCityOut(CityIn in);

    /**
     * Custom mapper inside the Mapper class
     */
    public AddressOut mapAddress(AddressIn addressIn){
        AddressOut res= null;
        if (addressIn != null){
            res = new AddressOut();
            res.setCity(this.asCityOut(addressIn.getCity()));
            res.setExtras(addressIn.getExtras() == null ? null : new ArrayList<String>(addressIn.getExtras()));
            res.setNumber(addressIn.getNumber() + NUMBER_INCREMENT);
            res.setPrincipal(addressIn.isPrincipal());
            res.setStreet(addressIn.getStreet());
        }
        return res;
    }
}

The generated code will call mapAddress() method for each AddressIn it should map. To handle the mapping of CityIn to CityOut the custom mapping method calls asCityOut() implementation of the generated code.

If you need to add some dependencies to use in the custom mapping methods, you can simply add a setter and use it at the mapper build time.

Selma Mapper as custom mapper

Selma allows to declare a Mapper interface as the custom mapper of another one. Doing this way you can compose a complex mapping strategy made of several mappers calling each other. All mappers will be generated in their private scope.

Person Mapper
  @Mapper(withCustom = AddressMapper.class)
  public interface PersonMapper {

      PersonOut mapWithAddressMapper(PersonIn in);

  }
  
Address Mapper
  @Mapper
  public interface AddressMapper {

      AddressOut asAddressOut(AddressIn in);

      AddressOut asAddressOut(AddressIn in, AddressOut out);

  }

  

AddressMapper will be called by the generated mapping code to handle the mapping of AddressIn to AddressOut.

As every custom mapper, the mapper instance should be injected in the generated class when you build it from Selma.

Mapping immutable types

By default Selma tries to map every bean. This means that given the same source and destination propertie types, Selma will generate code to build a fresh new instance for the destination field.
If you want to directly use the source field in destination passing source reference to the destination field, you should tell Selma that this is an immutable type.

Person Bean
public class Person {

    private String name;
    private String phoneNumber;
    private Date birthDate;
    private Address address;

    // + Getters and Setters
}
PersonDTO Bean
public class PersonDTO {

    private String name;
    private String phoneNumber;
    private Address address;

    // + Getters and Setters
}
Mapper passing by reference
@Mapper(withImmutables = Address.class, withImmutablesPackages = "org.project.immutables")
public class PersonMapper {

    // Returns a PersonDTO instance mapped from source with same address
    @Maps(withIgnoreMissing = IgnoreMissing.DESTINATION)
    PersonDTO asPersonDTO(Person source);

}

This mapper will pass by reference any mapping from Address to Address. You can specify as many immutables type as you want. It is also possible to declare immutables inside @Maps. The withImmutablesPackages, will make Selma consider any bean inside org.project.immutables as immutable class.

Using factory

Some beans can not be built with a default empty constructor. For some reason the application code use a factory to provide new instances of these beans. Default Selma behavior is to call a default constructor without parameters.
To solve this issue without using reflection at runtime, Selma supports sourced beans, the idea is to pass one or more parameters to the constructor call in the generated code.

Sourced Beans

Person Bean bound to a DataSource
public class Person {

    private final DataSource ds;
    private String name;
    private String phoneNumber;
    private Date birthDate;

    // Constructor with DataSource
    public Person (DataSource ds){
        this.ds = ds;
    }

    // + Getters and Setters
}
PersonDTO Bean
public class PersonDTO {

    private String name;
    private String phoneNumbe;
    private Date birthDate;

    // + Getters and Setters
}
Mapper declaring a source
@Mapper(withSources = DataSource.class)
public class PersonMapper {

    // Returns a Person instance mapped from source populated with a DataSource
    Person asPerson(PersonDTO source);

}

This mapper will pass the given DataSource instance to the Person constructor. To see how to inject the DataSource instance refer to Building the mapper.

Factory methods

You can also provide one or more Factory using the withFactory Mapper annotation parameter. The Factory instance can be injected using Selma builder see Building the mapper.

Person Bean bound to a DataSource
public class Person implement SourcedBeans {

    private final DataSource ds;
    private String name;
    private String phoneNumber;
    private Date birthDate;

    // Constructor with DataSource
    public Person (DataSource ds){
        this.ds = ds;
    }

    // + Getters and Setters
}
PersonDTO Bean
public class PersonDTO {

    private String name;
    private String phoneNumbe;
    private Date birthDate;

    // + Getters and Setters
}
Mapper declaring a Factory
@Mapper(withFactories = MyFactory.class)
public class PersonMapper {

    // Returns a Person instance mapped from source populated with a DataSource
    Person asPerson(PersonDTO source);

}
MyFactory bean
public class MyFactory {

    private final DataSource ds;

    public MyFactory(DataSource ds){
        this.ds = ds;
    }

    public <T extends SourcedBeans>  T newSourceBeansInstance(Class<T> targetType) {
        /* ... */
    }

    public City newCity(){
        return new City(ds);
    }

}

This mapper will call the given MyFactory to build new Person by calling the generic newSourceBeansInstance method. You can also specify methods like newCity to statically provide new instances of the City bean. Selma will always first choose matching static typed factory methods. If none are found, Selma will choose the generic method that matches with the closest type boundaries.

Building the mapper

Now that you've define the mapping contract you need, you probably want to retrieve the generated mapper instance. All you need to know for this is the Selma class. It provides a builder API to build and configure your mapper instance.

Builder API
PersonMapper mapper = Selma.builder(PersonMapper.class).
                        withCustom(customMappers).
                        withSource(dataSources).
                        withFactories(factories).
                        disableCache().build();

Here we build a PersonMapper, providing to it custom mappers and sources.

  1. We start a builder for the PersonMapper mapper interface.
  2. We use withCustom(...) to provide our custom mappers.
  3. We use withSource(...) to provide sources to our sourced beans.
  4. We use withFactories(...) to provide Factories to our Mapper.
  5. We call disableCache() to bypass mapper instance cache and force Selma to build a new mapper instance .

By default Selma maintains a cache of the previously loaded mappers, so mappers instance are considered Singleton.

Spring IoC

If you are using Spring IoC framework, you can configure your mapper interface to be configured as a Spring service. Selma will use a @Service to annotate the generated mapper class and it will use @Autowired for all claimed custom mappers and factories. The only thing you have to do is configure Spring to scan for annotations in the Mapper package.

Spring Mapper
@Mapper(withCustom =  CustomImmutableMapperClass.class, withIoC = IoC.SPRING)
public interface AddressMapper {

    AddressOut asAddressOut(AddressIn in);

}

Here we declared a AddressMapper, which will be annotated using Spring annotations. You can inject the generated mapper inside your Java beans or services.

By default Selma does not specify a name for the @Service declared, but you can add since 0.14 withIoCServiceName = YourServiceName to specify a name for the Spring service.

CDI IoC

The withIoC parameter of the mapper has three options to handle CDI annotations. The CDI value generates a @Named bean. The CDI_SINGLETON value generates a @Named and @Singleton bean. The CDI_APPLICATION_SCOPED value generates a @ApplicationScoped bean.

Builder API
@Mapper(withIoC = IoC.CDI)
public interface CustomCdiMapperInMaps {

    public Book asBook(BookDTO source);

    public BookDTO asBookDto(Book source);

}


@Mapper(withIoC = IoC.CDI_SINGLETON)
public interface CustomCdiSingletonMapperInMaps {

    public Book asBook(BookDTO source);

    public BookDTO asBookDto(Book source);

}

@Mapper(withIgnoreFields = "extras", withCustom = CustomImmutableMapperClass.class, withFactories = BeanFactoryClass.class, withIoC = IoC.CDI_APPLICATION_SCOPED, withFinalMappers = false)
public interface AddressMapperCDI {

	AddressOut asAddressOut(AddressIn in);

}

Here we declared three different mappers, to demonstrate the use of the three possible CDI values.

Configuration

Selma can be used from Java 6 to 8.

Using Maven

Old releases of the maven compiler plugin tend to badly support annotation processor messages. We use successfully version 3.2.

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.2</version>
    <configuration>
        <showWarnings>true</showWarnings>
        <optimize>true</optimize>
        <showDeprecation>true</showDeprecation>
    </configuration>
</plugin>

You can also use the annotation processor maven plugin if you can not move to maven compiler 3.2.

<plugin>
    <groupId>org.bsc.maven</groupId>
    <artifactId>maven-processor-plugin</artifactId>
    <version>2.2.4</version>
    <configuration>
        <defaultOutputDirectory>
            ${project.build.directory}/generated-sources
        </defaultOutputDirectory>
        <processors>
            <processor>fr.xebia.extras.selma.codegen.MapperProcessor</processor>
        </processors>
    </configuration>
    <executions>
        <execution>
            <id>process</id>
            <phase>generate-sources</phase>
            <goals>
                <goal>process</goal>
            </goals>
        </execution>
    </executions>
    <dependencies>
        <dependency>
            <groupId>fr.xebia.extras</groupId>
            <artifactId>selma-processor</artifactId>
            <version>${selma.version}</version>
        </dependency>
    </dependencies>
</plugin>

You can find help about configuring eclipse here.

Snapshot builds

You can retrieve snapshots builds from 0.16-SNAPSHOT in the sonatype snapshot repository. For this to work, you should add Sonatype snaphot repository in your maven settings.xml or pom.xml

<repositories>
    <repository>
        <id>oss-sonatype</id>
        <name>oss-sonatype</name>
        <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
        <snapshots>
            <enabled>true</enabled>
        </snapshots>
    </repository>
</repositories>