Fork me on GitHub

Selma logo

Selma - Java bean mapping that compiles !

Selma stand for Stupid Simple Statically Linked Mapper, it generates static java code to map properties from one bean to another. We Keep It Stupid Simple: convention over configuration, light footprint, easy ot use, easy to learn, are Selma's roots. On one side, Selma is an Annotation Processor that generate Java code to handle the mapping from field to field at compile time. On the over side, it is a java API to instantiate and invoke the generated Mapper implementation.

Why another Java mapping framework ?

Existing frameworks massively use reflection API to build dynamically the mapping code. Well let me see, it's fine ! In fact, it can be, but the better mapping framework I know is vanilla Java code with it's long list of if conditions. Because performance are far better, and I have total control on the mapping. More over, the static code is refactoring proof, and easy to debug. But that is painful :p. Writing all this code is always my preferred part, almost always since there is sometime more than 3 poor little beans. That's where Selma comes, the idea is simple get advantages of the manual mapping without the pain.

The 1 minute tutorial

First add Selma to your pom

<!-- scope provided because the processor is only needed for the compiler -->
    <dependency>
        <groupId>fr.xebia.extras</groupId>
        <artifactId>selma-processor</artifactId>
        <version>0.10</version>
        <scope>provided</scope>
    </dependency>

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

There are 2 artifacts

Then declare your mapper

@Mapper
public interface SelmaMapper {
    OutBean asOutBean(InBean in);
}

Here we've defined SelmaMapper which maps InBean to an instance of OutBean.

Finally, use the mapper

// Get SelmaMapper
SelmaMapper mapper = Selma.getMapper(SelmaMapper.class);

// Map my InBean
OutBean res = mapper.asOutBean(in);

Build and enjoy!

Any issue will be reported in the compiler log, it will also give you fix tips.

Check our sample app project in github.

News

Brand new 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.

See release notes or Milestone 0.10.

Brand new release 0.9
This release includes enhancements and new features:

See release notes or Milestone.

Brand new release 0.8
Few bugs were fixed for this release:

See release notes or Milestone.

Brand new release 0.7
Few bugs were fixed for this release:

See release notes or Milestone.

Brand new release 0.6
Many performance improvements have been done, immutable class mapping does not generate null checks anymore.
We also added support for BigDecimal, and fixed the collections size constructor bug (generated code always passed a size parameter to the constructor even if not existing).
Final big feature is the new builder style API that allows to use multiple data sources and multiple custom mappers.
See release notes or Milestone.

Documentation

This is where you go when get stuck ! First keep in mind that Selma knows only one thing, generate implementation code for mapper interface.

Mapping

Selma match fields using setter and getter names. What does this mean ? Selma match can map only same field names. We only use setters and getters so don't worry about the inner field name.

By default, missing properties aka properties from in bean does not match any field in out bean and reverse, will break compilation.

Let see an example

First the model object a Person :

public class Person {

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

    // + Getters and Setters
}

Then the DTO a PersonDto :

public class PersonDto {

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

    // + Getters and Setters
}

Now we define the Mapper interface :

@Mapper
public interface PersonMapper {

    PersonDto asPersonDto(Person in);
}

All properties are exactly matching so this will work. In this exemple we used an immutable mapping which means that the generated class will be in charge of building from scratch a PersonDTO.

We could also define an update graph mapper :

@Mapper
public interface PersonMapper {

    PersonDto asPersonDto(Person in, PersonDto out);
}

With this kind of declaration, generated code will take care of updating the existing bean or building a new one if it is null. This is what we call mutable mapping.

Both mapping styles can be used in the same mapper interface !

Generated code is:

// GENERATED BY S3LM4
package fr.xebia.extras.selma.beans;


public final class PersonMapperSelmaGeneratedClass implements PersonMapper {


    /**
    * Single constructor
    */
    public PersonMapperSelmaGeneratedClass() {
    }

