Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

OSGL (Part 6): Bean Copy Framework

DZone's Guide to

OSGL (Part 6): Bean Copy Framework

The continuation of this series shows you how to use the OSGL API framework for easy bean copying and mapping capabilities.

· Open Source Zone ·
Free Resource

DON’T STRESS! Assess your OSS. Get your free code scanner from FlexeraFlexNet Code Aware scans Java, NuGet, and NPM packages.

This post is about using OSGL library for bean copy/mapping.

Previous posts about OSGL library:

1. API at a Glance

// shallow copy from `foo` to `bar`
$.copy(foo).to(bar);

// deep copy from `foo` to `bar
$.deepCopy(foo).to(bar);

// deep copy using loose name match
$.deepCopy(foo).looseMatching().to(bar);

// deep copy with filter
$.deepCopy(foo).filter("-password,-address.streetNo").to(bar);

// deep copy with special name mapping rule
$.deepCopy(foo)
    .map("id").to("no")
    .map("subject").to("title")
    .to(bar);

// merge data from `foo` to `bar`
$.merge(foo).to(bar);

// map data from `foo` to `bar`
$.map(foo).to(bar);

// map data from `foo` to `bar` using strict name match
$.map(foo).strictMatching().to(bar);

// merge map data from `foo` to `bar`
$.mergeMap(foo).to(bar);


2. Concept

The OSGL bean copy framework relies on Java reflection to get an internal structure of the source and target bean. Unlike some other bean copy tools, OSGL bean copy framework use fields instead of getter/setter methods.

2.1 Semantic

OSGL mapping framework supports the following five different semantics:

  1.  SHALLOW_COPY : copy the first level fields

  2.  DEEP_COPY : copy recursively until immutable type reached

  3.  MERGE : similar to DEEP_COPY , but append elements from source container to target container including array, list, set and map

  4.  MAP : similar to DEEP_COPY , with value type conversion support

  5.  MERGE_MAP : similar to MERGE , with value type conversion support

2.1.1 Immutable Type

Immutable type is an important concept. When OSGL detects a source bean property is immutable-typed, it will stop, dig further down the structure, and copy the reference to the target bean directly.

The following types are considered to be immutable types:

  • primitive types
  • wrapper type of primitive types

  • String

  • Enum

  • Any type that has been registered into OsglConfig   via  registerImmutableClassNames  API

  • Any type that when applied to the predicate function in  OsglConfig  which is registered via  registerImmutableClassPredicate($.Predicate )   API, cause  true  returned.

2.2 Name Mapping

OSGL mapping framework support the following three different name mapping rules:

1) Strict matching, require source name be equal to target name

2) Keyword matching or loose matching, matches keywords of two names. For example, the following names are considered to be a match to each other

  • foo_bar

  • foo-bar

  • fooBar

  • FooBar

  • Foo-Bar

  • Foo_Bar

3) Special matching rules can be set for each mapping process to match completely two different names.

Here is an example of using special mapping rules:

$.deepCopy(foo)
    .map("id").to("no")
    .map("subject").to("title")
    .to(bar);

The above call tells the mapping framework to map id  field in foo  to the no  field in target bar , and map subject  field in foo  to the title  field in  bar .

2.3 Filter

  • Filter can be used to skip copying/mapping certain fields. Filter is provided with a list of field names separated by , , if a field name is prefixed with  -  , it means the field must not be copied/mapped. If the field were prefixed with  +  or without prefix, then it means the field shall be copied/mapped and the fields that are not mentioned shall NOT be copied/mapped. For example:

  •  -email,-password  — do not copy/map email  and password  fields, all other fields shall be copied/mapped

  • +email  — copy only email field, all other fields shall not be copied.

  •  -cc.cvv — do not copy  cvv  field in the instance of cc   field, all other fields shall be copied

  •  -cc,+cc.cvv  — copy cvv  field in the instance of cc  field, all other fields in the cc  instance shall not be copied, all fields other than cc  instance shall be copied.

To apply filter use the following API:

$.deepCopy(foo).filter("-password,-address.streetNo").to(bar);

Note: filter matches the field names in the target object, not source object.

2.4 Root Class

OSGL copy/mapping tool applied on fields instead of Getter/Setter methods. The exploring of fields of a bean is a recursive procedure till it reaches the  Object.class . However, there are cases that it needs to stop the field's exploring journey at a certain parent class. For example, suppose we have defined the following Base class:

public abstract class ModelBase {
    public Date _created;
}

Your other model classes extend from ModelBase , and your Dao uses the  _created  field to check whether the instance is new (when _created is  null ) or an existing record.

Now you want to copy an existing record int a new record to prepopulate that new record for updates, and in the end, you will save the updated copy as a new record. Thus, in this case, you do not want to copy the  _created  field which is defined in  ModelBase . Here is how to do it with  rootClass :

