GithubHelp home page GithubHelp logo

expediagroup / bull Goto Github PK

View Code? Open in Web Editor NEW
183.0 18.0 45.0 24.56 MB

BULL - Bean Utils Light Library

Home Page: https://opensource.expediagroup.com/bull

License: Apache License 2.0

Java 100.00%
java-bean transformations immutable-objects beanutils beancopy bull mapping mapper mutable transformation

bull's Introduction

BULL

Bean Utils Light Library

BULL is a Java Bean to Java Bean transformer that recursively copies data from one object to another, it is generic, flexible, reusable, configurable, and incredibly fast. It's the only library able to transform Mutable, Immutable, and Mixed bean without any custom configuration.

Start using

Maven Central Javadocs Build Status Join the chat at https://join.slack.com/t/bull-crew/shared_invite/enQtNjM1MTE5ODg1MTQzLWI5ODhhYTQ2OWQxODgwYzU1ODMxMWJiZDkzODM3OTJkZjBlM2MwMTI3ZWZjMmU0OGZmN2RmNjg4NWI2NTMzOTk

GitHub site Coverage Status License Dependabot

All BULL modules are available on Maven Central:

  • Bean BOM

It contains all the modules available in the project

<dependency>
    <groupId>com.expediagroup.beans</groupId>
    <artifactId>bull-bom</artifactId>
    <version>x.y.z</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>
  • Bean Transformer

<dependency>
    <groupId>com.expediagroup.beans</groupId>
    <artifactId>bull-bean-transformer</artifactId>
    <version>x.y.z</version>
</dependency>
  • Map Transformer

<dependency>
    <groupId>com.expediagroup.beans</groupId>
    <artifactId>bull-map-transformer</artifactId>
    <version>x.y.z</version>
</dependency>

The project provides two different builds, one compatible with jdk 8 (or above), one with jdk 11 and on with jdk 15 or above.

In case you need to integrate it in a:

Some jdk versions remove the Java Bean constructor's argument names from the compiled code and this may cause problems to the library. On top of that, it's suggested to configure the maven-compiler-plugin, inside your project, as follow:

<build>
    ...
    <pluginManagement>
        <plugins>
            ...
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${maven.compiler.plugin.version}</version>
                <configuration>
                    <source>${maven.compiler.source}</source>
                    <target>${maven.compiler.target}</target>
                    <parameters>true</parameters>
                    <forceJavacCompilerUse>true</forceJavacCompilerUse>
                </configuration>
            </plugin>
        </plugins>
    </pluginManagement>
    ...
</build>

Maven build

Full build

./mvnw clean install

or on Windows

mvnw.cmd clean install

Skip test coverage and checkstyle check

./mvnw clean install -P relaxed

or on Windows

mvnw.cmd clean install -P relaxed

Check for dependencies update

mvn versions:display-dependency-updates -P check-for-updates

or on Windows

mvnw.cmd versions:display-dependency-updates -P check-for-updates

Features:

  • support copy of immutable beans.
  • support copy of mutable beans.
  • support copy of hybrid beans (some fields private and some not).
  • support copy of Java Records.
  • support copy of Java beans without getter and setter methods.
  • support copy with Java primitive type.
  • support copy with Java Collection type. e.g. List<BeanA> => List<BeanB>
  • support copy with nested map fields. e.g. Map<String, Map<String, String>>
  • support copy with array containing primitive types. e.g. String[] => String[]
  • support copy with an array type. e.g. BeanA[] => BeanB[]
  • support copy with property name mapping. e.g. int id => int userId
  • support copy with recursion copy.
  • support validation through annotations.
  • support copy of beans with different field's name.
  • support lambda function field transformation.
  • support copy of java bean built through Builder.
  • easy usage, declarative way to define the property mapping (in case of different names), or simply adding the Lombok annotations.
  • allows setting the default value for all objects not existing in the source object.
  • allows skipping transformation for a given set of fields.
  • supports the retrieval of the value from getters if a field does not exist in the source object.
  • supports the automatic conversion of primitive types.

Feature samples

Bean transformation samples

Simple case:

public class FromBean {                                     public class ToBean {
    private final String name;                                  @NotNull
    private final BigInteger id;                                public BigInteger id;
    private final List<FromSubBean> subBeanList;                private final String name;
    private List<String> list;                                  private final List<String> list;
    private final FromSubBean subObject;                        private final List<ToSubBean> subBeanList;
    private ImmutableToSubFoo subObject;

    // all constructors                                         // all args constructor
    // getters and setters...                                   // getters and setters... 
}    

And one line code as:

ToBean toBean = beanUtils.getTransformer().transform(fromBean, ToBean.class);

Different field names copy:

From class and To class with different field names:

public class FromBean {                                     public class ToBean {

    private final String name;                                  private final String differentName;
    private final int id;                                       private final int id;
    private final List<FromSubBean> subBeanList;                private final List<ToSubBean> subBeanList;
    private final List<String> list;                            private final List<String> list;
    private final FromSubBean subObject;                        private final ToSubBean subObject;

    // getters...
    public ToBean(final String differentName,
                  final int id,
}                                                                       final List<ToSubBean> subBeanList,
    final List<String> list,
    final ToSubBean subObject) {
        this.differentName = differentName;
        this.id = id;
        this.subBeanList = subBeanList;
        this.list = list;
        this.subObject = subObject;
    }

    // getters...           

}

And one line code as:

beanUtils.getTransformer().withFieldMapping(new FieldMapping<>("name", "differentName")).transform(fromBean, ToBean.class);                                                               

it is also possible to map a field in the source class into multiple fields in the destination object.

Given the following source class:

public class SourceClass {
    private final String name;
    private final int id;
}

the following destination class:

public class DestinationClass {
    private final String name;
    private final int id;
    private final int index;
}

and the following operations:

var sourceObj = new SourceClass("foo", 123);

var multipleFieldMapping = new FieldMapping<>("id", "index", "identifier");

var destObj = new BeanUtils().getBeanTransformer()
                     .withFieldMapping(multipleFieldMapping)
                     .transform(sourceObj, DestinationClass.class);

System.out.println("name = " + destObj.getName());
System.out.println("id = " + destObj.getId());
System.out.println("index = " + destObj.getIndex());

the output will be:

name = foo
id = 123
index = 123

Mapping destination fields with correspondent fields contained inside one of the nested objects in the source object:

Assuming that the object FromSubBean is declared as follow:

public class FromSubBean {

    private String serialNumber;
    private Date creationDate;

    // getters and setters... 

}

and our source object and destination object are described as follow:

public class FromBean {                                     public class ToBean {

    private final int id;                                       private final int id;
    private final String name;                                  private final String name;
    private final FromSubBean subObject;                        private final String serialNumber;
    private final Date creationDate;