    /**
    * Mapping method overridden by Selma
    */
    @Override
    public final PersonDto asPersonDto(Person in) {
        fr.xebia.extras.selma.beans.PersonDto out = null;
        if (in != null) {
            out = new fr.xebia.extras.selma.beans.PersonDto();
            if (in.getTags() != null) {
                java.util.ArrayList atagsTmpCollection = new java.util.ArrayList(in.getTags().size());
                out.setTags(atagsTmpCollection);
                for (java.lang.String atagsItem : in.getTags()) {
                    atagsTmpCollection.add(atagsItem);
                }
            } else {
                out.setTags(null);
            }
            if (in.getBirthDay() != null) {
                out.setBirthDay(new java.util.Date(in.getBirthDay().getTime()));
            }else {
                out.setBirthDay(null);
            }
            out.setAge(in.getAge());
            if (in.getIndices() != null) {
                java.lang.Long[] aindicesTmpArray = new java.lang.Long[in.getIndices().length];
                int aindicesTotalCount = in.getIndices().length;
                out.setIndices(aindicesTmpArray);
                for (int aindicesIndex = 0 ; aindicesIndex < aindicesTotalCount; aindicesIndex++) {
                    aindicesTmpArray[aindicesIndex] = in.getIndices()[aindicesIndex];
                }
            }
            else {
                out.setIndices(null);
            }
            out.setLastName(in.getLastName());
            out.setFirstName(in.getFirstName());
        }
        return out;
    }
}

Add the generated source to youre IDE
Using a maven build, you will find the generated sources under target/generated-sources/annotations.

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.11-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>
        

Mapper class

All mappers are generated using the name of the interface with a suffix SelmaGeneratedClass. The class are generated in the same package as the interface and declared final.

Null values

Selma generated code always protect against NPE. The mapper will process mapping if value is not null else the mapper will explicitly set null to the destination bean.

Strict copy

Selma warrants immutability of the in bean, all values set in the destination bean are strict copy of original values except for already immutable classes like String, BigDecimal, Enum, ...

Compiler warnings

Selma tries to notify developers with clear warnings. When a mapping is not possible, a field should be ignored or something in the mapping configuration is not used during the generation process, you will be notified with a clear warning or error.

Custom immutable types

Starting with release 0.9, Selma allows to declare immutable types. Declared immutable types will never be duplicated during mapping. Instead, fields with immutable type will mapped by reference only.

@Mapper has an optional parameter withImmutables you can define with an array of youre own immutable types.

@Mapper(withImmutables={MyImmutableType.class})
public interface ImmutableTypeMapper {

     PersonOut asPersonOut(PersonIn in);
}

Ignore fields

Selma makes strict copy, so it will try to match each single property. When matching properties are not found in source or destination bean, the compiler will fail reporting missing properties.

At this point, you have 2 solutions:

Scope the @Maps(withIgnoreFields={"field"})

You can define this annotation either at the class level of the Mapper interface, or for specific mapping methods.

// Ignore "name" and "size" properties for all beans
@Mapper(withIgnoreFields={"name", "size"})
public interface IgnoreFieldMapper {

    PersonOut asPersonOut(PersonIn in);

    // Ignore "id" only for the CityIn/Out beans
    @Maps(withIgnoreFields={"id"})
    CityOut asCityOut(CityIn in);

}

Qualify withIgnoreFields