MyModel copy = $.copy(existing).rootClass(ModelBase.class).to(MyModel.class);


2.5 Target Generic Type

If you want to map from a container to another container with different element type, you need to provide the  targetGenericType  parameter to make it work:

List<Foo> fooList = C.list(new Foo(), new Foo());
List<Bar> barList = C.newList();
$.map(fooList).targetGenericType(new TypeReference<List<Bar>>(){}).to(barList);


2.6 Type Convert

If you need to map from a type to a different type field in the target bean, OSGL allows you to specify a type converter. For example, suppose you have a source bean defined as:

public class RawData {
    Calendar date;
    public RawData(long currentTimeMillis) {
        date = Calendar.getInstance();
        date.setTimeInMillis(currentTimeMillis);
    }
}

And your target type is:

public static class ConvertedData {
    DateTime date;
}

If you want to map from RawData  to ConvertedData , you need a type converter to help convert Calendar date  in RawData  to DateTime  date in ConvertedData :

public static Lang.TypeConverter<Calendar, DateTime> converter = new Lang.TypeConverter<Calendar, DateTime>() {
    @Override
    public DateTime convert(Calendar calendar) {
        return new DateTime(calendar.getTimeInMillis());
    }
};

Now you can use the following API to specify the converter defined:

@Test
public void testWithTypeConverter() {
RawData src = new RawData($.ms());
ConvertedData tgt = $.map(src).withConverter(converter).to(ConvertedData.class);
eq(tgt.date.getMillis(), src.date.getTimeInMillis());
}


Note

  • You probably don't need to define too many type converters for common types as most of them has already been defined and registered in OSGL. 

  • Type converter only applied on MAP  and MERGE_MAP  semantic. For SHALLOW_COPY DEEP_COPY  and MERGE , the converter will not be used.

2.6.1 Convert hint

There are some cases that your type convert relies on certain configuration. For example, you want to convert a String to a Date, you need to provide the date format string as a convert hint. Another case is you convert a string to int. You can provide a convert int as the radix.

The source type:

public static class RawDataV2 {
    String date;
    public RawDataV2(String date) {
        this.date = date;
    }
}

The target type:

public static class ConvertedDataV2 {
    Date date;
}

As shown above, we need to map String typed date in source to Date typed date in target, so in the following code we provide the date format string as the hint:

RawDataV2 src = new RawDataV2("20180518");
ConvertedDataV2 tgt = $.map(src).conversionHint(Date.class, "yyyyMMdd").to(ConvertedDataV2.class);

It's worth note that the hint  "yyyyMMdd"  is provided along with a type  Date.class , this tells OSGL to use the hint when the convert target is of  Date.class  type.

2.7 Instance Factory

During the copy/mapping process it might need to create an new instance of a certain type, by default OSGL relies on the  Lang.newInstance(Class)  call to create the new instance. In certain environments it might have the need to inject other instance create logic, for example, when app running in ActFramework might want to delegate the instance creation to  Act.getInstance(Class)  call. OSGL allows it to replace global instance factory and specify instance factory for one copy/mapping operation.

2.7.1 Register a global instance factory

Sample code of registering a global instance factory:

OsglConfig.registerGlobalInstanceFactory(new $.Function<Class, Object>() {
    final App app = Act.app();
    @Override
    public Object apply(Class aClass) throws NotAppliedException, $.Break {
        return app.getInstance(aClass);
    }
});

3. Performance

In this section, we will do a performance benchmark on the OSGL bean copy framework with a few commonly used Bean copy utilities including

  • Apache Commons PropertyUtils (Shallow copy only)

  • Apache Commons BeanUtils (Shallow copy only)

  • HuTool (Shallow copy only)

  • EasyMapper (Shallow copy only)

  • Java Clone (Shallow copy only)

  • ModelMapper (Shallow copy only)

  • Dozer (Deep copy only)

  • Orika (Deep copy only)

3.1 Test Environment

CPU i7 8550U
RAM 16G
OS

LinuxMint 18.3

3.2 Shallow Copy Benchmark

Image title

The source code of Shallow copy benchmark could be found here.

3.3 Deep Copy Benchmark

Image title

The source code of Deep Copy Benchmark can be found here.

4. Conclusion

OSGL Bean Copy framework provides an easy-to-use API for bean copy/mapping with a rich feature set and moderated performance. 

To use OSGL Bean Copy Framework add the following dependency into your pom.xml file:

<dependency>
  <groupId>org.osgl</groupId>
  <artifactId>osgl-tool</artifactId>
  <version>${osgl-tool.version}</version>
</dependency>

At the time of writting this post, ${osgl-tool.version}   is 1.14.0  

Try FlexNet Code Aware Today! A free scan tool for developers. Scan Java, NuGet, and NPM packages for open source security and license compliance issues.

Topics:
java ,bean copy ,tool ,utilities ,open source ,api ,bean mapping ,osgl framework

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}