    // all args constructor                                     // all args constructor
    // getters...                                               // getters... 

}                                                           }

the fields: serialNumber and creationDate needs to be retrieved from subObject, this can be done by defining the whole path to the end property:

FieldMapping serialNumberMapping = new FieldMapping<>("subObject.serialNumber", "serialNumber");                                                             
FieldMapping creationDateMapping = new FieldMapping<>("subObject.creationDate", "creationDate");
                                                             
beanUtils.getTransformer()
         .withFieldMapping(serialNumberMapping, creationDateMapping)
         .transform(fromBean, ToBean.class);                                                               

Different field names defining constructor args:

public class FromBean {                                     public class ToBean {                           
   private final String name;                                  private final String differentName;                   
   private final int id;                                       private final int id;                      
   private final List<FromSubBean> subBeanList;                private final List<ToSubBean> subBeanList;                 
   private final List<String> list;                            private final List<String> list;                    
   private final FromSubBean subObject;                        private final ToSubBean subObject;                    
   
   // all args constructor
   // getters...
                                                               public ToBean(@ConstructorArg("name") final String differentName, 
                                                                        @ConstructorArg("id") final int id,
}                                                                       @ConstructorArg("subBeanList") final List<ToSubBean> subBeanList,
                                                                        @ConstructorArg(fieldName ="list") final List<String> list,
                                                                        @ConstructorArg("subObject") final ToSubBean subObject) {
                                                                        this.differentName = differentName;
                                                                        this.id = id;
                                                                        this.subBeanList = subBeanList;
                                                                        this.list = list;
                                                                        this.subObject = subObject; 
                                                                    }
                                                                
                                                                    // getters...           
                                              
                                                            }

And one line code as:

ToBean toBean = beanUtils.getTransformer().transform(fromBean, ToBean.class);

Different field names and types applying transformation through lambda function:

public class FromBean {                                     public class ToBean {                           
   private final String name;                                  @NotNull                   
   private final BigInteger id;                                public BigInteger identifier;                      
   private final BigInteger index;                             public BigInteger index;                      
   private final List<FromSubBean> subBeanList;                private final String name;                 
   private List<String> list;                                  private final List<String> list;                    
   private final FromSubBean subObject;                        private final List<ImmutableToSubFoo> nestedObjectList;                    
   private final String locale;                                private final Locale locale;                    
                                                               private ImmutableToSubFoo nestedObject;
       
   // constructors...                                          // constructors...
   // getters and setters...                                   // getters and setters...
                                                                                                                              
}                                                           }
FieldTransformer<BigInteger, BigInteger> fieldTransformer = new FieldTransformer<>("identifier", BigInteger::negate);
FieldTransformer<String, Locale> localeTransformer = new FieldTransformer<>("locale", Locale::forLanguageTag);
beanUtils.getTransformer()
    .withFieldMapping(new FieldMapping<>("id", "identifier"))
    .withFieldTransformer(fieldTransformer).transform(fromBean, ToBean.class)
    .withFieldTransformer(localeTransformer);

It's also possible to apply the same transformation function on multiple fields. Taking as an example the above bean and assuming that we would negate both the id and the identifier, the transformer function has to be defined as follows:

FieldTransformer<BigInteger, BigInteger> fieldTransformer = new FieldTransformer<>(List.of("identifier", "index"), BigInteger::negate);

Assign a default value in case of missing field in the source object:

Assign a default value in case of a missing field in the source object:

public class FromBean {                                     public class ToBean {                           
   private final String name;                                  @NotNull                   
   private final BigInteger id;                                public BigInteger id;                      
                                                               private final String name;                 
                                                               private String notExistingField; // this will be null and no exceptions will be raised

   // constructors...                                          // constructors...
   // getters...                                               // getters and setters...

}                                                           }

And one line code as:

ToBean toBean = beanUtils.getTransformer()
                    .setDefaultValueForMissingField(true).transform(fromBean, ToBean.class);

Disable the default value set for primitive types in case they are null:

BULL by default sets the default value for all primitive types fields in case their value is in the source object. Given the following Java Bean:

public class FromBean {                                     public class ToBean {                           
   private final String name;                                  @NotNull                   
   private final BigInteger id;                                public BigInteger id;                      
                                                               private final String name;                 

   // constructors...                                          // constructors...
   // getters...                                               // getters and setters...

}                                                           }

in case the field id in the FromBean object is null, the value assigned the correspondent field in the ToBean object will be 0. To disable this you can simply do:

ToBean toBean = beanUtils.getTransformer()
                    .setDefaultValueForMissingPrimitiveField(false).transform(fromBean, ToBean.class);

in this case, the field id after the transformation will be null

Applying a transformation function in case of missing fields in the source object:

Assign a default value in case of a missing field in the source object:

public class FromBean {                                     public class ToBean {                           
   private final String name;                                  @NotNull                   
   private final BigInteger id;                                public BigInteger id;                      
                                                               private final String name;                 
                                                               private String notExistingField; // this will have value: sampleVal
                                                               
   // all args constructor                                     // constructors...
   // getters...                                               // getters and setters...
}                                                           }

And one line code as:

FieldTransformer<String, String> notExistingFieldTransformer = new FieldTransformer<>("notExistingField", () -> "sampleVal");
ToBean toBean = beanUtils.getTransformer()
                    .withFieldTransformer(notExistingFieldTransformer)
                    .transform(fromBean, ToBean.class);

Apply a transformation function on a field contained in a nested object:

This example shows how a lambda transformation function can be applied to a nested object field.

Given:

public class FromBean {                                     public class ToBean {                           
   private final String name;                                  private final String name;                   
   private final FromSubBean nestedObject;                     private final ToSubBean nestedObject;                    

   // all args constructor                                     // all args constructor
   // getters...                                               // getters...
}                                                           }

and

public class ToSubBean {                           
   private final String name;                   
   private final long index;                    
}

Assuming that the lambda transformation function should be applied only to field: name contained into the ToSubBean object, the transformation function has to be defined as follow:

FieldTransformer<String, String> nameTransformer = new FieldTransformer<>("nestedObject.name", StringUtils::capitalize);
ToBean toBean = beanUtils.getTransformer()
                    .withFieldTransformer(nameTransformer)
                    .transform(fromBean, ToBean.class);

Map a primitive type field in the source object into a nested object:

This example shows how to map a primitive field into a nested object into the destination one.

Given:

public class FromBean {                                     public class ToBean {                           
   private final String name;                                  private final String name;                   
   private final FromSubBean nestedObject;                     private final ToSubBean nestedObject;                    
   private final int x;
   // all args constructor                                     // all args constructor
   // getters...                                               // getters...
}                                                           }

and

public class ToSubBean {                           
   private final int x;
   
   // all args constructor
}  // getters...          

Assuming that the value x should be mapped into the field: x contained into the ToSubBean object, the field mapping has to be defined as follow:

ToBean toBean = beanUtils.getTransformer()
                    .withFieldMapping(new FieldMapping<>("x", "nestedObject.x"));

Apply a transformation function on all fields matching with the given one:

This example shows how a lambda transformation function can be applied to all fields matching with the given one independently from their position.

Given:

public class FromBean {                                     public class ToBean {                           
   private final String name;                                  private final String name;                   
   private final FromSubBean nestedObject;                     private final ToSubBean nestedObject;                    