The withIgnoreFields support 3 notations to qualify the property to ignore:

  1. Simple property name
  2. Simple class name with propertie (MyClass.name)
  3. Fully qualified class name with propertie (org.package.MyClass.name)

    @Maps(withIgnoreFields="name")  // Will ignore all fields named name

    @Maps(withIgnoreFields="Library.name") // Will ignore name field if found in any Library class

    @Maps(withIgnoreFields=fr.xebia.extras.selma.Library.name") // Will Ignore name field for FQCN Library class.

Unused ignore fields

When one or more fields described in withIgnoreFields, is not found in the graph, so not used, you will get compiler warning. Giving you the opportunity to keep a clean mapping configuration.


Ignored field "toto" is never used
            

Enumerations

Default behavior, is to consider that all values in source enum exists in destination enum. We take great care to build a separate method that will be used every time the same enum to enum mapping is needed. This method maps using a Switch block without default.

In some situation, you'll need more than the default behavior. The EnumMapper annotation is there for you, with it, Selma will use a slightly different strategy. The processor will still build a separate method but this time it map only identical values from both @withEnums and use the given default value. This annotation can be used at interface level inside the Mapper annotation.


@Mapper(withEnums = @EnumMapper(from=EnumA.class, to=EnumB.class, defaultValue="UNKNOWN"))
public interface PersonMapper {

    PersonDto asPersonDto(Person in);

}
             

Use @EnumMapper on methods

@Mapper
public interface PersonMapper {

    @Maps(withEnums = @EnumMapper(from=EnumA.class, to=EnumB.class, defaultValue="UNKNOWN"))
    PersonDto asPersonDto(Person in);

}
         

@Mapper
public interface PersonMapper {

    @EnumMapper(defaultValue="UNKNOWN")
    EnumB asEnumB(EnumA in);

}
         

All parameters are optional but ...
At class level @Mapper, and @Maps you need to specify from and to, if you don't specify defaultValue it will defaults to null in generated code. At method level, if in and out types are withEnums, you won't need to specify from and to.

Unused @EnumMapper

When one or more enum described by @EnumMapper, is not found in the graph, so not used, you will get compiler warning. Giving you the opportunity to keep a clean mapping configuration.


@EnumMapper(from=fr.xebia.extras.selma.beans.EnumIn, to=fr.xebia.extras.selma.beans.EnumOut) is never used
            

Field name mapping

By default the generated mapper, will map fields with same name. But you can also specify a custom name to name mapping using the @Field annotation.

@Field are defined in the @Fields annotation, it contains the list of custom field name mapping. See below, we map the field "nom" to "lastname" and the field "prenom" to "firstname".


@Mapper(
        withCustomFields={@Field({"nom", "lastname"}), @Field({"prenom", "firstname"}, @Field({"cardId", "card.id"})}
})
public interface CustomFieldMapper {

    SimplePersonDto asDto(SimplePerson in);

    SimplePerson fromDto(SimplePersonDto in);

}
            

withCustomFields has a scope
In the example above, we defined three @Field available for the whole mapper implementation class, but you can also define fields name mapping on a per-method basis. @Maps is your friend for this purpose.

Field notation inside @Field

Field notation respect the same exact convention as withIgnoreFields for this attribute. This means that you can specify the class simple name or FQCN before the field name. Field name can be followed by embedded fields separated by dot to target embedded fields.

Unused @Field

When one or more field pairs described by withCustomFields, is not found in the graph, so not used, you will get compiler warning. Giving you the opportunity to keep a clean mapping configuration.


Custom @Field({"nom","lastname"}) mapping is never used !
            

Nested bean

Mapper code will declare a single separated method for each nested bean in the graph.

Declare nested bean methods
Selma will override the methods defined in the mapper interface and use it everywhere it is needed. If they are declared, you will be able to use them from the interface.

@Mapper
public interface UseDeclaredMethodMapper {

    /**
     * Handles mapping of PersonIn to PersonOut
     */
    PersonOut asPersonOut(PersonIn in);

    /**
     * Handles mapping of AddressIn to AddressOut.
     * will be called by asPersonOut() method to map persons addresses
     */
    AddressOut asAddressOut(AddressIn in);

     /*
        This way you can also customize your mappings at method level
      */
}
 

Collection and Map

Collection

Selma detects class implementing java.util.Collection and either use a default implementation, or the given one. This means that Selma can convert any Collection to another Collection class. Let say you have a LinkedList in the inBean and an ArrayList in the out bean or you want to convert from Collection type to a Set.

Map

Selma will also process the mapping of java.util.Map ensuring that both the Key and the values will be converted as any type. Both key and value are subject to mapping, so Pojo will be mapped in both case.

Defaults

Selma uses default collection implementation, when mapping requires to return an interface. See the table below to find corresponding implementation.

Interface Implementation
Set HashSet
SortedSet TreeSet
BlockingDeque LinkedBlockingDeque
BlockingQueue LinkedBlockingQueue
Queue PriorityQueue
Deque ArrayDeque
List ArrayList
NavigableSet ConcurrentSkipListSet
Map HashMap
ConcurrentMap ConcurrentHashMap
ConcurrentNavigableMap ConcurrentSkipListMap
NavigableMap TreeMap
SortedMap TreeMap
Collection ArrayList

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 methods 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 annotation.

Let see it

First, we add an address field to Person and PersonDto classes.

public class Person {

    private String firstName;
    // ...
    private Address address;

               // + Getters and Setters
}

Here address is stored in a nested bean, but in PersonDto:

public class PersonDto {

    private String firstName;
    // ...
    private String address;
    // + Getters and Setters
}

We use a simple String to store the address so on mapping we want to serialize the Address to String in PersonDTO. Now lets define the CustomMapper:

public class AddressMapper {

    // Immutable CustomMapper
    public String addressToString (Address in) {
        return in.toString();
    }

    // Update graph CustomMapper
    public Car carFromDto(CarDto source, Car dest) {
        // Update the dest with properties from source
        return dest;
    }
}

For simplicity, we use the toString() method to demonstrate the feature. The method name is not relevant since Selma only match methods based on the modifier, return type and one or two parameters type. Compiler will report Custom Mapper miss use.

Finally, we should define the Mapper interface:

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

    PersonDto asPersonDto(Person in);
}

Here we are, the custom mapper is now called from Selma generated base code to map our address field from Address class to String.

Custom Mapper Tips
You can define multiple mapping methods in a CustomMapper class. But you can also, define multiple CustomMapper in the @Mapper definition.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

When one or more custom mapper method described by withCustom, is not used, you will get compiler warning. Giving you the opportunity to keep a clean mapping configuration.


Custom mapping method "fr.xebia.extras.selma.it.mappers.CustomMapper.asCityOut" is never used
            

Injecting your custom mapper instance

Sometimes the default mapping depends on services, as an example you need an encryption service to convert the password from clear string to a ciphered one. Selma allows you to give your custom mapper instance when you get the generated Mapper from Selma.

            // Here we inject the AddressMapper instance to the Selma Mapper
Selma.getMapperWithCustom(PersonMapper.class, new AddressMapper());

Mapper 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 in bean and the out bean. See the example below:

public class AddressMapper {

    public void interceptAddressToDto(Address in, AddressDto out) {
        // Do some processing here the out is already filled in using in.
    }
}

Sourced beans

Sourced beans ? What do you mean ?
In some case, the beans needs to be bound to DataSource. With most libraries you'll need to provide a Factory that will be called to get an instance of the targeted bean. But this kind of behaviour drives the developer to use Reflection during the mapping. STOP, this breaks our philosophy because we want static compiled code.

So how does Selma do ? Well, what we support is passing one or more objects to the bean constructor. Doing this we are able to pass a DataSource object to the bean. The only, constraints are to pass the source to Selma when retrieving the generated Mapper and to declare sources type in the mapper interface.

@Mapper(withSources = DataSource.class)
public interface SourcedBeanMapper {

    AddressOutWithDataSource asAddressOut(AddressIn in);
}

Below is the out class:

public class AddressOutWithDataSource {

    // ...
    public final DataSource dataSource;

    public AddressOutWithDataSource(DataSource dataSource) {
       this.dataSource = dataSource;
    }

    // ...
}

Now you can retrieve, the mapper by passing the dataSource:

DataSource dataSource = new DataSource();
SourcedBeanMapper mapper = Selma.getMapper(SourcedBeanMapper.class, dataSource);

Never forget constructors
For sourced beans, Selma will call a constructor passing the source in the same exact order as defined in @Mapper. So ensure, that you have defined the constructors, and don't forget custom mappers !

Builder style API

Selma offers a builder API to configure and build the mapper instance. This is the only way to use multiple data sources and / or multiple custom mapper.

MyMapper mapper = Selma.builder(MyMapper.class).
                        withCustom(customMappers).
                        withSource(dataSources).
                        disableCache().build();

Mapper instance registry

By default Selma class acts as a registry, building and persisting mapper instances. You can call Selma multiple times to get the same mapper instance, once built the instances are stored in private cache so that next calls will return the pre-built instances. You can consider that Mapper instances are Singleton by default.

If you need Selma to build a fresh new mapper instance at each call then you should use the builder API described above and call disableCache().