expediagroup / bull Goto Github PK
View Code? Open in Web Editor NEWBULL - Bean Utils Light Library
Home Page: https://opensource.expediagroup.com/bull
License: Apache License 2.0
BULL - Bean Utils Light Library
Home Page: https://opensource.expediagroup.com/bull
License: Apache License 2.0
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,
byteor
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
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
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:
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.
Given in input two types A and B that follow the JavaBean convention, generates the Transformer model through JavaPoet.
The output is: TypeSpec (from JavaPoet library)
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:
null
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
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.
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
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
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)
given in input two types A and B, A following the JavaBean convention, B being immutable with an accessible constructor, generates the Transformer model through JavaPoet.
The output is: TypeSpec (from JavaPoet library)
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:
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()));
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
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());
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
Provide detailed documentation (and samples) of the possible testing methods that can be implemented for checking the lambda function transformation
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.
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.
Description
Implement a Map Transformer
that offers the same features now available for the Java Bean:
Desired solution
The ideal solution would have a new maven module (optional) dedicated to the Map
transformation, its name should be bull-map-transformer
.
bull-map-transformer
is specified.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.new MapUtils().getTransformer()
.Transformer
Additional context
Any concern, question or detail, can be discussed on the slack channel: #map-transformer-feature.
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
BeanTransformer
automatically recognized the Builder pattern and use it properlyIt would be useful to have the possibility to configure the transformer in order to skip the Java Bean validation.
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
Integrate Coveralls to check the test coverage in the application: https://coveralls.io/
It would be useful to improve the exception messages in order to be more descriptive in order to facilitate the error debugging
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());
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.
java.nio.file.FileSystem
.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.
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
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:
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"));
}
}
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.
Description.
Use the maven-spotless-plugin
to automatically order and cleanup the code.
The purpose of this is to have the Unit Tests written in Kotlin instead of Java
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>
As per the migration to the Expedia Group GitHub, it has been established to adopt GitHub actions as CI/CD
Replicate the same behaviour previously on Travis to GitHub action
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());
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.
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 !
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);
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:
FieldTransformer
to translate the work
field value null
to true
:FieldTransformer<Boolean, Boolean> nullToTrue =
new FieldTransformer<>("work", aBoolean -> aBoolean == null || aBoolean);
null
beanUtils.getTransformer()
.withFieldTransformer(nullToTrue)
.transform(fromFooSimpleNullFields, ImmutableToFooSimpleBoolean.class);
false
value instead of the expected true
Expected behavior
FieldTransformer
is working on the original value, not on a default one.
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
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.
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.
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.
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.
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()
Identify the best Java library for source code generation, the suitable candidates are:
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>?
The Generator Bytecode Adapter will take in input a TypeSpec from the Core module and then :
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.