   // all args constructor                                     // all args constructor
   // getters...                                               // getters...
}                                                           }

and

public class FromSubBean {                                  public class ToSubBean {                           
   private final String name;                                  private final String name;                   
   private final long index;                                   private final long index;                    
   
   // all args constructor                                     // all args constructor
   // getters...                                               // getters...
}                                                           }

Assuming that the lambda transformation function should be applied only to the field: name contained in the ToSubBean object, the transformation function has to be defined as follow:

FieldTransformer<String, String> nameTransformer = new FieldTransformer<>("name", StringUtils::capitalize);
ToBean toBean = beanUtils.getTransformer()
                    .setFlatFieldNameTransformation(true)
                    .withFieldTransformer(nameTransformer)
                    .transform(fromBean, ToBean.class);

Static transformer function:

List<FromFooSimple> fromFooSimpleList = Arrays.asList(fromFooSimple, fromFooSimple);

can be transformed as follow:

Function<FromFooSimple, ImmutableToFooSimple> transformerFunction = BeanUtils.getTransformer(ImmutableToFooSimple.class);
List<ImmutableToFooSimple> actual = fromFooSimpleList.stream()
                .map(transformerFunction)
                .collect(Collectors.toList());

or if you have a pre-configured transformer:

Function<FromFooSimple, ImmutableToFooSimple> transformerFunction = BeanUtils.getTransformer(<yourPreconfiguredTransformer>, ImmutableToFooSimple.class);
List<ImmutableToFooSimple> actual = fromFooSimpleList.stream()
                .map(transformerFunction)
                .collect(Collectors.toList());

Enable Java Beans validation:

Assuming that the field: id in the fromBean instance is null.

public class FromBean {                                     public class ToBean {                           
   private final String name;                                  @NotNull                   
   private final BigInteger id;                                public BigInteger id;                      
                                                               private final String name;

   // all args constructor                                     // all args constructor
   // getters...                                               // getters and setters...
}                                                            }

adding the following configuration an exception will be thrown:

ToBean toBean = beanUtils.getTransformer()
                     .setValidationEnabled(true)
                     .transform(fromBean, ToBean.class);

Copy on an existing instance:

Given:

public class FromBean {                                     public class ToBean {                           
   private final String name;                                  private String name;                   
   private final FromSubBean nestedObject;                     private ToSubBean nestedObject;                    

   // all args constructor                                     // constructor
   // getters...                                               // getters and setters...
}                                                           }

if you need to perform the copy on an already existing object, just do:

ToBean toBean = new ToBean();
beanUtils.getTransformer().transform(fromBean, toBean);

Skip transformation on a given set of fields:

Given:

public class FromBean {                                     public class ToBean {                           
   private final String name;                                  private String name;                   
   private final FromSubBean nestedObject;                     private ToSubBean nestedObject;                    

   // all args constructor                                     // constructor
   // getters...                                               // getters and setters...
}                                                           }

public class FromBean2 {                   
   private final int index;             
   private final FromSubBean nestedObject;
                                          
   // all args constructor                
   // getters...                          
}                                         

if you need to skip the transformation for a given field, just do:

ToBean toBean = new ToBean();
beanUtils.getTransformer()
    .skipTransformationForField("nestedObject")
    .transform(fromBean, toBean);

where nestedObject is the name of the field in the destination object.

This feature allows us to transform an object keeping the data from different sources.

To better explain this function let's assume that the ToBean (defined above) should be transformed as follow:

  • name field value has been taken from the FromBean object
  • nestedObject field value has been taken from the FromBean2 object

the objective can be reached by doing:

// create the destination object
ToBean toBean = new ToBean();

// execute the first transformation skipping the copy of: 'nestedObject' field that should come from the other source object
beanUtils.getTransformer()
    .skipTransformationForField("nestedObject")
    .transform(fromBean, toBean);

// then execute the transformation skipping the copy of: 'name' field that should come from the other source object
beanUtils.getTransformer()
    .skipTransformationForField("name")
    .transform(fromBean2, toBean);

Keep a field type value from the source object as is:

Given:

public class FromBean {                                     public class ToBean {                           
   private final String name;                                  private String name;                   
   private final DateTime dateTime;                            private final DateTime dateTime;              

   // all args constructor                                     // constructor
   // getters...                                               // getters and setters...
}                                                           }

if you need to keep the value of a field type from the source object as it, you can add all the types you want to keep as they are by doing:

ClassUtils.CUSTOM_SPECIAL_TYPES.add(DateTime.class);

ToBean toBean = new ToBean();
beanUtils.getTransformer()
    .transform(fromBean, toBean);

Not existing field in the source object:

In case the destination class has a field that does not exist in the source object, but it contains a getter method returning the value, the library should gets the field value from that method.

public class FromBean {                                     public class ToBean {                           
                                                               private final BigInteger id;
    public BigInteger getId() {                                   
        return BigInteger.TEN;                                 // all args constructor
   }                                                           // getters...
}                                                               
                                                            }

And one line code as:

ToBean toBean = beanUtils.getTransformer().transform(fromBean, ToBean.class);

Transform primitive types automatically

Given the following Java Bean:

public class FromBean {                                     public class ToBean {                           
   private final String indexNumber;                           private final int indexNumber;                                 
   private final BigInteger id;                                public Long id;                      

   // constructors...                                          // constructors...
   // getters...                                               // getters and setters...

}                                                           }

as, by default the primitive type conversion is disabled, to get the above object converted we should have implemented transformer functions for both field indexNumber and id, but this can be done automatically by enabling the the functionality described above.

Transformer transformer = beanUtils.getTransformer()
                             .setPrimitiveTypeConversionEnabled(true);

ToBean toBean = transformer.transform(fromBean, ToBean.class);

IMPORTANT: The primitive type transformation (if enabled) is executed before any other FieldTransformer function is defined on a specific field. This means that once the FieldTransformer function will be executed the field value has already been transformed.

Builder supported patterns

The library supports the transformation of Java Bean using the following Builder patterns:

Standard pattern:

public class ItemType {
    private final Class<?> objectClass;
    private final Class<?> genericClass;

    ItemType(final Class<?> objectClass, final Class<?> genericClass) {
        this.objectClass = objectClass;
        this.genericClass = genericClass;
    }

    public static ItemTypeBuilder builder() {
        return new ItemType.ItemTypeBuilder();
    }

    // getter methods

    public static class ItemTypeBuilder {
        private Class<?> objectClass;
        private Class<?> genericClass;

        ItemTypeBuilder() {
        }

        public ItemTypeBuilder objectClass(final Class<?> objectClass) {
            this.objectClass = objectClass;
            return this;
        }

