-
Notifications
You must be signed in to change notification settings - Fork 235
How to use Merge
Available since: WebLaF v1.2.9 release
Required Java version: Java 6 update 30 or any later
Module: core
Merge
is a configurable utility allowing you to merge object settings. It was created to replace MergeUtils
with a more flexible and configurable option.
It is quite a niche utility and might not be required at all for a wide variety of projects, but due to specifics of WebLaF styling system and some other projects I am working on I decided to make a proper implementation of it.
It is quite flexible since each Merge
instance can be configured to behave differently, according to the provided settings.
WebLaF core
module offers two basic intefaces - Mergeable
and MergeBehavior
- which are heavily used by Merge
feature. Mergeable
is a simple indicator interface, just like widely known Cloneable
interface. Thought unlike Object.clone()
there is no default method for customizing merge of two objects for Mergeable
interface, that is why MergeBehavior
interface exists and offers merge(...)
method, but let me get to the details about it slightly later.
Merge
itself is a final
class that could be configured through providing different merge behavior settings upon its construction:
-
MergeNullResolver
- Defines how exactlynull
objects should be handled by default. SomeGlobalMergeBehavior
implementations might provide a different way to handle them. You can provide only oneMergeNullResolver
implementation. -
MergePolicy
- Defines whether merge operation can be performed on the two specified objects or not. For exampleExactTypeMergePolicy
require objects to have the same class type. You can provide only oneMergePolicy
implementation. -
GlobalMergeBehavior
- Defines how exactly two objects will be merged. For exampleListMergeBehavior
implements merge algorithm of twoList
objects. You can provide multipleGlobalMergeBehavior
implementations at once.
Custom implementations can be created of each of those interfaces to further customize Merge
behavior, but there are multiple implementations already available within the library.
For MergeNullResolver
:
-
OverwritingNullResolver
- Always overwrites base object -
SkippingNullResolver
- Overwrites base object only if merged object is notnull
For MergePolicy
:
-
AnyTypeMergePolicy
- Allows merge of any objects -
RelatedTypeMergePolicy
- Restricts merge to objects with related class types only -
ExactTypeMergePolicy
- Restricts merge to objects with the same class type only -
SpecificTypesMergePolicy
- Restricts merge to objects of the specified types only -
CompoundMergePolicy
- Restricts merge depending on specified policies
For GlobalMergeBehavior
:
-
BasicMergeBehavior
- Merge behavior for primitives, enumerations and some basic types -
MergeableMergeBehavior
- Merge behavior for properMergeBehavior
implementations usage -
IndexArrayMergeBehavior
-Array
merge behavior by their element indices -
MapMergeBehavior
-Map
merge behavior -
ListMergeBehavior
- SmartList
merge behavior that tracksIdentifiable
elements -
ReflectionMergeBehavior
- Smart merge behavior for any types ofObject
with related class types
All of these implementations are quite simple but are enough to support various complex merge algorithms. I will be extending list of available behaviors and settings in the future to provide even more ways to customize Merge
.
Main Merge
method you will be using is:
public <T> T merge ( final Object object, final Object merged )
Call to this method asks Merge
to do its best effort to merge merged
object settings on top of object
settings, so in most cases merged
object will be returned as the result, but with modified settings. When it is not possible to merge two objects - merged
will simply replace object
, which means merged
will be returned as the result instead.
Side note: With the future addition of Clone
there will be some extra settings coming to Merge
as well which will allow merged
and object
to be cloned before performing any merge operations. This might be quite important when you want to keep source objects intact.
So, first of all you need to instantiate Merge
with the settings you need:
public class MergeExample
{
public static void main ( final String[] args )
{
final Merge merge = new Merge (
new SkippingNullResolver (),
new RelatedTypeMergePolicy (),
new BasicMergeBehavior (),
new MergeableMergeBehavior (),
new IndexArrayMergeBehavior (),
new ReflectionMergeBehavior ()
);
}
}
I picked the most basic set of behaviors - one for primitives and basic types, one for MergeBehavior
implementations support, one for arrays merging support and the reflection one for all other cases - I will talk about it slightly later.
Now you can already perform merge operations on any objects.
The most simple case is merging primitives:
final Integer mergedInteger = merge.merge ( 1, 20 );
final Boolean mergedBoolean = merge.merge ( true, false );
Or merging some simple immutable objects like String
or Date
:
final String mergedText = merge.merge ( "Text 1", "Text 2" );
final Date mergedDate = merge.merge ( new Date (), new Date ( System.currentTimeMillis () - 604800000 ) );
The outcome for those merge operations will be:
20
false
and:
Text 2
Thu Mar 09 13:58:24 MSK 2017
At least today - second date is a week before current date.
All those merge operations in this example are handled by BasicMergeBehavior
which simply returns merged value as the result for primitives and immutable types. You can of course change the way Merge
behaves by providing different GlobalMergeBehavior
implementations, even for primitive types, into Merge
constructor.
Example above is not really interesting though, so let's try Merge
on something more complex. For that we will create a few custom classes containing some random data:
public class SampleObject
{
private final String title;
private final int value;
private final SampleInnerObject inner;
private final String[] states;
public SampleObject ( final String title, final int value, final String inner, final String... states )
{
this.title = title;
this.value = value;
this.inner = new SampleInnerObject ( inner );
this.states = states;
}
@Override
public String toString ()
{
return title + " - " + value + " - " + inner + " - " + Arrays.toString ( states );
}
}
And SampleInnerObject
class:
public class SampleInnerObject implements MergeBehavior<SampleInnerObject>
{
private String title;
public SampleInnerObject ( final String title )
{
this.title = title;
}
@Override
public SampleInnerObject merge ( final SampleInnerObject object )
{
title = title + " & " + object.title;
return this;
}
@Override
public String toString ()
{
return title;
}
}
I should straight up mention a few important things here you might miss:
- All fields within those two classes are
private
- In
SampleObject
all fields arefinal
- There are not getters or settings for any fields
- Unlike
SampleInnerObject
classSampleObject
doesn't implementMergeBehavior
- Method
toString()
is overridden for output purposes only
So let's try merging two instances of SampleObject
now:
final SampleObject object1 = new SampleObject ( "Object1", 1, "Inner1", "One", "Two", "Three" );
final SampleObject object2 = new SampleObject ( null, 2, "Inner2", "Three", "Four" );
final SampleObject mergedObject = merge.merge ( object1, object2 );
And the result is (defined by SampleObject.toString()
method):
Object1 - 2 - Inner1 & Inner2 - [Three, Four, Three]
And result referenced by mergedObject
variable is the same as object1
since all settings got merged into it.
So this is what exactly happened here:
- All
SampleObject
fields - even final ones - got their values replaced or updated withinobject1
instance - Field
title
value was preserved fromobject1
because it isnull
inobject2
and we are usingSkippingNullResolver
inMerge
- Array
states
received a new value - a combination of values fromobject2
andobject1
according toIndexArrayMergeBehavior
merge algorithm - Instance of
SampleInnerObject
referenced byinner
field got merged according to itsMergeBehavior
method implementation
All these actions went according to settings we provided into Merge
earlier. We could easily see that if some of the GlobalMergeBehavior
implementations would be excluded - we will get different result.
Let's try excluding IndexArrayMergeBehavior
and check the result again:
Object1 - 2 - Inner1 & Inner2 - [Three, Four]
Everything is the same except the array - it simply got replaced by object2
value because Merge
was not instructed about how it should process arrays.
Now let's exclude MergeableMergeBehavior
:
Object1 - 2 - Inner2 - [Three, Four]
Instance of SampleInnerObject
from object1
was preserved, but its title
field value got replaced by the value from SampleInnerObject
in object2
.
As you can see - every setting we provided can change the outcome of the merge operation and it is very important to understand how exactly each of the implementations work to make sure you get your objects merged properly.
To customize Merge
behavior you can create new implementations for MergeNullResolver
, MergePolicy
and GlobalMergeBehavior
interfaces.
I have already explained what each of those interfaces exist for, so I will go straight to examples here.
MergeNullResolver
Implementations of MergeNullResolver
are quite simple, but they do matter.
Let's make our own one:
public class PreserveNullResolver implements MergeNullResolver
{
@Override
public Object resolve ( final Merge merge, final Object object, final Object merged )
{
return object;
}
}
We only get into resolve ( ... )
method call when either object
or merged
is null
, so we basically ask Merge
to keep base object in such cases, even if it is the one being null
.
This might actually be handy if you have deeper meaning to null
values in your objects, for example if you don't want anything to be able to overwrite them.
MergePolicy
Implementations of MergePolicy
are basically global filters for merged objects, so when creating one you should consider that it will be applied to all kinds of objects when merging operation is performed.
Let's create one that will cover a wide variety of objects, but will still have a quite specific meaning behind it:
public final class SerializableMergePolicy implements MergePolicy
{
@Override
public boolean accept ( final Merge merge, final Object object, final Object merged )
{
return object instanceof Serializable && merged instanceof Serializable;
}
}
This MergePolicy
will only allow Merge
to merge classes which implement Serializable
interface.
It might be useful if you only want to merge objects which are Serializable
and nothing else. All non-Serializable
base objects will simply be overwritten by merged
object.
GlobalMergeBehavior
For this example I'll use sample classes and objects from above and show a custom GlobalMergeBehavior
implementation to handle some of the data.
Here is a custom GlobalMergeBehavior
implementation for SampleInnerObject
:
public class SampleInnerObjectMergeBehavior implements GlobalMergeBehavior<SampleInnerObject, SampleInnerObject, SampleInnerObject>
{
@Override
public boolean supports ( final Merge merge, final Object object, final Object merged )
{
return object instanceof SampleInnerObject && merged instanceof SampleInnerObject;
}
@Override
public SampleInnerObject merge ( final Merge merge, final SampleInnerObject object, final SampleInnerObject merged )
{
return new SampleInnerObject ( "Merge of: " + object.toString () + " & " + merged.toString () );
}
}
Now we need to modify Merge
construction to make use of it:
public class MergeExample
{
public static void main ( final String[] args )
{
final Merge merge = new Merge (
new SkippingNullResolver (),
new RelatedTypeMergePolicy (),
new BasicMergeBehavior (),
new SampleInnerObjectMergeBehavior (),
new IndexArrayMergeBehavior (),
new ReflectionMergeBehavior ()
);
}
}
I replaced MergeableMergeBehavior
with SampleInnerObjectMergeBehavior
as it will do merging for all objects of SampleInnerObject
type from now on.
Now we can run merge operation:
final SampleObject object1 = new SampleObject ( "Object1", 1, "Inner1", "One", "Two", "Three" );
final SampleObject object2 = new SampleObject ( null, 2, "Inner2", "Three", "Four" );
final SampleObject mergedObject = merge.merge ( object1, object2 );
And see the result:
Object1 - 2 - Merge of: Inner1 & Inner2 - [Three, Four, Three]
The only thing that changed is the resulting SampleInnerObject
value - in this case it was handled by SampleInnerObjectMergeBehavior
.
One more thing you might notice if you run a debug - this time SampleInnerObject
in the resulting object was a completely new instance unlike before. That is because we are constructing a new object within SampleInnerObjectMergeBehavior
upon merge.
This is important thing to mentione because normally when objects are merged all settings are applied to the base object instead of creating a new one, but that behavior might be customized - just like we did right now.
Merge
is a quite simple and yet powerful tool that can help you with merging your objects together.
That can be extremely helpful in some cases, for example you are using object instances to keep settings your application settings and you want to combine them at some point.
Though you should always understand how exactly that merge operation will behave to make sure things would work as intended.
If you found any mistakes or inconsistency in this article, feel that it is lacking explanation or simply want to request an additional wiki article covering some topic:
I will do my best to answer and provide assistance as soon as possible!