All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.danielbechler.diff.differ.CollectionDiffer Maven / Gradle / Ivy

There is a newer version: 0.95
Show newest version
/*
 * Copyright 2015 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.CollectionItemAccessor;
import de.danielbechler.diff.access.Instances;
import de.danielbechler.diff.comparison.ComparisonStrategy;
import de.danielbechler.diff.comparison.ComparisonStrategyResolver;
import de.danielbechler.diff.identity.IdentityStrategy;
import de.danielbechler.diff.identity.IdentityStrategyResolver;
import de.danielbechler.diff.node.DiffNode;
import de.danielbechler.util.Assert;

import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;

/**
 * Used to find differences between {@link Collection Collections}.
 *
 * @author Daniel Bechler
 */
public final class CollectionDiffer implements Differ
{
	private final DifferDispatcher differDispatcher;
	private final ComparisonStrategyResolver comparisonStrategyResolver;
	private final IdentityStrategyResolver identityStrategyResolver;

	public CollectionDiffer(final DifferDispatcher differDispatcher,
							final ComparisonStrategyResolver comparisonStrategyResolver,
							final IdentityStrategyResolver identityStrategyResolver)
	{
		Assert.notNull(differDispatcher, "differDispatcher");
		this.differDispatcher = differDispatcher;

		Assert.notNull(comparisonStrategyResolver, "comparisonStrategyResolver");
		this.comparisonStrategyResolver = comparisonStrategyResolver;

		Assert.notNull(identityStrategyResolver, "identityStrategyResolver");
		this.identityStrategyResolver = identityStrategyResolver;
	}

	public boolean accepts(final Class type)
	{
		return Collection.class.isAssignableFrom(type);
	}

	public final DiffNode compare(final DiffNode parentNode, final Instances collectionInstances)
	{
		final DiffNode collectionNode = newNode(parentNode, collectionInstances);
		final IdentityStrategy identityStrategy = identityStrategyResolver.resolveIdentityStrategy(collectionNode);
		if (identityStrategy != null)
		{
			collectionNode.setChildIdentityStrategy(identityStrategy);
		}
		if (collectionInstances.hasBeenAdded())
		{
			final Collection addedItems = collectionInstances.getWorking(Collection.class);
			compareItems(collectionNode, collectionInstances, addedItems, identityStrategy);
			collectionNode.setState(DiffNode.State.ADDED);
		}
		else if (collectionInstances.hasBeenRemoved())
		{
			final Collection removedItems = collectionInstances.getBase(Collection.class);
			compareItems(collectionNode, collectionInstances, removedItems, identityStrategy);
			collectionNode.setState(DiffNode.State.REMOVED);
		}
		else if (collectionInstances.areSame())
		{
			collectionNode.setState(DiffNode.State.UNTOUCHED);
		}
		else
		{
			final ComparisonStrategy comparisonStrategy = comparisonStrategyResolver.resolveComparisonStrategy(collectionNode);
			if (comparisonStrategy == null)
			{
				compareInternally(collectionNode, collectionInstances, identityStrategy);
			}
			else
			{
				compareUsingComparisonStrategy(collectionNode, collectionInstances, comparisonStrategy);
			}
		}
		return collectionNode;
	}

	private static DiffNode newNode(final DiffNode parentNode,
									final Instances collectionInstances)
	{
		final Accessor accessor = collectionInstances.getSourceAccessor();
		final Class type = collectionInstances.getType();
		return new DiffNode(parentNode, accessor, type);
	}

	private void compareItems(final DiffNode collectionNode,
							  final Instances collectionInstances,
							  final Iterable items,
							  final IdentityStrategy identityStrategy)
	{
		for (final Object item : items)
		{
			final Accessor itemAccessor = new CollectionItemAccessor(item, identityStrategy);
			differDispatcher.dispatch(collectionNode, collectionInstances, itemAccessor);
		}
	}

	private void compareInternally(final DiffNode collectionNode,
								   final Instances collectionInstances,
								   final IdentityStrategy identityStrategy)
	{
		final Collection working = collectionInstances.getWorking(Collection.class);
		final Collection base = collectionInstances.getBase(Collection.class);

		final Iterable added = new LinkedList(working);
		final Iterable removed = new LinkedList(base);
		final Iterable known = new LinkedList(base);

		remove(added, base, identityStrategy);
		remove(removed, working, identityStrategy);
		remove(known, added, identityStrategy);
		remove(known, removed, identityStrategy);

		compareItems(collectionNode, collectionInstances, added, identityStrategy);
		compareItems(collectionNode, collectionInstances, removed, identityStrategy);
		compareItems(collectionNode, collectionInstances, known, identityStrategy);
	}

	private static void compareUsingComparisonStrategy(final DiffNode collectionNode,
													   final Instances collectionInstances,
													   final ComparisonStrategy comparisonStrategy)
	{
		comparisonStrategy.compare(collectionNode,
				collectionInstances.getType(),
				collectionInstances.getWorking(Collection.class),
				collectionInstances.getBase(Collection.class));
	}

	private void remove(final Iterable from, final Iterable these, final IdentityStrategy identityStrategy)
	{
		final Iterator iterator = from.iterator();
		while (iterator.hasNext())
		{
			final Object item = iterator.next();
			if (contains(these, item, identityStrategy))
			{
				iterator.remove();
			}
		}
	}

	private boolean contains(final Iterable haystack, final Object needle, final IdentityStrategy identityStrategy)
	{
		for (final Object item : haystack)
		{
			if (identityStrategy.equals(needle, item))
			{
				return true;
			}
		}
		return false;
	}
}