        public ItemTypeBuilder genericClass(final Class<?> genericClass) {
            this.genericClass = genericClass;
            return this;
        }

        public ItemType build() {
            return new ItemType(this.objectClass, this.genericClass);
        }
    }
}

Custom Builder pattern:

To enable the transformation of Java Beans using the following Builder pattern:

public class ItemType {
    private final Class<?> objectClass;
    private final Class<?> genericClass;

    ItemType(final ItemTypeBuilder builder) {
        this.objectClass = builder.objectClass;
        this.genericClass = builder.genericClass;
    }

    public static ItemTypeBuilder builder() {
        return new ItemType.ItemTypeBuilder();
    }

    // getter methods

    public static class ItemTypeBuilder {
        private Class<?> objectClass;
        private Class<?> genericClass;

        ItemTypeBuilder() {
        }

        public ItemTypeBuilder objectClass(final Class<?> objectClass) {
            this.objectClass = objectClass;
            return this;
        }

        public ItemTypeBuilder genericClass(final Class<?> genericClass) {
            this.genericClass = genericClass;
            return this;
        }

        public ItemType build() {
            return new ItemType(this);
        }
    }
}

It's needed to enable the custom Builder Transformation as follows:

ToBean toBean = new BeanTransformer()
                         .setCustomBuilderTransformationEnabled(true)
                         .transform(sourceObject, ToBean.class);

Transform Java Record

Simple case:

public record FromFooRecord(BigInteger id, String name) {    public record RecordToFoo(BigInteger id, String name) {                           
}                                                            }  

And one line code as:

var toBean = beanUtils.getTransformer().transform(fromBean, RecordToFoo.class);

Constraints:

  • the class's fields that have to be copied must not be static

More sample beans can be found in the test package: com.expediagroup.beans.sample

Third-party library comparison

Following a comparison between the BULL functionalities and the following Third-Party libraries:

BULL Apache Bean Utils Jackson Dozer
Mutable bean copy X X X X+
Mutable bean with nested objects X - X X+
Mutable bean extending classes X - X X+
Immutable bean copy X - - X*
Mixed bean copy X - - X+
Copy of beans without getter and setter methods defined X - - -
Mutable Bean with different field's name X - - X+
Mixed with different field's type X - - X+
Immutable with different field's type X - - X+
Mutable Bean containing collection type fields containing complex objects X - X X
Mixed Bean containing collection type fields containing complex objects X - - X+
Immutable Bean containing collection type fields containing complex objects X - - X+
Mutable Bean containing containing Map type fields with nested Maps inside. e.g. Map<String, Map<String, Integer>> X - X X
Mixed Bean containing containing Map type fields with nested Maps inside. e.g. Map<String, Map<String, Integer>> X - - X+
Immutable Bean containing containing Map type fields with nested Maps inside. e.g. Map<String, Map<String, Integer>> X - - X+
Annotation field validation X - X -

[*] Immutable types are not supported by Dozer. When a type doesn't have a no-arg constructor and all fields are final, Dozer can't perform the mapping. A workaround is introducing the Builder Pattern. An example can be found here [+] Requires a custom configuration

Performance

Let's have a look at the performance library performance. The test has been executed on the following objects:

  • Mutable objects
  • Mutable objects extending another mutable object
  • Immutable objects
  • Immutable objects extending another immutable object
  • Mixed objects
Mutable Immutable Mixed
Simple objects (without nested objects) ~0.011ms ~0.018ms NA
Complex objects (containing several nested object and several items in Map and Array objects) ~0.37ms ~0.21ms ~0.22ms
CPU/Heap usage ~0.2%/35 MB ~0.2%/30 MB ~0.2%/25 MB

Transformation time screenshot

Real case testing

The Bean Utils library has been tested on a real case scenario integrating it into a real edge service (called BPE). The purpose was to compare the latency introduced by the library plus the memory/CPU usage. The dashboard's screenshot shows the latency of the invoked downstream service (called BPAS) and the one where the library has been installed (BPE). Following the obtained results:

Classic transformer BeanUtils library
Throughput per second 60 60
Average CPU usage 0.3% 0.3%
Min/Max Heap Memory Usage (MB) 90/320 90/320
Average Latency than the downstream service +2ms +2ms
JVM stats screenshot screenshot screenshot
Dashboard screenshot screenshot screenshot

Validation samples

Validating a java bean has never been so simple. The library offers different APIs related to this, following some examples:

Validate a Java Bean:

Given the following bean:

public class SampleBean {                           
   @NotNull                   
   private BigInteger id;                      
   private String name;                 
   
   // constructor
   // getters and setters... 
}                                                               

an instance of the above object:

SampleBean sampleBean = new SampleBean();

And one line code as:

beanUtils.getValidator().validate(sampleBean);

this will throw an InvalidBeanException as the id field is null.

Retrieve the violated constraints:

Given the following bean:

public class SampleBean {                           
   @NotNull                   
   private BigInteger id;                      
   private String name;                 
   
   // constructor
   // getters and setters... 
}                                                               

an instance of the above object:

SampleBean sampleBean = new SampleBean();

And one line code as:

List<String> violatedConstraints = beanUtils.getValidator().getConstraintViolationsMessages(sampleBean);

this will return a list containing a constraint validation message for the id field as it's null and the constraint: @NotNull is not met.

in case it's needed to have the ConstraintViolation object:

Set<ConstraintViolation<Object>> violatedConstraints = beanUtils.getValidator().getConstraintViolations(sampleBean);

Primitive type object converter

Converts a given primitive value into the given primitive type. The supported types, in which an object can be converted (from/to), are:

