de.danielbechler.diff.differ.DifferDispatcher Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of java-object-diff Show documentation
Show all versions of java-object-diff Show documentation
Framework to detect and handle differences between Java objects
The newest version!
/*
* Copyright 2014 Daniel Bechler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.danielbechler.diff.differ;
import de.danielbechler.diff.access.Accessor;
import de.danielbechler.diff.access.Instances;
import de.danielbechler.diff.access.PropertyAwareAccessor;
import de.danielbechler.diff.category.CategoryResolver;
import de.danielbechler.diff.introspection.PropertyReadException;
import de.danielbechler.diff.circular.CircularReferenceDetector;
import de.danielbechler.diff.circular.CircularReferenceDetectorFactory;
import de.danielbechler.diff.circular.CircularReferenceExceptionHandler;
import de.danielbechler.diff.filtering.IsReturnableResolver;
import de.danielbechler.diff.inclusion.IsIgnoredResolver;
import de.danielbechler.diff.introspection.PropertyAccessExceptionHandler;
import de.danielbechler.diff.introspection.PropertyAccessExceptionHandlerResolver;
import de.danielbechler.diff.node.DiffNode;
import de.danielbechler.diff.path.NodePath;
import de.danielbechler.diff.selector.ElementSelector;
import de.danielbechler.util.Assert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static de.danielbechler.diff.circular.CircularReferenceDetector.CircularReferenceException;
/**
* @author Daniel Bechler
*/
public class DifferDispatcher
{
private static final Logger logger = LoggerFactory.getLogger(DifferDispatcher.class);
private final DifferProvider differProvider;
private final CircularReferenceDetectorFactory circularReferenceDetectorFactory;
private final CircularReferenceExceptionHandler circularReferenceExceptionHandler;
private final IsIgnoredResolver isIgnoredResolver;
private final CategoryResolver categoryResolver;
private final IsReturnableResolver isReturnableResolver;
private final PropertyAccessExceptionHandlerResolver propertyAccessExceptionHandlerResolver;
private static final ThreadLocal workingThreadLocal = new ThreadLocal();
private static final ThreadLocal baseThreadLocal = new ThreadLocal();
public DifferDispatcher(final DifferProvider differProvider,
final CircularReferenceDetectorFactory circularReferenceDetectorFactory,
final CircularReferenceExceptionHandler circularReferenceExceptionHandler,
final IsIgnoredResolver ignoredResolver,
final IsReturnableResolver returnableResolver,
final PropertyAccessExceptionHandlerResolver propertyAccessExceptionHandlerResolver,
final CategoryResolver categoryResolver)
{
Assert.notNull(differProvider, "differFactory");
this.differProvider = differProvider;
Assert.notNull(ignoredResolver, "ignoredResolver");
this.isIgnoredResolver = ignoredResolver;
Assert.notNull(categoryResolver, "categoryResolver");
this.categoryResolver = categoryResolver;
this.circularReferenceDetectorFactory = circularReferenceDetectorFactory;
this.circularReferenceExceptionHandler = circularReferenceExceptionHandler;
this.isReturnableResolver = returnableResolver;
this.propertyAccessExceptionHandlerResolver = propertyAccessExceptionHandlerResolver;
resetInstanceMemory();
}
public final void resetInstanceMemory()
{
workingThreadLocal.set(circularReferenceDetectorFactory.createCircularReferenceDetector());
baseThreadLocal.set(circularReferenceDetectorFactory.createCircularReferenceDetector());
}
public final void clearInstanceMemory()
{
workingThreadLocal.remove();
baseThreadLocal.remove();
}
/**
* Delegates the call to an appropriate {@link Differ}.
*
* @return A node representing the difference between the given {@link Instances}.
*/
public DiffNode dispatch(final DiffNode parentNode,
final Instances parentInstances,
final Accessor accessor)
{
Assert.notNull(parentInstances, "parentInstances");
Assert.notNull(accessor, "accessor");
final DiffNode node = compare(parentNode, parentInstances, accessor);
if (parentNode != null && isReturnableResolver.isReturnable(node))
{
parentNode.addChild(node);
}
if (node != null)
{
node.addCategories(categoryResolver.resolveCategories(node));
}
return node;
}
private DiffNode compare(final DiffNode parentNode, final Instances parentInstances, final Accessor accessor)
{
final DiffNode node = new DiffNode(parentNode, accessor, null);
if (isIgnoredResolver.isIgnored(node))
{
node.setState(DiffNode.State.IGNORED);
return node;
}
final Instances accessedInstances;
if (accessor instanceof PropertyAwareAccessor)
{
final PropertyAwareAccessor propertyAwareAccessor = (PropertyAwareAccessor) accessor;
try
{
accessedInstances = parentInstances.access(accessor);
}
catch (final PropertyReadException e)
{
node.setState(DiffNode.State.INACCESSIBLE);
final Class> parentType = parentInstances.getType();
final String propertyName = propertyAwareAccessor.getPropertyName();
final PropertyAccessExceptionHandler exceptionHandler = propertyAccessExceptionHandlerResolver
.resolvePropertyAccessExceptionHandler(parentType, propertyName);
if (exceptionHandler != null)
{
exceptionHandler.onPropertyReadException(e, node);
}
return node;
}
}
else
{
accessedInstances = parentInstances.access(accessor);
}
if (accessedInstances.areNull())
{
return new DiffNode(parentNode, accessedInstances.getSourceAccessor(), accessedInstances.getType());
}
else
{
return compareWithCircularReferenceTracking(parentNode, accessedInstances);
}
}
private DiffNode compareWithCircularReferenceTracking(final DiffNode parentNode,
final Instances instances)
{
DiffNode node = null;
try
{
rememberInstances(parentNode, instances);
try
{
node = compare(parentNode, instances);
}
finally
{
if (node != null)
{
forgetInstances(parentNode, instances);
}
}
}
catch (final CircularReferenceException e)
{
node = newCircularNode(parentNode, instances, e.getNodePath());
circularReferenceExceptionHandler.onCircularReferenceException(node);
}
if (parentNode == null)
{
resetInstanceMemory();
}
return node;
}
private DiffNode compare(final DiffNode parentNode, final Instances instances)
{
final Differ differ = differProvider.retrieveDifferForType(instances.getType());
if (differ == null)
{
throw new IllegalStateException("Couldn't create Differ for type '" + instances.getType() +
"'. This mustn't happen, as there should always be a fallback differ.");
}
return differ.compare(parentNode, instances);
}
protected static void forgetInstances(final DiffNode parentNode, final Instances instances)
{
final NodePath nodePath = getNodePath(parentNode, instances);
logger.debug("[ {} ] Forgetting --- WORKING: {} <=> BASE: {}", nodePath, instances.getWorking(), instances.getBase());
workingThreadLocal.get().remove(instances.getWorking());
baseThreadLocal.get().remove(instances.getBase());
}
private static NodePath getNodePath(final DiffNode parentNode, final Instances instances)
{
if (parentNode == null)
{
return NodePath.withRoot();
}
else
{
final NodePath parentPath = parentNode.getPath();
final ElementSelector elementSelector = instances.getSourceAccessor().getElementSelector();
return NodePath.startBuildingFrom(parentPath).element(elementSelector).build();
}
}
protected static void rememberInstances(final DiffNode parentNode, final Instances instances)
{
final NodePath nodePath = getNodePath(parentNode, instances);
logger.debug("[ {} ] Remembering --- WORKING: {} <=> BASE: {}", nodePath, instances.getWorking(), instances.getBase());
transactionalPushToCircularReferenceDetectors(nodePath, instances);
}
private static void transactionalPushToCircularReferenceDetectors(final NodePath nodePath, final Instances instances)
{
workingThreadLocal.get().push(instances.getWorking(), nodePath);
// TODO This needs to be solved more elegantly. If the push for one of these detectors fails,
// we need to make sure to revert the push to the other one, if it already happened.
try
{
baseThreadLocal.get().push(instances.getBase(), nodePath);
}
catch (final CircularReferenceException e)
{
workingThreadLocal.get().remove(instances.getWorking()); // rollback
throw e;
}
}
private static DiffNode findNodeMatchingPropertyPath(final DiffNode node, final NodePath nodePath)
{
if (node == null)
{
return null;
}
if (node.matches(nodePath))
{
return node;
}
return findNodeMatchingPropertyPath(node.getParentNode(), nodePath);
}
private static DiffNode newCircularNode(final DiffNode parentNode,
final Instances instances,
final NodePath circleStartPath)
{
final DiffNode node = new DiffNode(parentNode, instances.getSourceAccessor(), instances.getType());
node.setState(DiffNode.State.CIRCULAR);
node.setCircleStartPath(circleStartPath);
node.setCircleStartNode(findNodeMatchingPropertyPath(parentNode, circleStartPath));
return node;
}
}