/* * 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<Object>(working); final Iterable<?> removed = new LinkedList<Object>(base); final Iterable<?> known = new LinkedList<Object>(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; } }