  • Byte, byte or byte[]
  • Short or short
  • Integer or int
  • Long or long
  • Float or float
  • Double or double
  • BigDecimal
  • BigInteger
  • Character or char
  • Boolean or boolean
  • String

Convert a String into an int:

Given the following variable:

String indexNumber = "26062019";                                                        

to convert it in an int:

Converter converter = new BeanUtils().getPrimitiveTypeConverter();
int indexNumber = converter.convertValue(indexNumber, int.class);

Obtain a conversion function that converts from char to byte:

It's possible to obtain a type conversion function, reusable several times in different places. Assuming that the required conversion is from char to `byte

char c = '1';                                                        

the conversion function is retrieved through:

Converter converter = new BeanUtils().getPrimitiveTypeConverter();
Optional<Function<Object, Object>> conversionFunction = converter.getConversionFunction(char.class, byte.class);
byte converted = conversionFunction.map(processor -> processor.apply(c)).orElse(0);
  • in case the conversion is not needed as the primitive type and the destination type are the same it will return an empty Optional
  • in case the conversion function is unavailable or no not possible the method throws a: TypeConversionException

Map transformation samples

Samples on how to transform a Map and all others function applicable to it can be viewed here

Documentation

Detailed project documentation is available here, including some samples for testing the library inside your project.

An article that explains how it works, with suggestions and examples, is available on DZone: How to Transform Any Type of Java Bean With BULL

Credits

Created by: Fabio Borriello with the contribution of: Patrizio Munzi, Andrea Marsiglia, Giorgio Delle Grottaglie & the Hotels.com's Checkout team in Rome.

The application's logo has been designed by Rob Light.

Related articles

Release

All the instructions for releasing a new version are available at RELEASES.md

Badge your project

Bull enabled

Add the following snippet in your Markdown file:

[![Bull enabled](https://img.shields.io/badge/bull-enabled-red?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAA8FBMVEUAAADRKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi/RKi////+bJRrYAAAATnRSTlMAABoFB0UGATtcHQsinVIEAz3Dy6QwHmzlylsTl/uGVvXonjMm0+pLApjjNO3nv0H3/N9VTVnauEn49r391vCCcnsJCn76aa6LFxnVZrus5DL8AAAApElEQVR42k2NA7rDABAGM89mbZupbbd7/9sU8Xzc/aUYwN09igMeHp+eHR94eX17tx/w8fn1+m08gJ/fv3+XG+vh8fpE/AEPGI9gKCxXIrYlGhOJJ5KWI5UWyWRzmBP3+au/UASe0Qy/peujXKlyr9XyHJIrtbraaLa0x1tbdGKdlPbo9vqDQX84GstEu5kOZ/PFcrXOqpsf7bFN72B/mHA8LVAuoY0XA5ziPJYAAAAASUVORK5CYII=)](https://github.com/ExpediaGroup/bull)

Support

Join the chat at https://join.slack.com/t/bull-crew/shared_invite/enQtNjM1MTE5ODg1MTQzLWI5ODhhYTQ2OWQxODgwYzU1ODMxMWJiZDkzODM3OTJkZjBlM2MwMTI3ZWZjMmU0OGZmN2RmNjg4NWI2NTMzOTk

For any question, proposal, or help, please refer to the slack channel: #bull.

Legal

This project is available under the Apache 2.0 License.

Copyright 2018-2023 Expedia Inc.

bull's People

Contributors

antimoroma avatar dependabot-preview[bot] avatar dependabot[bot] avatar fborriello avatar gitter-badger avatar massdosage avatar mox601 avatar worldtiki avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

bull's Issues

Change Unit Test Language

Description

The purpose of this is to have the Unit Tests written in Kotlin instead of Java

Outcome

  • The library code is written in Java
  • Unit tests are written in Kotlin

Run with JDK8

Is your feature request related to a problem? Please describe.
when I try to import bull by Maven and use it in my project whose compile-level is JDK8, I got an error. Then I find out bull is compiled by JDK11.

Describe the solution you'd like
The features bull provided are really awesome~ So, I hope bull can provide a maven artifact like bean-utils-library-jdk8 which is compiled by JDK8.

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

Additional context
My english is not that good, I hope I have described my problem clearly. WELL DONE, bull !

Apply lambda function only on a specific field

Not unique field transformation
If anywhere in the destination object there are one or more fields with the same name and a lambda transformation function has been defined on it, the transformation will be applied on each occurrence.

e.g.

Given the following objects:

public class Source {
    private Foo foo;
    private ParentFoo parentFoo;
}

public class Destination {
    private Integer x;
    private ParentFoo parentFoo;
}
public class Foo {
    private Integer x;
}

public class ParentFoo {
    private Foo foo;
}

and the following transformation function:

FieldTransformer<Foo, Integer> fooTransformer = new FieldTransformer<>("x", x -> new Foo().getX());

this would be applied on both Destination.x and to: Destination.parentFoo.foo.x.

It would be useful to have the possibility to specify the full path of the field on which the transformation should be applied.

Thanks @Emi75 for providing this.

Skip transformation for a given set of fields.

It would be really useful to have the possibility to skip the transformation for a given set of fields.
The perfect scenario for this would be the case of copy on existing destination objects.

Java Bean validation skip

It would be useful to have the possibility to configure the transformer in order to skip the Java Bean validation.

Disable override of custom properties

Currently with:

    new com.expediagroup.beans.BeanUtils()
        .getTransformer()
        .skipTransformationForField("field")
        .transform(source, destination);

it sets to null destination field, but I wanted to keep existing value

Performance improvement

Performance improvement
Reduce the bean transformation time as much as possible.

Purpose
Review and refactor the existing functionalities (where possible) in order to make this library as fast as possible.
This will be an evergreen activity as there's always a margin for improvement

BULL Testing samples

Provide detailed documentation (and samples) of the possible testing methods that can be implemented for checking the lambda function transformation

100% Test Coverage

Description
As per today, the test coverage is: 97,3%, the purpose of this task is to reach 100%.

How the coverage is calculated
The plugin in charge of the test coverage calculation are Jacoco along with SonarCloud.
Jacoco it's executed during each build and produces a report inside the target folder of each module.
You can find it at:
[module-name]/target/site/jacoco

instead, the SonarCloud report is updated during each release

The purpose
The purpose is to identify all the "not covered" code areas and implement specific Unit Test for covering them.

Additional context
There are classes and packages excluded from the coverage calculation, such as enum, Java Bean, etc. The whole list of the excluded items are:

Transformation of Java Beans built through Builder

The library should be able to automatically transform classes that use the Builder pattern.
Given a source object:

import java.math.BigInteger;

public class FromFooSimple {
    public String name;
    public BigInteger id;

   // getter and setter
}

and the following destination object:

import java.math.BigInteger;

public class ToFoo {
    private String name;
    private BigInteger id;

    private ToFoo() {}
    
    // getters    

    public static class Builder {
        private String name;
        private BigInteger id;

        public Builder withName(String name) {
            this.name = name;
            return this;
        }

        public Builder withId(BigInteger id) {
            this.id = id;
            return this;
        }

        public ToFoo build() {
            ToFoo toFoo = new ToFoo();
            toFoo.id = this.id;
            toFoo.name = this.name;
            return toFoo;
        }
    }
}

The command:

beanUtils.getTransformer().transform([source obj instance], ToFoo.class);

should retun an instance of ToFoo object with all values set.

Automatic conversion of basic types

Overview
Make conversion of common object type (e.g. String -> Integer, BigDecimal -> Double, etc.) automatically

Desired solution
The "type" conversion has to be applied before executing the Field Transformation defined by the user.
The conversion should be available for the following types:

  • Byte, byteorbyte[]`
  • Short or short
  • Integer or int
  • Long or long
  • Float or float
  • Double or double
  • BigDecimal
  • BigInteger
  • Character or char
  • Boolean or boolean
  • String

Possible implementation
There should be a ConversionAnalyzer that given the source field type and the destination field type returns a lambda function that handles the conversion.
The lambda function should be added to the transformations related to the field and applied as the first operation

FieldTransformer is passed a default value instead of the original null

Describe the bug
When translating a Boolean field having a null value, the custom FieldTransformer is passed a default value instead of the value coming from the original bean.

To Reproduce
Steps to reproduce the behavior:

  1. Write a custom FieldTransformer to translate the work field value null to true:
FieldTransformer<Boolean, Boolean> nullToTrue =
                new FieldTransformer<>("work", aBoolean -> aBoolean == null || aBoolean);
  1. Configure it and transform a bean with a null
beanUtils.getTransformer()
                .withFieldTransformer(nullToTrue)
                .transform(fromFooSimpleNullFields, ImmutableToFooSimpleBoolean.class);
  1. The translated bean has a false value instead of the expected true

Expected behavior
FieldTransformer is working on the original value, not on a default one.

Fix race condition imposed by Map interface

Describe the bug
CacheManagerFactory. CACHE_MAP should have type ConcurrentMap instead of Map. This would allow the implementation to use the atomic ConcurrentMap.computeIfAbsent method when creating caches instead of Map.get and Map.put.

Kotlin constructor parameters with default break the transformer

Sample code:

import com.expediagroup.beans.BeanUtils

data class BeanFrom(
    val foo: String
)

data class BeanTo(
    val foo: String = "bar"
)

val beanFrom = BeanFrom("bar")
val beanTo: BeanTo = BeanUtils().transformer.transform(beanFrom, BeanTo::class.java)

This throws:

com.expediagroup.transformer.error.InvalidBeanException: Constructor's parameters name have been removed from the compiled code. This caused a problems during the: BeanTo injection. Consider to use: @ConstructorArg annotation: https://github.com/ExpediaGroup/bull#different-field-names-defining-constructor-args or add the property: <parameters>true</parameters> to your maven-compiler configuration
	at com.expediagroup.beans.transformer.TransformerImpl.handleInjectionException(TransformerImpl.java:201)
	at com.expediagroup.beans.transformer.TransformerImpl.injectValues(TransformerImpl.java:164)
	at com.expediagroup.beans.transformer.TransformerImpl.handleInjectionException(TransformerImpl.java:188)
	at com.expediagroup.beans.transformer.TransformerImpl.injectValues(TransformerImpl.java:164)
	at com.expediagroup.beans.transformer.TransformerImpl.injectValues(TransformerImpl.java:134)
	at com.expediagroup.beans.transformer.TransformerImpl.transform(TransformerImpl.java:69)
	at com.expediagroup.beans.transformer.AbstractBeanTransformer.transform(AbstractBeanTransformer.java:124)
	at Line_4.<init>(Line_4.kts:1)
Caused by: com.expediagroup.transformer.error.InstanceCreationException
	at com.expediagroup.transformer.utils.ClassUtils.getInstance(ClassUtils.java:548)
	at com.expediagroup.beans.transformer.TransformerImpl.injectValues(TransformerImpl.java:162)
Caused by: java.lang.reflect.InvocationTargetException
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
	at com.expediagroup.transformer.utils.ClassUtils.getInstance(ClassUtils.java:546)
Caused by: java.lang.NullPointerException: Parameter specified as non-null is null: method Line_2$BeanTo.<init>, parameter foo
	at Line_2$BeanTo.<init>(Line_2.kts)
	at Line_2$BeanTo.<init>(Line_2.kts:1)

The initial message is probably misleading. What happens here is that Kotlin creates an additional constructor with two more parameters (an int and a kotlin.jvm.internal.DefaultConstructorMarker), and BULL assumes that's the "all args" one.
That fails due to receiving the wrong number of arguments, and so BULL falls back to using the no args constructor and injecting fields.
But the no args constructor cannot be used, because the class expects a non-null value for its only field, and that yields the last exception you can see in the stack trace.

Group id rename into ExpediaGroup

Description

As per the transition to the new repository, the project groupId and the packages need to be renamed from hotels to expediagroup.

As a consequence of this change the library can be added inside any maven project by adding this dependency:

<dependency>
    <groupId>com.expediagroup.beans</groupId>
    <artifactId>bull-bom</artifactId>
    <version>x.y,z</version>
</dependency>

Use the new "hasPackage" assertion from AssertJ 3.18.0

Is your feature request related to a problem? Please describe.
Since the test dependency on AssertJ has been updated to 3.18.0, a new assertion is available to check a Class package:

Class<?> actual = ...

assertThat(actual).hasPackage("com.foo.bar");

while previously the best you could do was for example

assertThat(actual.getName()).startsWith("com.foo.bar");
// or in Java 9+
assertThat(actual.getPackageName()).equals("com.foo.bar");

Describe the solution you'd like
We should replace the existing usages of assertion on packages with the new one to make the tests more expressive. Currently, the only usages are in TransformerBytecodeAdapterTest, in the "transformer-bytecode-adapter" module.

Bean validation offered as public feature

Bean validation
validate a Java Bean against the defined constraints should be offered as a BeanUtils public feature.

Desired solution
Given the following java bean:

public class Foo {
    @NonNull
    private final String name;
    private final BigInteger id;
    
    // all args constructor
    // getters
}

and an instance of the above object, it would be useful to validate it through the following instruction:
BeanUtils.getValidator().validate(foo);

Generator Core Module

The module should be able, given two Java Beans Types, to analyze the source class and generate a transformer that maps it into the destination one.
The result of this will be available in memory.

Map transformer

Description
Implement a Map Transformer that offers the same features now available for the Java Bean:

  • key mapping: map the value of a given key into another one
  • key name transformation: apply a transformation of a given key name
  • element transformation: apply a transformation on a value for the given key

Desired solution
The ideal solution would have a new maven module (optional) dedicated to the Map transformation, its name should be bull-map-transformer.

  • The module has to be compiled only if the profile: bull-map-transformer is specified.
  • The existing module: bean-utils-library has to be renamed in bull-bean-transformer and has to be compiled when using the default profile but, not when the profile: bull-map-transformer is specified.
    The new transformer has to be retrieved with the instruction: new MapUtils().getTransformer().
    The Map transformer interface must have the same behaviour as the existing Transformer
  • Detailed documentation with examples and maven site section have to be provided

Additional context
Any concern, question or detail, can be discussed on the slack channel: #map-transformer-feature.

Retrieve values from getters if a field does not exists

Description
In case the destination class has a field that does not exist in the source object, but it contains a getter method returning the value, the library should get the field value from that method.

e.g.
Given:

A source object declared as follow:

public class SourceObject {

    public BigInteger getId() {
        return BigInteger.ONE;
    }

    public boolean isActive() {
        return Boolean.TRUE;
    }
}

and a destination object declared as follow:

public class TargetObject {
    private BigInteger id;
    private boolean active;

    public void setId(final BigInteger id) {
        this.id = id;
    }

    public void setActive(final boolean active) {
        this.active = active;
    }
}

The value of TargetObject.id should be retrived from SourceObject.getId() and TargetObject.active should be retrived from SourceObject.isActive()

Add possibility to define transformer function without input parameter

It would be useful to have the possibility to create a field transformer function that accepts a Supplier type function.

This feature would be used in case we should set a field value taking it from a constant or from an external object.

e.g.

Given the following source class:

public class SourceClass {
    private final String name;
    private final BigInteger id;
}

and the following destination class:

public class DestinationClass {
    private final String name;
    private final BigInteger id;
    private final Instant creationDate;
}

It should be possible to define the following transformation function for field creationDate>

FieldTransformer<Date> creationDateFieldTransformer =
new FieldTransformer<>("creationDate", () -> Instant.now());

Disable the default value set for primitive types in case they are null

Description.
as per the current implementation, the Transformer automatically assigns the primitive default value (0 for Integer, false for Boolean, etc..) to all primitive types fields, in the destination object, in case their correspondent field in the source object is null.
It would be good to have the possibility to disable this behaviour.

Possible solution
A solution would be to leave by default the current behaviour and have an additional method setDefaultValueSetEnabled that, if false, disables this mechanism.

Transform Map<K1,V> -> Map<K2,V>

In case of multiple distributed services which are exchanging data, in many cases the most generic deserialization format can be a simple Map<String, Object> collection with some LinkedMap or even more concrete/specific implementation. In many cases this helps to reduce the boilerplate code and avoid writing third-party beans.
Would it make sense or possible to add such feature, where map<k1,v> can be deep transformed into map<k2,v>?

Static "transform" method

Add static "transform" method to BeanUtils class like:

public static <T, R> Function<? extends T, R> getTransformer(Class<R> toBeanClass) {
    return fromBean -> new BeanUtils().getTransformer().transform(fromBean, toBeanClass);
}

This will give the possibility to use your library with streams like:

List<FromType> sourceList = ...

List<ToType> destinationList = sourceList.stream()
      .map(BeanUtils.getTransformer(ToType.class))
      .collect(Collectors.toList());

Wildcards types support

Description
As per today, the library is able to transform all the elements inside a Collection or a Map if their type is specified in the generic type, so it's not able to transform object described as following:

private final Collection<? super Object> collection;
private final Collection collection;

private final Map map;
private final Map<?, ?> map;
private final Map<? super Object, ? super Object> map;

The purpose of this issue is to make the library able to transform such objects.

BULL Step up! Efficiency and performance improval!

Description
Is there a better and efficient way to perform the same operations the application does? Can the algorithms be improved to reduce resource usage and increase the performances?
Well, this is your occasion to start (or continue) your collaboration on the project adding your added value!
BULL wants you!

Ideal solution
The ideal solution:

  • improves the existing algorithms
  • improves the performance and the efficiency
  • reduces the resources usage
  • doesn't change the output
  • doesn't reduce the test coverage

Replace Hamcrest with AssertJ

Is your feature request related to a problem? Please describe.
At present, the codebase uses the standard Hamcrest matchers for test assertions. While largely adopted, this library hasn't an expressive API, which works fine for simple assertions but falls short for more complex cases making the code difficult to read, often forcing test authors to write helper methods or classes. Moreover, its extension mechanism is clunky, resulting in very few developers writing custom Matchers.

Describe the solution you'd like
A valid alternative exists in the AssertJ library which has a fluent and very expressive API, and covers lots of common complex cases. It's fully compatible with Java 8+ and supports lambdas as assertion callbacks.
The API revolves around static methods assertThat(obj) that let you chain one or more assertions fluidly. To get a feeling of it check out the core features highlight page.

My proposal is to replace existing test assertions in BULL using AssertJ, which will provide a powerful and expressive API for complex assertions on objects/JavaBeans (there are a lot of them as test samples) without requiring extra code.

Dependencies checker

It would be useful to have a an automatic checker that:

  • informs the developer about dependencies updates during the build. It should run during the build with a specific profile: check-for-updates
    e.g.
    maven clean install -P check-for-updates

  • Make the build failing in case of the presence of same dependencies with different versions

[Core] Transformer model generator - JavaBean -> Mixed

given in input two types A and B, A following the JavaBean convention, B being mixed, that is with an accessible constructor and optional setters, generates the Transformer model through JavaPoet.

The output is: TypeSpec (from JavaPoet library)

Multiple Builder pattern support

Description

As per today, the BeanTransformer is able to transform Java Bean using the following builder pattern:

public class ItemType {
    private final Class<?> objectClass;
    private final Class<?> genericClass;

    ItemType(final Class<?> objectClass, final Class<?> genericClass) {
        this.objectClass = objectClass;
        this.genericClass = genericClass;
    }

    public static ItemTypeBuilder builder() {
        return new ItemType.ItemTypeBuilder();
    }

    // getter methods

    public static class ItemTypeBuilder {
        private Class<?> objectClass;
        private Class<?> genericClass;

        ItemTypeBuilder() {
        }

        public ItemTypeBuilder objectClass(final Class<?> objectClass) {
            this.objectClass = objectClass;
            return this;
        }

        public ItemTypeBuilder genericClass(final Class<?> genericClass) {
            this.genericClass = genericClass;
            return this;
        }

        public com.hotels.transformer.model.ItemType build() {
            return new ItemType(this.objectClass, this.genericClass);
        }
    }
}

The purpose is to support also the following pattern too:

public class ItemType {
    private final Class<?> objectClass;
    private final Class<?> genericClass;

    ItemType(final ItemTypeBuilder builder) {
        this.objectClass = builder.objectClass;
        this.genericClass = builder.genericClass;
    }

    public static ItemTypeBuilder builder() {
        return new ItemType.ItemTypeBuilder();
    }

    // getter methods

    public static class ItemTypeBuilder {
        private Class<?> objectClass;
        private Class<?> genericClass;

        ItemTypeBuilder() {
        }

        public ItemTypeBuilder objectClass(final Class<?> objectClass) {
            this.objectClass = objectClass;
            return this;
        }

        public ItemTypeBuilder genericClass(final Class<?> genericClass) {
            this.genericClass = genericClass;
            return this;
        }

        public com.hotels.transformer.model.ItemType build() {
            return new ItemType(this);
        }
    }
}

Acceptance Criteria

  • The solution must not degrade the performances
  • The BeanTransformer automatically recognized the Builder pattern and use it properly

"Generics" discover function

Description

A really useful function, this project should offer, is the possibility to figure out the "Generic" type of a Collection or a Map.
This function is also required to complete the implementation of a new Map Transformer (for more details please refer to issue: #88)

Desired solution
The ideal solution would be the implementation of a new method: getGenericType that takes in input any kind of Java Collection or Map and returns its Generic type(s).

e.g.

List<String> v = new ArrayList<String>();
Class<?> genericClazz = getGenericType(v.getClass()); // will return: String

and

Map<String, Integer> v = new HashMap<String, Integer>();
Pair<Class<?>, Class<?>> typeMap = getGenericType(v.getClass()); // will return a Pair Class<?> keyClass = typeMap.getLeft(); // will return: String
Class<?> elemClass = typeMap.getRight(); // will return: Integer

Additional context

The method should be added into the utility class: ReflectionUtil

Automatic transformation between different Enum types

Description
BULL already transforms enum values of the same type, that is the source and destination fields are of the same type. But when you need to map two different enum types you're forced to write a custom property transformer.
The desired feature is being able to map enum values of different types. Given the following types:

enum A {
    x, y
}

enum B {
    x, y
}

class Source {
    A type;
    // constructor, getters&setters...
}

class Destination {
    B type;
    // constructor, getters&setters...
}

we want the type field to be automatically mapped by the following instruction:

Destination d = new BeanUtils().getTransformer().transform(new Source(A.x), Destination.class);
assert d.getType() == B.x

The default behavior for this feature should be:

  • all enum values should map to constants with the same name
  • if the source enum has more values than destination, it should map to null

JDK8 Compatible version automatic release

Description
All the changes implemented on BULL are released in two different versions: one compatible with jdk8 and the other with compatibility with jdk11.

The jdk11 release is totally automatic and performed by a Travis build, but the jdk8 one is manual.

The purpose is to automatize this process

Migration to GitHub action

Description

As per the migration to the Expedia Group GitHub, it has been established to adopt GitHub actions as CI/CD

Requested implementation

Replicate the same behaviour previously on Travis to GitHub action

Enable one-to-many field mapping and field transformer

Description

As of today, it's possible to define a one-to-one FieldMapping and FieldTransformer between a source field and a destination one.

The purpose of this is to add the possibility to map the same source field to multiple destination fields:

var fieldMapping = new FieldMapping<String, List<String>>("sourceFieldName", "destinationFieldOne", "destinationFieldTwo");
var multipleDestinationFieldTransformer =
                new FieldTransformer<String, String>(List.of("sourceFieldName", "destinationFieldOne"),
                        val -> val.toUpperCase()));

Expected behaviour

Given the following source class:

public class SourceClass {
    private final String name;
    private final int id;
}

the following destination class:

public class DestinationClass {
    private final String name;
    private final int id;
    private final int index;
}

and the following operations:

var sourceObj = new SourceClass("foo", 123);

var multipleFieldMapping = new FieldMapping<>("id", "index", "identifier");

var multipleDestinationFieldTransformer =
                new FieldTransformer<String, String>(List.of("id", "index"),
                        val -> val + 1));

var destObj = new BeanUtils().getBeanTransformer()
                     .withFieldMapping(multipleFieldMapping)
                     .transform(sourceObj, DestinationClass.class);

System.out.println("name = " + destObj.getName());
System.out.println("id = " + destObj.getId());
System.out.println("index = " + destObj.getIndex());

the output will be:

name = foo
id = 124
index = 124

Acceptance Criteria

  • The transformer is mapping the specified source field value into all the specified destinations one

Exception messages improvement

It would be useful to improve the exception messages in order to be more descriptive in order to facilitate the error debugging

Generator Source Adapter

The module should take the transformers generated from the Core module and produce source file representations. Those will be compiled as part of the standard build of the client project.
The output could be a JavaFile (from Java Poet library) to be written explicitly by the client, or a Path where the file has been written: this depends if we want the adapter to be free of side-effects.

  • The class package should be customizable or have a default value and it will correspond to a path on disk where the file will be written according to Java convention.
  • A base path, where the package directories will be written, should be customizable or have a default value.
  • In order to be testable and decoupled from the underlying filesystem, the adapter should be injected with a java.nio.file.FileSystem.
  • For testing purposes, an in-memory filesystem library could be used such as https://github.com/google/jimfs

To maintain uniformity in this project, a common interface should be extracted from this adapter and the Bytecode Adapter, to anticipate future adapters and to decouple the module's clients from concrete implementations.

This module is intended as internal usage in Bull and should not be referenced directly by clients.

Transformer generation modules setup

Description.

As part of the Transformer Generation project, it is required to set up the project modules.

Desired solution

The "Transformer generation" modules have to be built on demand, using a specific maven profile.
This will speed up the process and avoid the build of modules not strictly required.

The modules must have the following hierarchy:

  • bull-transformer-generator-parent
    • transformer-generator-core
    • transformer-bytecode-adapter
    • transformer-generator-registry
    • transformer-source-adapter

The module: bull-transformer-generator-parent will have bean-utils-library-parent as a parent.
All the others:

  • transformer-generator-core
  • transformer-bytecode-adapter
  • transformer-generator-registry
  • transformer-source-adapter

will have as a parent: bull-transformer-generator-parent

How to get a many to one field mapping for a transformer?

Please see my code.

@Data
@AllArgsConstructor
class Name {
    private String first;
    private String last;
}

@Data
class User {
    private String name;
}

public class TransformerTest {
    @Test
    public void testMultiSourceField() {
        BeanUtils beanUtils = new BeanUtils();
        BeanTransformer t = beanUtils.getTransformer()
                .withFieldMapping(new FieldMapping<>("this?", "name")) //want to map first AND last to name.
                .withFieldTransformer(new FieldTransformer<>("name", (Name object) -> object.getFirst()+ " " + object.getLast())); 
        Name name = new Name("f","l");
        User user = t.transform(name, User.class);
        assertThat(user.getName(),equalTo("f l"));
    }
}

Static transformation with a given transformer

The static transformation became a widely used feature, but, as it is now, it uses a brand new Transformer (dynamically generated) without any custom configuration.

The solution I'd like
Possibility to use the static transformation feature with a given Transformer properly configured.

Describe alternatives you've considered
Alternatively, the same result can be obtained without using static transformation.

Example call

Transformer myCustomTransformer = 
                new BeanUtils().getTransformer()
                        .withFieldMapping(new FieldMapping("name", "differentName"));

Function<FromFooSimple, ImmutableToFooSimple> transformerFunction = BeanUtils.getTransformer(myCustomTransformer, ImmutableToFooSimple.class);
List<ImmutableToFooSimple> actual = fromFooSimpleList.stream()
                .map(transformerFunction)
                .collect(Collectors.toList());

Copy on existing destination

It would be useful to add the possibility to copy "source" bean properties to an existing destination bean instance like:
beanUtils.getTransformer().transform(fromBean, toBean);

This can improve flexibility:

  • I can use transformer into my bean constructor
  • I can use properties compositing from multiple sources

Java Records copy

Description
with the release of Java 16 and the introduction of new class type, such as Records it's required to enhance the library to make it able to copy this kind of objects too.

Outcome of this implementation
After this implementation, the library should be able to copy objects like:

public record Foo(String value, int aNumber, byte[] someBytes) {
}

Additional context
As to have this in place, it's needed to make the project running with the latest Java releases (at least jdk14) the feature will not be available in the BULL jdk11 compatible version

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.