com.alee.api.merge.Merge Maven / Gradle / Ivy
The newest version!
/*
* This file is part of WebLookAndFeel library.
*
* WebLookAndFeel library is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* WebLookAndFeel library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with WebLookAndFeel library. If not, see .
*/
package com.alee.api.merge;
import com.alee.api.annotations.NotNull;
import com.alee.api.annotations.Nullable;
import com.alee.api.clone.Clone;
import com.alee.api.clone.CloneException;
import com.alee.api.matcher.IdentifiableMatcher;
import com.alee.api.merge.behavior.*;
import com.alee.api.merge.clonepolicy.PerformClonePolicy;
import com.alee.api.merge.clonepolicy.SkipClonePolicy;
import com.alee.api.merge.nullresolver.SkippingNullResolver;
import com.alee.api.merge.unknownresolver.ExceptionUnknownResolver;
import com.alee.utils.CollectionUtils;
import com.alee.utils.TextUtils;
import com.alee.utils.collection.ImmutableList;
import com.alee.utils.reflection.ModifierType;
import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Configurable algorithm for merging object instances.
* It can be customized through the settings provided in its constructor once on creation.
* To merge any two objects using this class instance call {@link #merge(Object, Object)} method.
* To merge multiple objects using this class instance call {@link #merge(Object, Object, Object...)} method.
* To merge all objects from a single collection using this class instance call {@link #merge(Collection)} method.
*
* @author Mikle Garin
* @see How to use Merge
* @see NullResolver
* @see UnknownResolver
* @see GlobalMergeBehavior
* @see MergeBehavior
* @see Mergeable
* @see Overwriting
*/
public final class Merge implements Serializable
{
/**
* Common lazy {@link Merge} instances cache.
*/
@Nullable
private static Map commons;
/**
* {@link ClonePolicy} for base object.
*/
@NotNull
private final ClonePolicy baseClonePolicy;
/**
* {@link ClonePolicy} for merged objects.
*/
@NotNull
private final ClonePolicy mergedClonePolicy;
/**
* Object merge {@code null} case resolver.
* It is used to resolve merge outcome when either of {@code source} and {@code merged} objects are {@code null}.
*
* @see NullResolver
*/
@NotNull
private final NullResolver nullResolver;
/**
* Unknown object types case resolver.
* It is used to resolve merge outcome when either of {@code source} and {@code merged} objects are not supported by behaviors.
*/
@NotNull
private final UnknownResolver unknownResolver;
/**
* List of behaviors taking part in this merge algorithm instance.
* These behaviors define which object types can actually be merged and which ones will simply be overwritten.
*
* @see GlobalMergeBehavior
*/
@NotNull
private final List behaviors;
/**
* Constructs new {@link Merge} algorithm.
*
* @param nullResolver object merge {@code null} case resolver
* @param unknownResolver unknown object types case resolver
* @param behaviors behaviors taking part in this merge algorithm instance
*/
public Merge ( @NotNull final NullResolver nullResolver, @NotNull final UnknownResolver unknownResolver,
@NotNull final GlobalMergeBehavior... behaviors )
{
this ( new SkipClonePolicy (), new SkipClonePolicy (), nullResolver, unknownResolver,
new ImmutableList ( behaviors ) );
}
/**
* Constructs new {@link Merge} algorithm.
*
* @param baseClonePolicy {@link ClonePolicy} for base object
* @param mergedClonePolicy {@link ClonePolicy} for merged objects
* @param nullResolver object merge {@code null} case resolver
* @param unknownResolver unknown object types case resolver
* @param behaviors behaviors taking part in this merge algorithm instance
*/
public Merge ( @NotNull final ClonePolicy baseClonePolicy, @NotNull final ClonePolicy mergedClonePolicy,
@NotNull final NullResolver nullResolver, @NotNull final UnknownResolver unknownResolver,
@NotNull final GlobalMergeBehavior... behaviors )
{
this ( baseClonePolicy, mergedClonePolicy, nullResolver, unknownResolver, new ImmutableList ( behaviors ) );
}
/**
* Constructs new {@link Merge} algorithm.
*
* @param nullResolver object merge {@code null} case resolver
* @param unknownResolver unknown object types case resolver
* @param behaviors behaviors taking part in this merge algorithm instance
*/
public Merge ( @NotNull final NullResolver nullResolver, @NotNull final UnknownResolver unknownResolver,
@NotNull final List behaviors )
{
this ( new SkipClonePolicy (), new SkipClonePolicy (), nullResolver, unknownResolver, behaviors );
}
/**
* Constructs new {@link Merge} algorithm.
*
* @param baseClonePolicy {@link ClonePolicy} for base object
* @param mergedClonePolicy {@link ClonePolicy} for merged objects
* @param nullResolver object merge {@code null} case resolver
* @param unknownResolver unknown object types case resolver
* @param behaviors behaviors taking part in this merge algorithm instance
*/
public Merge ( @NotNull final ClonePolicy baseClonePolicy, @NotNull final ClonePolicy mergedClonePolicy,
@NotNull final NullResolver nullResolver, @NotNull final UnknownResolver unknownResolver,
@NotNull final List behaviors )
{
this.baseClonePolicy = baseClonePolicy;
this.mergedClonePolicy = mergedClonePolicy;
this.nullResolver = nullResolver;
this.unknownResolver = unknownResolver;
this.behaviors = behaviors instanceof ImmutableList ? behaviors : new ImmutableList ( behaviors );
}
/**
* Performs merge of the two provided objects and returns resulting object.
* Depending on the case it might be one of the two provided objects, their copy or their merge result.
* Whether or not {@code base} and/or {@code merged} will be copied depends on {@link #baseClonePolicy} and {@link #mergedClonePolicy}.
*
* @param base base object
* @param merged object to merge
* @param resulting object type
* @return merge result
*/
@Nullable
public T merge ( @Nullable final Object base, @Nullable final Object merged )
{
final Object baseCopy = cloneBase ( base );
final Object mergedCopy = cloneMerged ( merged );
final InternalMerge internalMerge = new InternalMerge ();
return internalMerge.merge ( Object.class, baseCopy, mergedCopy, 0 );
}
/**
* Performs merge of the two provided objects and returns resulting non-{@code null} object.
* Depending on the case it might be one of the two provided objects, their copy or their merge result.
* Whether or not {@code base} and/or {@code merged} will be copied depends on {@link #baseClonePolicy} and {@link #mergedClonePolicy}.
*
* @param base base object
* @param merged object to merge
* @param resulting object type
* @return non-{@code null} merge result
*/
@NotNull
public T nonNullMerge ( @NotNull final Object base, @NotNull final Object merged )
{
final T result = merge ( base, merged );
if ( result == null )
{
throw new MergeException ( "Objects merge result is null:" + base + "\n <- " + merged );
}
return result;
}
/**
* Performs merge of all provided objects and returns resulting object.
* Depending on the case it might be one of the two provided objects, their copy or their merge result.
* Whether or not {@code base} and/or {@code merged} will be copied depends on {@link #baseClonePolicy} and {@link #mergedClonePolicy}.
*
* @param base base object
* @param merged object to merge
* @param more more objects to merge
* @param resulting object type
* @return merge result
*/
@Nullable
public T merge ( @Nullable final Object base, @Nullable final Object merged, @NotNull final Object... more )
{
final Object baseCopy = cloneBase ( base );
final Object mergedCopy = cloneMerged ( merged );
final InternalMerge internalMerge = new InternalMerge ();
Object result = internalMerge.merge ( Object.class, baseCopy, mergedCopy, 0 );
for ( final Object another : more )
{
final Object anotherCopy = cloneMerged ( another );
result = internalMerge.merge ( Object.class, result, anotherCopy, 0 );
}
return ( T ) result;
}
/**
* Performs merge of all provided objects and returns resulting non-{@code null} object.
* Depending on the case it might be one of the two provided objects, their copy or their merge result.
* Whether or not {@code base} and/or {@code merged} will be copied depends on {@link #baseClonePolicy} and {@link #mergedClonePolicy}.
*
* @param base base object
* @param merged object to merge
* @param more more objects to merge
* @param resulting object type
* @return non-{@code null} merge result
*/
@NotNull
public T nonNullMerge ( @NotNull final Object base, @NotNull final Object merged, @NotNull final Object... more )
{
final T result = merge ( base, merged, more );
if ( result == null )
{
throw new MergeException ( "Objects merge result is null:" + base + "\n <- " + merged +
"\n <- " + TextUtils.arrayToString ( "\n <- ", more ) );
}
return result;
}
/**
* Performs merge of all provided objects and returns resulting object.
* Depending on the case it might be one of the provided objects, their copy or their merge result.
* Whether or not {@code base} and/or {@code merged} will be copied depends on {@link #baseClonePolicy} and {@link #mergedClonePolicy}.
*
* @param objects objects to merge
* @param resulting object type
* @return merge result
*/
@Nullable
public T merge ( @NotNull final Collection> objects )
{
if ( CollectionUtils.notEmpty ( objects ) )
{
final Iterator> iterator = objects.iterator ();
final InternalMerge internalMerge = new InternalMerge ();
Object result = cloneBase ( iterator.next () );
while ( iterator.hasNext () )
{
final Object mergedCopy = cloneMerged ( iterator.next () );
result = internalMerge.merge ( Object.class, result, mergedCopy, 0 );
}
return ( T ) result;
}
else
{
throw new MergeException ( "At least one object must be specified for merge operation" );
}
}
/**
* Performs merge of all provided objects and returns resulting non-{@code null} object.
* Depending on the case it might be one of the provided objects, their copy or their merge result.
* Whether or not {@code base} and/or {@code merged} will be copied depends on {@link #baseClonePolicy} and {@link #mergedClonePolicy}.
*
* @param objects objects to merge
* @param resulting object type
* @return non-{@code null} merge result
*/
@NotNull
public T nonNullMerge ( @NotNull final Collection> objects )
{
final T result = merge ( objects );
if ( result == null )
{
throw new MergeException ( "Objects merge result is null:" + TextUtils.collectionToString ( objects, "\n <- " ) );
}
return result;
}
/**
* Returns either object or its clone based on {@link #baseClonePolicy}.
* This is an utility method mostly for {@link GlobalMergeBehavior} implementations.
*
* @param base object to clone
* @return either object or its clone based on {@link #baseClonePolicy}
*/
@Nullable
private Object cloneBase ( @Nullable final Object base )
{
return baseClonePolicy.clone ( base );
}
/**
* Returns either object or its clone based on {@link #mergedClonePolicy}.
* This is an utility method mostly for {@link GlobalMergeBehavior} implementations.
*
* @param merged object to clone
* @return either object or its clone based on {@link #mergedClonePolicy}
*/
@Nullable
private Object cloneMerged ( @Nullable final Object merged )
{
return mergedClonePolicy.clone ( merged );
}
/**
* {@link RecursiveMerge} implementation providing access to different {@link Merge} methods.
* It is used to process recursive merge calls differently from how public {@link Merge} methods process them.
*/
private class InternalMerge implements RecursiveMerge
{
@Nullable
@Override
public T merge ( @NotNull final Class type, @Nullable final Object base, @Nullable final Object merged, final int depth )
{
final T result;
if ( base != null && merged != null )
{
// Ensuring that merged object doesn't want to fully overwrite source one
if ( !( merged instanceof Overwriting && ( ( Overwriting ) merged ).isOverwrite () ) )
{
// Trying to find fitting merge behavior
Object mergeResult = null;
for ( final GlobalMergeBehavior behavior : behaviors )
{
// Checking that behavior supports objects
if ( behavior.supports ( this, type, base, merged ) )
{
// Executing merge behavior
mergeResult = behavior.merge ( this, type, base, merged, depth );
break;
}
}
// Resolving result object
result = mergeResult != null ? ( T ) mergeResult : ( T ) unknownResolver.resolve ( this, base, merged );
}
else
{
// Merged fully overwrites base
result = ( T ) merged;
}
}
else
{
// Resolving null case outcome
result = ( T ) nullResolver.resolve ( this, base, merged );
}
return result;
}
@NotNull
@Override
public T mergeFields ( @NotNull final Class type, @NotNull final Object base, @NotNull final Object merged, final int depth )
{
for ( final GlobalMergeBehavior behavior : behaviors )
{
if ( behavior instanceof ReflectionMergeBehavior )
{
return ( T ) behavior.merge ( this, type, base, merged, depth );
}
}
throw new CloneException ( "There is no ReflectionMergeBehavior in Merge algorithm" );
}
@Nullable
@Override
public Object overwrite ( @Nullable final Object base, @Nullable final Object merged )
{
final Object result;
if ( base != null && merged != null )
{
result = merged;
}
else
{
result = nullResolver.resolve ( this, base, merged );
}
return result;
}
}
/**
* Returns {@link Merge} algorithm that is able to merge basic object types.
* It also creates copy for both base and merged objects before merging them together.
*
* @return {@link Merge} algorithm that is able to merge basic object types
*/
@NotNull
public static Merge basic ()
{
final String identifier = "basic";
Merge merge = commonInstance ( identifier );
if ( merge == null )
{
merge = new Merge (
new PerformClonePolicy ( Clone.deep () ),
new PerformClonePolicy ( Clone.deep () ),
new SkippingNullResolver (),
new ExceptionUnknownResolver (),
new BasicMergeBehavior (),
new MergeableMergeBehavior (),
new IndexArrayMergeBehavior (),
new MapMergeBehavior (),
new ListMergeBehavior ( new IdentifiableMatcher () )
);
getCommons ().put ( identifier, merge );
}
return merge;
}
/**
* Returns {@link Merge} algorithm that is able to merge basic object types.
*
* @return {@link Merge} algorithm that is able to merge basic object types
*/
@NotNull
public static Merge basicRaw ()
{
final String identifier = "basicRaw";
Merge merge = commonInstance ( identifier );
if ( merge == null )
{
merge = new Merge (
new SkippingNullResolver (),
new ExceptionUnknownResolver (),
new BasicMergeBehavior (),
new MergeableMergeBehavior (),
new IndexArrayMergeBehavior (),
new MapMergeBehavior (),
new ListMergeBehavior ( new IdentifiableMatcher () )
);
getCommons ().put ( identifier, merge );
}
return merge;
}
/**
* Returns {@link Merge} algorithm that can also merge custom objects through {@link ReflectionMergeBehavior}.
* Be careful when using this merge algorithm as it will go through all object references and will merge any existing fields.
* It also creates copy for both base and merged objects before merging them together.
*
* @return {@link Merge} algorithm that can also merge custom objects through {@link ReflectionMergeBehavior}
*/
@NotNull
public static Merge deep ()
{
final String identifier = "deep";
Merge merge = commonInstance ( identifier );
if ( merge == null )
{
merge = new Merge (
new PerformClonePolicy ( Clone.deep () ),
new PerformClonePolicy ( Clone.deep () ),
new SkippingNullResolver (),
new ExceptionUnknownResolver (),
new BasicMergeBehavior (),
new MergeableMergeBehavior (),
new IndexArrayMergeBehavior (),
new MapMergeBehavior (),
new ListMergeBehavior ( new IdentifiableMatcher () ),
new ReflectionMergeBehavior ( ReflectionMergeBehavior.Policy.mergeable, ModifierType.STATIC )
);
getCommons ().put ( identifier, merge );
}
return merge;
}
/**
* Returns {@link Merge} algorithm that can also merge custom objects through {@link ReflectionMergeBehavior}.
* Be careful when using this merge algorithm as it will go through all object references and will merge any existing fields.
*
* @return {@link Merge} algorithm that can also merge custom objects through {@link ReflectionMergeBehavior}
*/
@NotNull
public static Merge deepRaw ()
{
final String identifier = "deepRaw";
Merge merge = commonInstance ( identifier );
if ( merge == null )
{
merge = new Merge (
new SkippingNullResolver (),
new ExceptionUnknownResolver (),
new BasicMergeBehavior (),
new MergeableMergeBehavior (),
new IndexArrayMergeBehavior (),
new MapMergeBehavior (),
new ListMergeBehavior ( new IdentifiableMatcher () ),
new ReflectionMergeBehavior ( ReflectionMergeBehavior.Policy.mergeable, ModifierType.STATIC )
);
getCommons ().put ( identifier, merge );
}
return merge;
}
/**
* Returns common {@link Merge} instance by its indentifier.
*
* @param identifier {@link Merge} instance indentifier
* @return common {@link Merge} instance by its indentifier
*/
@Nullable
private static Merge commonInstance ( final String identifier )
{
return getCommons ().get ( identifier );
}
/**
* Returns common instances {@link Map}.
*
* @return common instances {@link Map}
*/
@NotNull
private static Map getCommons ()
{
if ( commons == null )
{
synchronized ( Merge.class )
{
if ( commons == null )
{
commons = new ConcurrentHashMap ( 4 );
}
}
}
return commons;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy